Lecture: Embracing Functions

Dear Computer

Chapter 8: Functions Revisited

Lecture: Embracing Functions

Dear students:

In this chapter you learned about four different ways of making functions in Haskell: named definitions, lambdas, partial application, and composition. These four tools are like the hammer and saw used by a carpenter. Instead of making furniture and homes, you assemble functions into algorithms. Today we'll assemble functions to solve a handful of programming challenges. We'll also see pattern matching and revisit the canonical higher-order functions map, filter, and fold.

In the most recent lab, you wrote the head utility. Likely it was more complex than it needed to be. That's because you hadn't yet learned about pattern matching and higher-order functions! Now that you have them in your repertoire, you can rewrite it like this:

Haskell
import System.Environment

main = do
  file : nString : _ <- getArgs
  text <- readFile file
  let n = read nString :: Int
  mapM_ putStrLn $ take n $ lines text

See that gauntlet on the last line? We could have written that as this composition:

Haskell
(mapM_ putStrLn . take n . lines) text

Map, Filter, and Fold

Before we dive into more complex programs, let's solve a few higher-order function problems together. Use map, filter, and foldl to solve these challenges:

Abbreviate a string to only its consonants.
Given a list of rectangular dimensions, each a pair, calculate the rectangles' total area.
Turn a list of times in milliseconds into a list of times in seconds.

I find map operations easiest to write. The time conversion is a map, and here's how I'd write it:

Haskell
millisToSeconds :: [Double] -> [Double]
millisToSeconds millis = map (* 1000) millis

If I recognize that I'm just building a function from other functions, I can assign to millisToSeconds rather than “define” a function:

Haskell
millisToSeconds = map (* 1000)

This is point-free style. I've partially applied the map function. We're still waiting on the list parameter.

Filter is the next easiest. We want to take the characters of the string that are not in "aeiou". The elem function tests if a value is in a list. That leads us to this point-free definition:

Haskell
consonants :: String -> String
consonants = filter (\c -> not $ elem c "aeiou")

Lastly we have the fold operation. We want to sum up the areas, so we write a lambda that destructures a rectangle, computes its area, and tacks it on to the accumulator:

Haskell
area :: [(Double, Double)] -> Double
area = foldl (\accum (w, h) -> accum + w * h) 0

parseInts

In many forthcoming problems, we are going to read in a bunch of whole numbers from a file. We'll need to convert a list of String to a list of Int. Let's write a helper method to perform this collective conversion. To make it reusable, we'll put it in a module in Utilities.hs:

Haskell
module Utilities where

parseInts :: [String] -> [Int]
parseInts = map read

Advent of Code 2018, Day 1

Write a program that solves Advent of Code 2018, Day 1. Accept a file name containing the input as a command-line argument.

Here's one possible solution:

Haskell
import System.Environment
import Utilities

main = do
  first : _ <- getArgs
  text <- readFile first
  let strippedText = filter (/= '+') text
  let frequencies = parseInts $ lines strippedText
  print $ sum frequencies

TODO

Here's your list of things to do before we meet next:

Complete the middle quiz as desired.
No class on Thursday, but complete the lab.
Keep learning Haskell. There will be one more chapter on types. Start going through the practice exams.

See you next time.

Sincerely,

← Higher-order I/OLab: Higher-order Mains →