get programming with haskell · lesson 7 rules for recursion and pattern matching 65 lesson 8...

16

Upload: trinhdung

Post on 26-Apr-2018

222 views

Category:

Documents


2 download

TRANSCRIPT

Page 1: Get Programming with Haskell · Lesson 7 Rules for recursion and pattern matching 65 Lesson 8 Writing recursive functions 74 ... Builder, you give a name to the anonymous function
Page 2: Get Programming with Haskell · Lesson 7 Rules for recursion and pattern matching 65 Lesson 8 Writing recursive functions 74 ... Builder, you give a name to the anonymous function

Get Programming with Haskellby Will Kurt

Lesson 5

Copyright 2018 Manning Publications

Page 3: Get Programming with Haskell · Lesson 7 Rules for recursion and pattern matching 65 Lesson 8 Writing recursive functions 74 ... Builder, you give a name to the anonymous function

v

Contents

Preface viiAcknowledgments ixAbout this book xAbout the author xiv

Lesson 1 Getting started with Haskell 1

Unit 1

FOUNDATIONS OF FUNCTIONAL PROGRAMMING

Lesson 2 Functions and functional programming 13

Lesson 3 Lambda functions and lexical scope 23

Lesson 4 First-class functions 33

Lesson 5 Closures and partial application 43

Lesson 6 Lists 54

Lesson 7 Rules for recursion and pattern matching 65

Lesson 8 Writing recursive functions 74

Lesson 9 Higher-order functions 83

Lesson 10 Capstone: Functional object-oriented pro-gramming with robots! 92

Unit 2

INTRODUCING TYPES

Lesson 11 Type basics 107

Lesson 12 Creating your own types 120

Lesson 13 Type classes 132

Lesson 14 Using type classes 142

Lesson 15 Capstone: Secret messages! 155

Unit 3

PROGRAMMING IN TYPES

Lesson 16 Creating types with “and” and “or” 175

Lesson 17 Design by composition—Semigroups and Monoids 187

Lesson 18 Parameterized types 201

Lesson 19 The Maybe type: dealing with missing values 214

Lesson 20 Capstone: Time series 225

Unit 4

IO IN HASKELL

Lesson 21 Hello World!—introducing IO types 249

Lesson 22 Interacting with the command line and lazy I/O 261

Lesson 23 Working with text and Unicode 271

Lesson 24 Working with files 282

Lesson 25 Working with binary data 294

Lesson 26 Capstone: Processing binary files and book data 308

Unit 5

WORKING WITH TYPE IN A CONTEXT

Lesson 27 The Functor type class 331

Page 4: Get Programming with Haskell · Lesson 7 Rules for recursion and pattern matching 65 Lesson 8 Writing recursive functions 74 ... Builder, you give a name to the anonymous function

vi Contents

Lesson 28 A peek at the Applicative type class: using functions in a context 343

Lesson 29 Lists as context: a deeper look at the Ap-plicative type class 357

Lesson 30 Introducing the Monad type class 372

Lesson 31 Making Monads easier with do-notation 387

Lesson 32 The list monad and list comprehensions 402

Lesson 33 Capstone: SQL-like queries in Haskell 411

Unit 6

ORGANIZING CODE AND BUILDING PROJECTS

Lesson 34 Organizing Haskell code with modules 431

Lesson 35 Building projects with stack 442

Lesson 36 Property testing with QuickCheck 452

Lesson 37 Capstone: Building a prime-number library 466

Unit 7

PRACTICAL HASKELL

Lesson 38 Errors in Haskell and the Either type 483

Lesson 39 Making HTTP requests in Haskell 497

Lesson 40 Working with JSON data by using Aeson 507

Lesson 41 Using databases in Haskell 524

Lesson 42 Efficient, stateful arrays in Haskell 544

Afterword What’s next? 561

Appendix Sample answers to exercises 566

Index 589

Page 5: Get Programming with Haskell · Lesson 7 Rules for recursion and pattern matching 65 Lesson 8 Writing recursive functions 74 ... Builder, you give a name to the anonymous function

43

5 LESSON

CLOSURES AND PARTIAL APPLICATION

After reading lesson 5, you’ll be able to

Capture values in a lambda expression Use closures to create new functions Simplify this process with partial application

In this lesson, you’ll learn the final key element of functional programming: closures. Closures are the logical consequence of having lambda functions and first-class functions. By combining these lambda functions and first-class functions to create closures, you can dynamically create functions. This turns out to be an incredibly powerful abstraction, though the one that takes the most getting used to. Haskell makes closures much easier to work with by allowing for partial application. By the end of the lesson, you’ll see how partial application makes otherwise confusing closures much easier to work with.

Consider this In the preceding lesson, you learned how to pass in programming logicto other functions because of first-class functions. For example, you might have a get-Price function that takes a URL and a website-specific price-extraction function:

getPrice amazonExtractor url

Although this is useful, what happens if you need to extract items from 1,000 URLs, butall using amazonExtractor? Is there a way to capture this argument on the fly so you haveto pass in only the url parameter for future calls?

Page 6: Get Programming with Haskell · Lesson 7 Rules for recursion and pattern matching 65 Lesson 8 Writing recursive functions 74 ... Builder, you give a name to the anonymous function

44 Lesson 5 Closures and partial application

5.1 Closures—creating functions with functions

In lesson 4, you defined a function named ifEven (listing 4.3). By using a function as an argument to ifEven, you were able to abstract out a pattern of computation. You then cre-ated the functions ifEvenInc, ifEvenDouble, and ifEvenSquare.

ifEvenInc n = ifEven inc nifEvenDouble n = ifEven double nifEvenSquare n = ifEven square n

Using functions as arguments helped to clean up your code. But you’ll notice you’re still repeating a programming pattern! Each of these definitions is identical except for the function you’re passing to ifEven. What you want is a function that builds ifEvenX func-tions. To solve this, you can build a new function that returns functions, called genIfEven, as shown in figure 5.1.

Now you’re passing in a function and returning a lambda function. The function f that you passed in is captured inside the lambda function! When you capture a value inside a lambda function, this is referred to as a closure.

Even in this small example, it can be difficult to understand exactly what’s happening. To see this better, let’s see how to create your ifEvenInc function by using genIfEven, as shown in figure 5.2.

Listing 5.1 ifEvenInc, ifEvenDouble, ifEvenSquare

You're returningthis entire lambdafunction.

The f argument is capturedin the lambda function.

Your new function is stillwaiting for an argument.

The argument f isthe function you wantto use in ifEven.

genIfEven f = (\x -> ifEven f x)

Figure 5.1 The genIfEven function lets you build ifEvenX functions simply.

Page 7: Get Programming with Haskell · Lesson 7 Rules for recursion and pattern matching 65 Lesson 8 Writing recursive functions 74 ... Builder, you give a name to the anonymous function

45Example: Generating URLs for an API

Now let’s move on to a real-world example of using closures to help build URLs to use with an API.

5.2 Example: Generating URLs for an API

One of the most common ways to get data is to make calls to a RESTful API by using an HTTP request. The simplest type of request is a GET request, in which all of the parame-ters you need to send to another server are encoded in the URL. In this example, the data you need for each request is as follows:

The hostname The name of the resource you’re requesting The ID of the resource Your API key

Figure 5.3 shows an example URL.

ifEvenInc = genIfEven inc (\x -> ifEven f x)

(\x -> ifEven inc x)

ifEvenInc = (\x -> ifEven inc x) Figure 5.2 ifEvenInc with closure

Quick check 5.1 Write a function genIfXEven that creates a closure with x and returns a newfunction that allows the user to pass in a function to apply to x if x is even.

QC 5.1 answer ifEven f x = if even x then f x else x

genIfXEven x = (\f -> ifEven f x)

Page 8: Get Programming with Haskell · Lesson 7 Rules for recursion and pattern matching 65 Lesson 8 Writing recursive functions 74 ... Builder, you give a name to the anonymous function

46 Lesson 5 Closures and partial application

Building a URL from these parts is straightforward. Here’s your basic getRequestURL builder.

getRequestURL host apiKey resource id = host ++ "/" ++ resource ++ "/" ++ id ++ "?token=" ++ apiKey

One thing that might strike you as odd about this function is that the order of your argu-ments isn’t the same as the order you use them or that they appear in the URL itself. Anytime you might want to use a closure (which in Haskell is pretty much anytime), you want to order your arguments from most to least general. In this case, each host can have multiple API keys, each API key is going to use different resources, and each resource is going to have many IDs associated with it. The same is true when you define ifEven; the function you pass will work with a huge range of inputs, so it’s more general and should appear first in the argument list.

Now that you have the basic request-generating function down, you can see how it works:

GHCi> getRequestURL "http://example.com" "1337hAsk3ll" "book" "1234"

"http://example.com/book/1234?token=1337hAsk3ll"

Great! This is a nice, general solution, and because your team as a whole will be query-ing many hosts, it’s important not to be too specific. Nearly every programmer on the team will be focusing on data from just a few hosts. It seems silly, not to mention error-prone, to have programmers manually type in http://example.com every time they need to make a request. What you need is a function that everyone can use to generate a request URL builder just for them. The answer to this is a closure. Your generator will look like figure 5.4.

Listing 5.2 getRequestUrl

Host

http://example.com/book/1234?token=1337hAsk3ll

API keyID

Resource Figure 5.3 Parts of a URL

Page 9: Get Programming with Haskell · Lesson 7 Rules for recursion and pattern matching 65 Lesson 8 Writing recursive functions 74 ... Builder, you give a name to the anonymous function

47Example: Generating URLs for an API

exampleUrlBuilder = genHostRequestBuilder "http://example.com"

When you pass the value example.com, you create a new, unnamed function that captures the host and needs only the three remaining arguments. When you define exampleUrl-Builder, you give a name to the anonymous function. Anytime you have a new URL that you want to make requests to, you now have an easy way to create a custom function for this. Load this function into GHCi and see how it simplifies your code:

GHCi> exampleUrlBuilder "1337hAsk3ll" "book" "1234""http://example.com/book/1234?token=1337hAsk3ll"

It’s clear you run into the same problem again when you look at apiKey. Passing your API key in each time you call exampleUrlBuilder is still tedious because you’ll likely be using only one or two API keys. Of course, you can use another closure to fix this! This time, you’ll have to pass both your exampleUrlBuilder function and your apiKey to your generator.

genApiRequestBuilder hostBuilder apiKey = (\resource id -> hostBuilder apiKey resource id)

What’s interesting here is that you’re combining both functions as arguments and func-tions as return values. Inside your closure is a copy of the specific function that you’re going to need, as well as the API key you need to capture. Finally, you can build a func-tion that makes creating a request URL much easier.

Listing 5.3 exampleUrlBuilder v.1

Listing 5.4 genApiRequestBuilder

Your new lambda function willbe waiting for three arguments.

genHostRequestBuilder host = (\apiKey resource id -> getRequestUrl host apikey resource id)

You're capturing the hostargument in this lambda function.

Figure 5.4 Capturing the host value in a closure

Page 10: Get Programming with Haskell · Lesson 7 Rules for recursion and pattern matching 65 Lesson 8 Writing recursive functions 74 ... Builder, you give a name to the anonymous function

48 Lesson 5 Closures and partial application

myExampleUrlBuilder = genApiRequestBuilder exampleUrlBuilder "1337hAsk3ll"

And you can use this to quickly create URLs for different resource/ID combos:

GHCi> myExampleUrlBuilder "book" "1234""http://example.com/book/1234?token=1337hAsk3ll"

5.2.1 Partial application: making closures simple

Closures are both powerful and useful. But the use of a lambda function to create the closure makes reading and reasoning about them more difficult than it should be. Addi-tionally, all the closures you’ve written so far follow a nearly identical pattern: provide some of the parameters that a function takes and create a new function awaiting the rest. Suppose you have a function add4 that takes four variables and adds them:

add4 a b c d = a + b + c + d

Now you want to create a function addXto3, which takes an argument x and then returns a closure awaiting the remaining three arguments:

addXto3 x = (\b c d ->add4 x b c d)

The explicit lambda makes it relatively hard to reason about what’s happening. What if you want to make an addXYto2?

addXYto2 x y = (\c d ->add4 x y c d)

With four arguments to manage visually, even this trivial function isn’t easy to under-stand. Lambda functions are powerful and useful, but can definitely clutter up other-wise neat function definitions.

Listing 5.5 myExampleUrlBuilder v.1

Quick check 5.2 Write a version of genApiRequestBuilder that also takes the resource as anargument.

QC 5.2 answer genApiRequestBuilder hostBuilder apiKey resource = (\id ->

hostBuilder apiKeyresource id)

Page 11: Get Programming with Haskell · Lesson 7 Rules for recursion and pattern matching 65 Lesson 8 Writing recursive functions 74 ... Builder, you give a name to the anonymous function

49Example: Generating URLs for an API

Haskell has an interesting feature that addresses this problem. What happens if you call add4 with fewer than four arguments? This answer seems obvious: it should throw an error. This isn’t what Haskell does. You can define a mystery value in GHCi by using Add4 and one argument:

GHCi> mystery = add4 3

If you run this code, you’ll find that it doesn’t cause an error. Haskell has created a brand new function for you:

GHCi> mystery 2 3 412GHCi> mystery 5 6 721

This mystery function adds 3 to the three remaining arguments you pass to it. When you call any function with fewer than the required number of parameters in Haskell, you get a new function that’s waiting for the remaining parameters. This language feature is called partial application. The mystery function is the same thing as if you wrote addXto3 and then passed in the argument 3 to it. Not only has partial application saved you from using a lambda function, but you don’t even need to define the awkwardly named addXto3! You can also easily re-create the behavior of addXYto2:

GHCi> anotherMystery = add4 2 3GHCi> anotherMystery 1 28GHCi> anotherMystery 4 514

If you find using closures confusing so far, you’re in luck! Thanks to partial application, you rarely have to write or think explicitly about closures in Haskell. All of the work of genHostRequestBuilder and genApiRequestBuilder is built in and can be replaced by leaving out the arguments you don’t need.

exampleUrlBuilder = getRequestUrl "http://example.com" myExampleUrlBuilder = exampleUrlBuilder "1337hAsk3ll"

In some cases in Haskell, you’ll still want to use lambda functions to create a closure, but using partial application is far more common. Figure 5.5 shows the process of par-tial application.

Listing 5.6 exampleUrlBuilder v.2 and myExampleUrlBuilder v.2

Page 12: Get Programming with Haskell · Lesson 7 Rules for recursion and pattern matching 65 Lesson 8 Writing recursive functions 74 ... Builder, you give a name to the anonymous function

50 Lesson 5 Closures and partial application

5.3 Putting it all together

Partial application is also the reason we created the rule that arguments should be ordered from most to least general. When you use partial application, the arguments are applied first to last. You violated this rule when you defined your addressLetter function in lesson 4 (listing 4.6):

addressLetter name location = locationFunction name where locationFunction = getLocationFunction location

In addressLetter, the name argument comes before the location argument. It makes much more sense that you’d want to create a function addressLetterNY that’s waiting for a name,

host apiKey idresource

Your new lambda function willbe waiting for three arguments:

Now you supplythe apiKey.

Finally you end up with a functionwaiting for two arguments.

exampleUrlBuilder = getRequestUrl "http://example.com" ? ? ?

myExampleUrlBuilder = exampleUrlBuilder "1337hAsk3ll" ? ?

myExampleUrlBuilder resource id

Figure 5.5 Visualizing partial application

Quick check 5.3 Make a builder function that’s specifically for http://example.com, the1337hAsk3ll API key, and the book resource. That’s a function that requires only the ID of a spe-cific book and then generates the full URL.

QC 5.3 answer exampleBuilder = getRequestUrl "http://example.com" "1337hAsk3ll" "books"

Page 13: Get Programming with Haskell · Lesson 7 Rules for recursion and pattern matching 65 Lesson 8 Writing recursive functions 74 ... Builder, you give a name to the anonymous function

51Putting it all together

rather than an addressLetterBobSmith that will write letters to all the Bob Smiths of the world. Rather than rewriting your function, which might not always be possible if you’re using functions from another library, you can fix this by creating a partial-application-friendly version, as follows.

addressLetterV2 location name = addressLetter name location

This is a fine solution for the one-time case of fixing your addressLetter function. What if you inherited a code base in which many library functions had this same error in the case of two arguments? It’d be nice to find a general solution to this problem rather than individually writing out each case. Combining all the things you’ve learned so far, you can do this in a simple function. You want to make a function called flipBinaryArgs that will take a function, flip the order of its arguments, and then return it otherwise untouched. To do this, you need a lambda function, first-class functions, and a closure. You can put all these together in a single line of Haskell, as shown in figure 5.6.

Now you can rewrite addressLetterV2 by using flipBinaryArgs, and then create an addressLetterNY:

addressLetterV2 = flipBinaryArgs addressLetteraddressLetterNY = addressLetterV2 "ny"

And you can test this out in GHCi:

GHCi> addressLetterNY ("Bob","Smith")Bob Smith: PO Box 789 - New York, NY, 10013

Your flipBinaryArgs function is useful for more than fixing code that didn’t follow our generalization guidelines. Plenty of binary functions have a natural order, such as

Listing 5.7 addressLetterV2

Lambda function used tocreate returning functionFunction as argument

flipBinaryArgs binaryFunction = (\x y -> binaryFunction y x)

Closure created withfunction argument

Figure 5.6 The flipBinaryArgs function

Page 14: Get Programming with Haskell · Lesson 7 Rules for recursion and pattern matching 65 Lesson 8 Writing recursive functions 74 ... Builder, you give a name to the anonymous function

52 Lesson 5 Closures and partial application

division. A useful trick in Haskell is that any infix operator (such as +, /, -, *) can be used as a prefix function by putting parentheses around it:

GHCi> 2 + 35GHCi> (+) 2 35GHCi> 10 / 25.0GHCi> (/) 10 25.0

In division and subtraction, the order of arguments is important. Despite there being a natural order for the arguments, it’s easy to understand that you might want to create a closure around the second argument. In these cases, you can use flipBinaryArgs to help you. Because flipBinaryArgs is such a useful function, there’s an existing function named flip that behaves the same.

Summary

In this lesson, our objective was to teach the important idea of a closure in functional programming. With lambda functions, first-class functions, and closures, you have all you need to perform functional programming. Closures combine lambda functions and first-class functions to give you amazing power. With closures, you can easily create new functions on the fly. You also learned how partial application makes working with closures much easier. After you’re used to using partial application, you may sometimes forget you’re working with closures at all! Let’s see if you got this.

Q5.1 Now that you know about partial application, you no longer need to use genIfEvenX. Redefine ifEvenInc, ifEvenDouble, and ifEvenSquare by using ifEven and partial application.

Quick check 5.4 Use flip and partial application to create a function called subtract2 thatremoves 2 from whatever number is passed in to it.

QC 5.4 answer subtract2 = flip (-) 2

Page 15: Get Programming with Haskell · Lesson 7 Rules for recursion and pattern matching 65 Lesson 8 Writing recursive functions 74 ... Builder, you give a name to the anonymous function

53Summary

Q5.2 Even if Haskell didn’t have partial application, you could hack together some approximations. Following a similar pattern to flipBinaryArgs (figure 5.6), write a func-tion binaryPartialApplication that takes a binary function and one argument and returns a new function waiting for the missing argument.

Page 16: Get Programming with Haskell · Lesson 7 Rules for recursion and pattern matching 65 Lesson 8 Writing recursive functions 74 ... Builder, you give a name to the anonymous function