Flashback to July 2012!

ITiCSE 2012

ITiCSE 2012

Dear students,

There are at least four ways of defining functions in Haskell:

- by defining a named function
- by partially applying parameters to get a new function expecting only the remaining parameters
- by composing a gauntlet of functions together
- by defining unnamed lambdas

We’ve seen the first two. Now let’s turn to composition. You’ve almost certainly worked with composition in your math classes. What is meant by *(f . g)(x)*? It means *g* is evaluated at *x*, and its result is used to further evaluate *f*.

Composing means to chain operations together, much like pipelining at the shell. The difference between composition and pipelining or Haskell’s `$`

operator is a matter of precedence. Haskell’s compose operator lets us chain functions together *before* we evaluate any of them. We can produce new functions by composing old ones together:

h = f . g h 5 -- these two calls will f (g 5) -- produce the same result

That means we can eliminate a lot of clutter from our code! Those gauntlets of function calls that our data must pass through can be shrunk down to a single meaningful name.

Time for some examples:

- Let’s write
`rsort`

which sorts a list in reverse:rsort = Ord a => [a] -> [a] rsort = reverse . sort

- Let’s write
`scalebias`

to map a value using a linear function:scalebias :: Double -> Double -> Double -> Double scalebias scale bias x = scale * x + bias -- without composition scalebias' scale bias = (+ bias) . (* scale) -- with composition

Now we write`calc2fahren`

like we should have back in high school:c2f :: Double -> Double c2f = scalebias' 1.8 32

- Let’s write a function to compute the number of negatives in a list:
nnegatives :: [Integer] -> Int nnegatives = length . filter (< 0)

- Let’s write a function that gives us the second element of a list, the neck:
neck :: [a] -> a neck = head . tail

- Let’s write a function that gives us a list of the unique items within another list:
onlies :: Ord a => [a] -> [a] onlies = concat . filter single . group . sort where single list = length list == 1

- Let’s write a function to create a spiral. As a first stop, let’s write some utility type synonyms and the (big three) functions to transform a 2D point:
type Point = (Double, Double) scale :: Double -> Point -> Point scale factor (x, y) = (factor * x, factor * y) translate :: (Double, Double) -> Point -> Point translate (dx, dy) (x, y) = (x + dx, y + dy) rotate :: Double -> Point -> Point rotate degrees (x, y) = (x', y') where radians = degrees * pi / 180 x' = x * cos radians - y * sin radians y' = x * sin radians + y * cos radians

We can make a spiral by repeatedly scaling and rotating a point. Each step will be this compound operation:spiral degrees growthRate = rotate degrees . scale growthRate

Okay, now it’s time to write a standalone Haskell program that does its own IO instead of operating in the REPL. Sadly, IO is one of those things that doesn’t fit well in a pure functional language. IO is stateful. It embeds a notion of time: first this line, then this line, and then that one… Pure function languages forbid the notion of time and state. They are about mathematical truth, which is timeless. We must leave the world of getters (expressions) and enter the world of doers (statements). Let’s start with a simple function that prints a`Point`

:print2 (x, y) = printf "%.5f,%.5f\n" x y

Now let’s write our first`main`

. It is different:main = do let p = (1, 0) print2 p let p' = spiral 1.05 20 p print2 p' return ()

We’ll talk about Haskell’s IO more another day. Right now let’s just remember these things: we must`return ()`

at the end, time sequences must be embedded in a`do`

block, and let bind values to identifiers with`let`

. What if we want to execute multiple steps? We can write a recursive method.`main`

will kick it off with the initial point and number of iterations:main = step (1, 0) 100

Since there’s only one line, we don’t need`do`

. The`step`

function might look this this:step p n = do let p' = spiral 1.05 20 p print2 p' if n > 0 then step p' (n - 1) else return ()

Now we can execute this script from the command-line:runhaskell foo.hs

To make this a little more flexible, let’s accept some command-line parameters:step :: (Double, Double) -> Double -> Double -> Int -> IO () step p rate degrees n = do let p' = spiral rate degrees p print2 p' if n > 0 then step p' rate degrees (n - 1) else return () main = do args <- getArgs let x = read (head args) :: Double let y = read (neck args) :: Double let rate = read (args !! 2) :: Double let degrees = read (args !! 3) :: Double let n = read (args !! 4) :: Int step (x, y) rate degrees n

- Let’s write a function to transform a rectangle around an arbitrary pivot point. Let’s represent with rectangle with four numbers: the x- and y-coordinates of the bottom-left corner, a width, and a height. Let’s create some helper functions to determine the coordinates of the points:
type Rectangle = (Point, Double, Double) bl (p, _, _) = p br ((left, bottom), width, _) = (left + width, bottom) tl ((left, bottom), _, height) = (left, bottom + height) tr ((left, bottom), width, height) = (left + width, bottom + height)

To rotate around an arbitrary pivot, we pass a point through this gauntlet:rotateAround :: Point -> Double -> Point -> Point rotateAround pivot@(dx, dy) degrees = translate pivot . rotate degrees . translate (-dx, -dy)

Finally, we can write a`main`

to rotate a rectangle by the number of degrees provided at the command-line:main = do args <- getArgs let degrees = read (head args) :: Double let r = (0, 0, 1, 1) print2 $ rotateAround (1, 1) degrees (bl r) print2 $ rotateAround (1, 1) degrees (br r) print2 $ rotateAround (1, 1) degrees (tr r) print2 $ rotateAround (1, 1) degrees (tl r) print2 $ rotateAround (1, 1) degrees (bl r) return ()

Here’s your TODO list:

- Read Execution in the Kingdom of Nouns. On a quarter sheet, write down 2-3 questions, observations, or thoughtful agreements or disagreements.

See you next time, when we discuss lambdas and higher-order functions!

Sincerely,

P.S. It’s Haiku Friday!

scramble = fry . whisk . crack hardboil = trash . peel . boil vandalize = freeze . throw

- May 2017
- April 2017
- March 2017
- February 2017
- January 2017
- December 2016
- November 2016
- October 2016
- September 2016
- August 2016
- July 2016
- June 2016
- May 2016
- April 2016
- March 2016
- February 2016
- January 2016
- December 2015
- November 2015
- October 2015
- September 2015
- August 2015
- June 2015
- May 2015
- April 2015
- March 2015
- February 2015
- January 2015
- December 2014
- November 2014
- October 2014
- September 2014
- August 2014
- July 2014
- June 2014
- May 2014
- April 2014
- March 2014
- February 2014
- January 2014
- December 2013
- November 2013
- October 2013
- September 2013
- August 2013
- July 2013
- June 2013
- May 2013
- April 2013
- March 2013
- February 2013
- January 2013
- December 2012
- November 2012
- October 2012
- September 2012
- August 2012
- July 2012
- June 2012
- May 2012
- April 2012
- March 2012
- February 2012
- January 2012
- December 2011
- November 2011
- October 2011
- September 2011
- July 2011
- January 2011

- 3d
- algorithms
- aphasia
- art
- blackbox
- blender
- buster
- code
- courses
- cs145
- cs245
- cs318
- cs330
- cs352
- cs396
- cs436
- cs455
- cs491 mobile
- exams
- experiments
- failures
- fall 2011
- fall 2012
- fall 2013
- fall 2014
- fall 2015
- fall 2016
- fish vision
- gallery
- gamedev for learning
- gamedev_cse
- gamedev2
- gamedev3
- games
- git
- graphics
- honors 304.503
- honors gamedev
- howto
- keystrokes
- labs
- lectures
- madeup
- meta
- mobile
- mutt
- nature
- nbsn
- outreach
- pop computer science
- postmortems
- programming languages
- public
- ratorvaders
- reader animated
- reading
- research
- responses
- semesters
- software
- specchecker
- specifications
- spring 2012
- spring 2013
- spring 2014
- spring 2015
- spring 2016
- spring 2017
- talks
- teaching
- travel
- Uncategorized
- unity
- vim
- writing
- wrong number

## Leave a Reply