1. Introduction

1.1 Functional JavaScript

Functional programming techniques have been making appearances in JavaScript for some time now:

Libraries such as UnderscoreJS allow the developer to leverage tried-and-trusted functions such as map , filter and reduce to create larger programs from smaller programs by composition: var sumOfPrimes = _ . chain ( _ . range ( 1000 )) . filter ( isPrime ) . reduce ( function ( x , y ) { return x + y ; }) . value ();

, and to create larger programs from smaller programs by composition: Asynchronous programming in NodeJS leans heavily on functions as first-class values to define callbacks. require ( 'fs' ). readFile ( sourceFile , function ( error , data ) { if ( ! error ) { require ( 'fs' ). writeFile ( destFile , data , function ( error ) { if ( ! error ) { console . log ( "File copied" ); } }); } });

Libraries such as React and virtual-dom model views as pure functions of application state.

Functions enable a simple form of abstraction which can yield great productivity gains. However, functional programming in JavaScript has its own disadvantages: JavaScript is verbose, untyped, and lacks powerful forms of abstraction. Unrestricted JavaScript code also makes equational reasoning very difficult.

PureScript is a programming language which aims to address these issues. It features lightweight syntax, which allows us to write very expressive code which is still clear and readable. It uses a rich type system to support powerful abstractions. It also generates fast, understandable code, which is important when interoperating with JavaScript, or other languages which compile to JavaScript. All in all, I hope to convince you that PureScript strikes a very practical balance between the theoretical power of purely functional programming, and the fast-and-loose programming style of JavaScript.

1.2 Types and Type Inference

The debate over statically typed languages versus dynamically typed languages is well-documented. PureScript is a statically typed language, meaning that a correct program can be given a type by the compiler which indicates its behavior. Conversely, programs which cannot be given a type are incorrect programs, and will be rejected by the compiler. In PureScript, unlike in dynamically typed languages, types exist only at compile-time, and have no representation at runtime.

It is important to note that in many ways, the types in PureScript are unlike the types that you might have seen in other languages like Java or C#. While they serve the same purpose at a high level, the types in PureScript are inspired by languages like ML and Haskell. PureScript’s types are expressive, allowing the developer to assert strong claims about their programs. Most importantly, PureScript’s type system supports type inference - it requires far fewer explicit type annotations than other languages, making the type system a tool rather than a hindrance. As a simple example, the following code defines a number, but there is no mention of the Number type anywhere in the code:

iAmANumber = let square x = x * x in square 42.0

A more involved example shows that type-correctness can be confirmed without type annotations, even when there exist types which are unknown to the compiler:

iterate f 0 x = x iterate f n x = iterate f ( n - 1 ) ( f x )

Here, the type of x is unknown, but the compiler can still verify that iterate obeys the rules of the type system, no matter what type x might have.

In this book, I will try to convince you (or reaffirm your belief) that static types are not only a means of gaining confidence in the correctness of your programs, but also an aid to development in their own right. Refactoring a large body of code in JavaScript can be difficult when using any but the simplest of abstractions, but an expressive type system together with a type checker can even make refactoring into an enjoyable, interactive experience.

In addition, the safety net provided by a type system enables more advanced forms of abstraction. In fact, PureScript provides a powerful form of abstraction which is fundamentally type-driven: type classes, made popular in the functional programming language Haskell.

1.3 Polyglot Web Programming

Functional programming has its success stories - applications where it has been particularly successful: data analysis, parsing, compiler implementation, generic programming, parallelism, to name a few.

It would be possible to practice end-to-end application development in a functional language like PureScript. PureScript provides the ability to import existing JavaScript code, by providing types for its values and functions, and then to use those functions in regular PureScript code. We’ll see this approach later in the book.

However, one of PureScript’s strengths is its interoperability with other languages which target JavaScript. Another approach would be to use PureScript for a subset of your application’s development, and to use one or more other languages to write the rest of the JavaScript.

Here are some examples:

Core logic written in PureScript, with the user interface written in JavaScript.

Application written in JavaScript or another compile-to-JS language, with tests written in PureScript.

PureScript used to automate user interface tests for an existing application.

In this book, we’ll focus on solving small problems with PureScript. The solutions could be integrated into a larger application, but we will also look at how to call PureScript code from JavaScript, and vice versa.

1.4 Prerequisites

The software requirements for this book are minimal: the first chapter will guide you through setting up a development environment from scratch, and the tools we will use are available in the standard repositories of most modern operating systems.

The PureScript compiler itself can be downloaded as a binary distribution, or built from source on any system running an up-to-date installation of the GHC Haskell compiler, and we will walk through this process in the next chapter.

The code in this version of the book is compatible with versions 0.11.* of the PureScript compiler.

1.5 About You

I will assume that you are familiar with the basics of JavaScript. Any prior familiarity with common tools from the JavaScript ecosystem, such as NPM and Gulp, will be beneficial if you wish to customize the standard setup to your own needs, but such knowledge is not necessary.

No prior knowledge of functional programming is required, but it certainly won’t hurt. New ideas will be accompanied by practical examples, so you should be able to form an intuition for the concepts from functional programming that we will use.

Readers who are familiar with the Haskell programming language will recognize a lot of the ideas and syntax presented in this book, because PureScript is heavily influenced by Haskell. However, those readers should understand that there are a number of important differences between PureScript and Haskell. It is not necessarily always appropriate to try to apply ideas from one language in the other, although many of the concepts presented here will have some interpretation in Haskell.

1.6 How to Read This Book

The chapters in this book are largely self contained. A beginner with little functional programming experience would be well-advised, however, to work through the chapters in order. The first few chapters lay the groundwork required to understand the material later on in the book. A reader who is comfortable with the ideas of functional programming (especially one with experience in a strongly-typed language like ML or Haskell) will probably be able to gain a general understanding of the code in the later chapters of the book without reading the preceding chapters.

Each chapter will focus on a single practical example, providing the motivation for any new ideas introduced. Code for each chapter are available from the book’s GitHub repository. Some chapters will include code snippets taken from the chapter’s source code, but for a full understanding, you should read the source code from the repository alongside the material from the book. Longer sections will contain shorter snippets which you can execute in the interactive mode PSCi to test your understanding.

Code samples will appear in a monospaced font, as follows:

module Example where import Control.Monad.Eff.Console ( log ) main = log "Hello, World!"

Commands which should be typed at the command line will be preceded by a dollar symbol:

$ pulp build

Usually, these commands will be tailored to Linux/Mac OS users, so Windows users may need to make small changes such as modifying the file separator, or replacing shell built-ins with their Windows equivalents.

Commands which should be typed at the PSCi interactive mode prompt will be preceded by an angle bracket:

> 1 + 2 3

Each chapter will contain exercises, labelled with their difficulty level. It is strongly recommended that you attempt the exercises in each chapter to fully understand the material.

This book aims to provide an introduction to the PureScript language for beginners, but it is not the sort of book that provides a list of template solutions to problems. For beginners, this book should be a fun challenge, and you will get the most benefit if you read the material, attempt the exercises, and most importantly of all, try to write some code of your own.

1.7 Getting Help

If you get stuck at any point, there are a number of resources available online for learning PureScript:

The PureScript IRC channel is a great place to chat about issues you may be having. Point your IRC client at irc.freenode.net, and connect to the #purescript channel.

The PureScript website contains links to several learning resources, including code samples, videos and other resources for beginners.

The PureScript documentation repository collects articles and examples on a wide variety of topics, written by PureScript developers and users.

Try PureScript! is a website which allows users to compile PureScript code in the web browser, and contains several simple examples of code.

Pursuit is a searchable database of PureScript types and functions.

If you prefer to learn by reading examples, the purescript , purescript-node and purescript-contrib GitHub organizations contain plenty of examples of PureScript code.

1.8 About the Author

I am the original developer of the PureScript compiler. I’m based in Los Angeles, California, and started programming at an early age in BASIC on an 8-bit personal computer, the Amstrad CPC. Since then I have worked professionally in a variety of programming languages (including Java, Scala, C#, F#, Haskell and PureScript).

Not long into my professional career, I began to appreciate functional programming and its connections with mathematics, and enjoyed learning functional concepts using the Haskell programming language.

I started working on the PureScript compiler in response to my experience with JavaScript. I found myself using functional programming techniques that I had picked up in languages like Haskell, but wanted a more principled environment in which to apply them. Solutions at the time included various attempts to compile Haskell to JavaScript while preserving its semantics (Fay, Haste, GHCJS), but I was interested to see how successful I could be by approaching the problem from the other side - attempting to keep the semantics of JavaScript, while enjoying the syntax and type system of a language like Haskell.

I maintain a blog, and can be reached on Twitter.

1.9 Acknowledgements

I would like to thank the many contributors who helped PureScript to reach its current state. Without the huge collective effort which has been made on the compiler, tools, libraries, documentation and tests, the project would certainly have failed.

The PureScript logo which appears on the cover of this book was created by Gareth Hughes, and is gratefully reused here under the terms of the Creative Commons Attribution 4.0 license.

Finally, I would like to thank everyone who has given me feedback and corrections on the contents of this book.

2. Getting Started

2.1 Chapter Goals

In this chapter, the goal will be to set up a working PureScript development environment, and to write our first PureScript program.

Our first project will be a very simple PureScript library, which will provide a single function which can compute the length of the diagonal in a right-angled triangle.

2.2 Introduction

Here are the tools we will be using to set up our PureScript development environment:

purs - The PureScript compiler itself.

- The PureScript compiler itself. npm - The Node Package Manager, which will allow us to install the rest of our development tools.

- The Node Package Manager, which will allow us to install the rest of our development tools. Pulp - A command-line tool which automates many of the tasks associated with managing PureScript projects.

The rest of the chapter will guide you through installing and configuring these tools.

2.3 Installing PureScript

The recommended approach to installing the PureScript compiler is to download a binary release for your platform from the PureScript website.

You should verify that the PureScript compiler executables are available on your path. Try running the PureScript compiler on the command line to verify this:

$ purs

Other options for installing the PureScript compiler include:

Via NPM: npm install -g purescript .

. Building the compiler from source. Instructions can be found on the PureScript website.

If you do not have a working installation of NodeJS, you should install it. This should also install the npm package manager on your system. Make sure you have npm installed and available on your path.

You will also need to install the Pulp command line tool, and the Bower package manager using npm , as follows:

$ npm install -g pulp bower

This will place the pulp and bower command line tools on your path. At this point, you will have all the tools needed to create your first PureScript project.

2.5 Hello, PureScript!

Let’s start out simple. We’ll use Pulp to compile and run a simple Hello World! program.

Begin by creating a project in an empty directory, using the pulp init command:

$ mkdir my-project $ cd my-project $ pulp init * Generating project skeleton in ~/my-project $ ls bower.json src test

Pulp has created two directories, src and test , and a bower.json configuration file for us. The src directory will contain our source files, and the test directory will contain our tests. We will use the test directory later in the book.

Modify the src/Main.purs file to contain the following content:

module Main where import Control.Monad.Eff.Console main = log "Hello, World!"

This small sample illustrates a few key ideas:

Every file begins with a module header. A module name consists of one or more capitalized words separated by dots. In this case, only a single word is used, but My.First.Module would be an equally valid module name.

would be an equally valid module name. Modules are imported using their full names, including dots to separate the parts of the module name. Here, we import the Control.Monad.Eff.Console module, which provides the log function.

module, which provides the function. The main program is defined as a function application. In PureScript, function application is indicated with whitespace separating the function name from its arguments.

Let’s build and run this code using the following command:

$ pulp run * Building project in ~/my-project * Build successful. Hello, World!

Congratulations! You just compiled and executed your first PureScript program.

2.6 Compiling for the Browser

Pulp can be used to turn our PureScript code into Javascript suitable for use in the web browser, by using the pulp browserify command:

$ pulp browserify * Browserifying project in ~/my-project * Building project in ~/my-project * Build successful. * Browserifying...

Following this, you should see a large amount of Javascript code printed to the console. This is the output of the Browserify tool, applied to a standard PureScript library called the Prelude, as well as the code in the src directory. This Javascript code can be saved to a file, and included in a HTML document. If you try this, you should see the words “Hello, World!” printed to your browser’s console.

2.7 Removing Unused Code

Pulp provides an alternative command, pulp build , which can be used with the -O option to apply dead code elimination, which removes unnecessary Javascript from the output. The result is much smaller:

$ pulp build -O --to output.js * Building project in ~/my-project * Build successful. * Bundling Javascript... * Bundled.

Again, the generated code can be used in a HTML document. If you open output.js , you should see a few compiled modules which look like this:

( function ( exports ) { "use strict" ; var Control_Monad_Eff_Console = PS [ "Control.Monad.Eff.Console" ]; var main = Control_Monad_Eff_Console . log ( "Hello, World!" ); exports [ "main" ] = main ; })( PS [ "Main" ] = PS [ "Main" ] || {});

This illustrates a few points about the way the PureScript compiler generates Javascript code:

Every module gets turned into an object, created by a wrapper function, which contains the module’s exported members.

PureScript tries to preserve the names of variables wherever possible

Function applications in PureScript get turned into function applications in JavaScript.

The main method is run after all modules have been defined, and is generated as a simple method call with no arguments.

PureScript code does not rely on any runtime libraries. All of the code that is generated by the compiler originated in a PureScript module somewhere which your code depended on.

These points are important, since they mean that PureScript generates simple, understandable code. In fact, the code generation process in general is quite a shallow transformation. It takes relatively little understanding of the language to predict what JavaScript code will be generated for a particular input.

2.8 Compiling CommonJS Modules

Pulp can also be used to generate CommonJS modules from PureScript code. This can be useful when using NodeJS, or just when developing a larger project which uses CommonJS modules to break code into smaller components.

To build CommonJS modules, use the pulp build command (without the -O option):

$ pulp build * Building project in ~/my-project * Build successful.

The generated modules will be placed in the output directory by default. Each PureScript module will be compiled to its own CommonJS module, in its own subdirectory.

2.9 Tracking Dependencies with Bower

To write the diagonal function (the goal of this chapter), we will need to be able to compute square roots. The purescript-math package contains type definitions for functions defined on the JavaScript Math object, so let’s install it:

$ bower install purescript-math --save

The --save option causes the dependency to be added to the bower.json configuration file.

The purescript-math library sources should now be available in the bower_components subdirectory, and will be included when you compile your project.

2.10 Computing Diagonals

Let’s write the diagonal function, which will be an example of using a function from an external library.

First, import the Math module by adding the following line at the top of the src/Main.purs file:

import Math ( sqrt )

It’s also necessary to import the Prelude module, which defines very basic operations such as numeric addition and multiplication:

import Prelude

Now define the diagonal function as follows:

diagonal w h = sqrt ( w * w + h * h )

Note that there is no need to define a type for our function. The compiler is able to infer that diagonal is a function which takes two numbers and returns a number. In general, however, it is a good practice to provide type annotations as a form of documentation.

Let’s also modify the main function to use the new diagonal function:

main = logShow ( diagonal 3.0 4.0 )

Now compile and run the project again, using pulp run :

$ pulp run * Building project in ~/my-project * Build successful. 5.0

2.11 Testing Code Using the Interactive Mode

The PureScript compiler also ships with an interactive REPL called PSCi. This can be very useful for testing your code, and experimenting with new ideas. Let’s use PSCi to test the diagonal function.

Pulp can load source modules into PSCi automatically, via the pulp repl command:

$ pulp repl >

You can type :? to see a list of commands:

> :? The following commands are available: :? Show this help menu :quit Quit PSCi :reset Reset :browse <module> Browse <module> :type <expr> Show the type of <expr> :kind <type> Show the kind of <type> :show import Show imported modules :show loaded Show loaded modules :paste paste Enter multiple lines, terminated by ^D

By pressing the Tab key, you should be able to see a list of all functions available in your own code, as well as any Bower dependencies and the Prelude modules.

Start by importing the Prelude module:

> import Prelude

Try evaluating a few expressions now:

> 1 + 2 3 > "Hello, " <> "World!" "Hello, World!"

Let’s try out our new diagonal function in PSCi:

> import Main > diagonal 5.0 12.0 13.0

You can also use PSCi to define functions:

> double x = x * 2 > double 10 20

Don’t worry if the syntax of these examples is unclear right now - it will make more sense as you read through the book.

Finally, you can check the type of an expression by using the :type command:

> :type true Boolean > :type [1, 2, 3] Array Int

Try out the interactive mode now. If you get stuck at any point, simply use the Reset command :reset to unload any modules which may be compiled in memory.

Exercises (Easy) Use the pi constant, which is defined in the Math module, to write a function circleArea which computes the area of a circle with a given radius. Test your function using PSCi (Hint: don’t forget to import pi by modifying the import Math statement). (Medium) Use bower install to install the purescript-globals package as a dependency. Test out its functions in PSCi (Hint: you can use the :browse command in PSCi to browse the contents of a module).

2.12 Conclusion

In this chapter, we set up a simple PureScript project using the Pulp tool.

We also wrote our first PureScript function, and a JavaScript program which could be compiled and executed either in the browser or in NodeJS.

We will use this development setup in the following chapters to compile, debug and test our code, so you should make sure that you are comfortable with the tools and techniques involved.

3. Functions and Records

3.1 Chapter Goals

This chapter will introduce two building blocks of PureScript programs: functions and records. In addition, we’ll see how to structure PureScript programs, and how to use types as an aid to program development.

We will build a simple address book application to manage a list of contacts. This code will introduce some new ideas from the syntax of PureScript.

The front-end of our application will be the interactive mode PSCi, but it would be possible to build on this code to write a front-end in Javascript. In fact, we will do exactly that in later chapters, adding form validation and save/restore functionality.

3.2 Project Setup

The source code for this chapter is contained in the file src/Data/AddressBook.purs . This file starts with a module declaration and its import list:

module Data.AddressBook where import Prelude import Control.Plus ( empty ) import Data.List ( List ( .. ), filter , head ) import Data.Maybe ( Maybe )

Here, we import several modules:

The Control.Plus module, which defines the empty value.

module, which defines the value. The Data.List module, which is provided by the purescript-lists package which can be installed using Bower. It contains a few functions which we will need for working with linked lists.

module, which is provided by the package which can be installed using Bower. It contains a few functions which we will need for working with linked lists. The Data.Maybe module, which defines data types and functions for working with optional values.

Notice that the imports for these modules are listed explicitly in parentheses. This is generally a good practice, as it helps to avoid conflicting imports.

Assuming you have cloned the book’s source code repository, the project for this chapter can be built using Pulp, with the following commands:

$ cd chapter3 $ bower update $ pulp build

3.3 Simple Types

PureScript defines three built-in types which correspond to JavaScript’s primitive types: numbers, strings and booleans. These are defined in the Prim module, which is implicitly imported by every module. They are called Number , String , and Boolean , respectively, and you can see them in PSCi by using the :type command to print the types of some simple values:

$ pulp repl > :type 1.0 Number > :type "test" String > :type true Boolean

PureScript defines some other built-in types: integers, characters, arrays, records, and functions.

Integers are differentiated from floating point values of type Number by the lack of a decimal point:

> :type 1 Int

Character literals are wrapped in single quotes, unlike string literals which use double quotes:

> :type 'a' Char

Arrays correspond to JavaScript arrays, but unlike in JavaScript, all elements of a PureScript array must have the same type:

> :type [1, 2, 3] Array Int > :type [true, false] Array Boolean > :type [1, false] Could not match type Int with Boolean.

The error in the last example is an error from the type checker, which unsuccessfully attempted to unify (i.e. make equal) the types of the two elements.

Records correspond to JavaScript’s objects, and record literals have the same syntax as JavaScript’s object literals:

> author = { name: "Phil", interests: ["Functional Programming", "JavaScript"] } > :type author { name :: String , interests :: Array String }

This type indicates that the specified object has two fields, a name field which has type String , and an interests field, which has type Array String , i.e. an array of String s.

Fields of records can be accessed using a dot, followed by the label of the field to access:

> author.name "Phil" > author.interests ["Functional Programming","JavaScript"]

PureScript’s functions correspond to JavaScript’s functions. The PureScript standard libraries provide plenty of examples of functions, and we will see more in this chapter:

> import Prelude > :type flip forall a b c. (a -> b -> c) -> b -> a -> c > :type const forall a b. a -> b -> a

Functions can be defined at the top-level of a file by specifying arguments before the equals sign:

add :: Int -> Int -> Int add x y = x + y

Alternatively, functions can be defined inline, by using a backslash character followed by a space-delimited list of argument names. To enter a multi-line declaration in PSCi, we can enter “paste mode” by using the :paste command. In this mode, declarations are terminated using the Control-D key sequence:

> :paste … add :: Int -> Int -> Int … add = \x y -> x + y … ^D

Having defined this function in PSCi, we can apply it to its arguments by separating the two arguments from the function name by whitespace:

> add 10 20 30

3.4 Quantified Types

In the previous section, we saw the types of some functions defined in the Prelude. For example, the flip function had the following type:

> :type flip forall a b c. (a -> b -> c) -> b -> a -> c

The keyword forall here indicates that flip has a universally quantified type. It means that we can substitute any types for a , b and c , and flip will work with those types.

For example, we might choose the type a to be Int , b to be String and c to be String . In that case we could specialize the type of flip to

(Int -> String -> String) -> String -> Int -> String

We don’t have to indicate in code that we want to specialize a quantified type - it happens automatically. For example, we can just use flip as if it had this type already:

> flip (

s -> show n <> s) "Ten" 10 "10Ten"

While we can choose any types for a , b and c , we have to be consistent. The type of the function we passed to flip had to be consistent with the types of the other arguments. That is why we passed the string "Ten" as the second argument, and the number 10 as the third. It would not work if the arguments were reversed:

> flip (

s -> show n <> s) 10 "Ten" Could not match type Int with type String

3.5 Notes On Indentation

PureScript code is indentation-sensitive, just like Haskell, but unlike JavaScript. This means that the whitespace in your code is not meaningless, but rather is used to group regions of code, just like curly braces in C-like languages.

If a declaration spans multiple lines, then any lines except the first must be indented past the indentation level of the first line.

Therefore, the following is valid PureScript code:

add x y z = x + y + z

But this is not valid code:

add x y z = x + y + z

In the second case, the PureScript compiler will try to parse two declarations, one for each line.

Generally, any declarations defined in the same block should be indented at the same level. For example, in PSCi, declarations in a let statement must be indented equally. This is valid:

> :paste … x = 1 … y = 2 … ^D

but this is not:

> :paste … x = 1 … y = 2 … ^D

Certain PureScript keywords (such as where , of and let ) introduce a new block of code, in which declarations must be further-indented:

example x y z = foo + bar where foo = x * y bar = y * z

Note how the declarations for foo and bar are indented past the declaration of example .

The only exception to this rule is the where keyword in the initial module declaration at the top of a source file.

3.6 Defining Our Types

A good first step when tackling a new problem in PureScript is to write out type definitions for any values you will be working with. First, let’s define a type for records in our address book:

type Entry = { firstName :: String , lastName :: String , address :: Address }

This defines a type synonym called Entry - the type Entry is equivalent to the type on the right of the equals symbol: a record type with three fields - firstName , lastName and address . The two name fields will have type String , and the address field will have type Address , defined as follows:

type Address = { street :: String , city :: String , state :: String }

Note that records can contain other records.

Now let’s define a third type synonym, for our address book data structure, which will be represented simply as a linked list of entries:

type AddressBook = List Entry

Note that List Entry is not the same as Array Entry , which represents an array of entries.

3.7 Type Constructors and Kinds

List is an example of a type constructor. Values do not have the type List directly, but rather List a for some type a . That is, List takes a type argument a and constructs a new type List a .

Note that just like function application, type constructors are applied to other types simply by juxtaposition: the type List Entry is in fact the type constructor List applied to the type Entry - it represents a list of entries.

If we try to incorrectly define a value of type List (by using the type annotation operator :: ), we will see a new type of error:

> import Data.List > Nil :: List In a type-annotated expression x :: t, the type t must have kind Type

This is a kind error. Just like values are distinguished by their types, types are distinguished by their kinds, and just like ill-typed values result in type errors, ill-kinded types result in kind errors.

There is a special kind called Type which represents the kind of all types which have values, like Number and String .

There are also kinds for type constructors. For example, the kind Type -> Type represents a function from types to types, just like List . So the error here occurred because values are expected to have types with kind Type , but List has kind Type -> Type .

To find out the kind of a type, use the :kind command in PSCi. For example:

> :kind Number Type > import Data.List > :kind List Type -> Type > :kind List String Type

PureScript’s kind system supports other interesting kinds, which we will see later in the book.

3.8 Displaying Address Book Entries

Let’s write our first function, which will render an address book entry as a string. We start by giving the function a type. This is optional, but good practice, since it acts as a form of documentation. In fact, the PureScript compiler will give a warning if a top-level declaration does not contain a type annotation. A type declaration separates the name of a function from its type with the :: symbol:

showEntry :: Entry -> String

This type signature says that showEntry is a function, which takes an Entry as an argument and returns a String . Here is the code for showEntry :

showEntry entry = entry . lastName <> ", " <> entry . firstName <> ": " <> showAddress entry . address

This function concatenates the three fields of the Entry record into a single string, using the showAddress function to turn the record inside the address field into a String . showAddress is defined similarly:

showAddress :: Address -> String showAddress addr = addr . street <> ", " <> addr . city <> ", " <> addr . state

A function definition begins with the name of the function, followed by a list of argument names. The result of the function is specified after the equals sign. Fields are accessed with a dot, followed by the field name. In PureScript, string concatenation uses the diamond operator ( <> ), instead of the plus operator like in Javascript.

3.9 Test Early, Test Often

The PSCi interactive mode allows for rapid prototyping with immediate feedback, so let’s use it to verify that our first few functions behave as expected.

First, build the code you’ve written:

$ pulp build

Next, load PSCi, and use the import command to import your new module:

$ pulp repl > import Data.AddressBook

We can create an entry by using a record literal, which looks just like an anonymous object in JavaScript. Bind it to a name with a let expression:

> address = { street: "123 Fake St.", city: "Faketown", state: "CA" }

Now, try applying our function to the example:

> showAddress address "123 Fake St., Faketown, CA"

Let’s also test showEntry by creating an address book entry record containing our example address:

> entry = { firstName: "John", lastName: "Smith", address: address } > showEntry entry "Smith, John: 123 Fake St., Faketown, CA"

3.10 Creating Address Books

Now let’s write some utility functions for working with address books. We will need a value which represents an empty address book: an empty list.

emptyBook :: AddressBook emptyBook = empty

We will also need a function for inserting a value into an existing address book. We will call this function insertEntry . Start by giving its type:

insertEntry :: Entry -> AddressBook -> AddressBook

This type signature says that insertEntry takes an Entry as its first argument, and an AddressBook as a second argument, and returns a new AddressBook .

We don’t modify the existing AddressBook directly. Instead, we return a new AddressBook which contains the same data. As such, AddressBook is an example of an immutable data structure. This is an important idea in PureScript - mutation is a side-effect of code, and inhibits our ability to reason effectively about its behavior, so we prefer pure functions and immutable data where possible.

To implement insertEntry , we can use the Cons function from Data.List . To see its type, open PSCi and use the :type command:

$ pulp repl > import Data.List > :type Cons forall a. a -> List a -> List a

This type signature says that Cons takes a value of some type a , and a list of elements of type a , and returns a new list with entries of the same type. Let’s specialize this with a as our Entry type:

Entry -> List Entry -> List Entry

But List Entry is the same as AddressBook , so this is equivalent to

Entry -> AddressBook -> AddressBook

In our case, we already have the appropriate inputs: an Entry , and a AddressBook , so can apply Cons and get a new AddressBook , which is exactly what we wanted!

Here is our implementation of insertEntry :

insertEntry entry book = Cons entry book

This brings the two arguments entry and book into scope, on the left hand side of the equals symbol, and then applies the Cons function to create the result.

3.11 Curried Functions

Functions in PureScript take exactly one argument. While it looks like the insertEntry function takes two arguments, it is in fact an example of a curried function.

The -> operator in the type of insertEntry associates to the right, which means that the compiler parses the type as

Entry -> ( AddressBook -> AddressBook )

That is, insertEntry is a function which returns a function! It takes a single argument, an Entry , and returns a new function, which in turn takes a single AddressBook argument and returns a new AddressBook .

This means that we can partially apply insertEntry by specifying only its first argument, for example. In PSCi, we can see the result type:

> :type insertEntry entry AddressBook -> AddressBook

As expected, the return type was a function. We can apply the resulting function to a second argument:

> :type (insertEntry entry) emptyBook AddressBook

Note though that the parentheses here are unnecessary - the following is equivalent:

> :type insertEntry example emptyBook AddressBook

This is because function application associates to the left, and this explains why we can just specify function arguments one after the other, separated by whitespace.

Note that in the rest of the book, I will talk about things like “functions of two arguments”. However, it is to be understood that this means a curried function, taking a first argument and returning another function.

Now consider the definition of insertEntry :

insertEntry :: Entry -> AddressBook -> AddressBook insertEntry entry book = Cons entry book

If we explicitly parenthesize the right-hand side, we get (Cons entry) book . That is, insertEntry entry is a function whose argument is just passed along to the (Cons entry) function. But if two functions have the same result for every input, then they are the same function! So we can remove the argument book from both sides:

insertEntry :: Entry -> AddressBook -> AddressBook insertEntry entry = Cons entry

But now, by the same argument, we can remove entry from both sides:

insertEntry :: Entry -> AddressBook -> AddressBook insertEntry = Cons

This process is called eta conversion, and can be used (along with some other techniques) to rewrite functions in point-free form, which means functions defined without reference to their arguments.

In the case of insertEntry , eta conversion has resulted in a very clear definition of our function - “ insertEntry is just cons on lists”. However, it is arguable whether point-free form is better in general.

3.12 Querying the Address Book

The last function we need to implement for our minimal address book application will look up a person by name and return the correct Entry . This will be a nice application of building programs by composing small functions - a key idea from functional programming.

We can first filter the address book, keeping only those entries with the correct first and last names. Then we can simply return the head (i.e. first) element of the resulting list.

With this high-level specification of our approach, we can calculate the type of our function. First open PSCi, and find the types of the filter and head functions:

$ pulp repl > import Data.List > :type filter forall a. (a -> Boolean) -> List a -> List a > :type head forall a. List a -> Maybe a

Let’s pick apart these two types to understand their meaning.

filter is a curried function of two arguments. Its first argument is a function, which takes a list element and returns a Boolean value as a result. Its second argument is a list of elements, and the return value is another list.

head takes a list as its argument, and returns a type we haven’t seen before: Maybe a . Maybe a represents an optional value of type a , and provides a type-safe alternative to using null to indicate a missing value in languages like Javascript. We will see it again in more detail in later chapters.

The universally quantified types of filter and head can be specialized by the PureScript compiler, to the following types:

filter :: ( Entry -> Boolean ) -> AddressBook -> AddressBook head :: AddressBook -> Maybe Entry

We know that we will need to pass the first and last names that we want to search for, as arguments to our function.

We also know that we will need a function to pass to filter . Let’s call this function filterEntry . filterEntry will have type Entry -> Boolean . The application filter filterEntry will then have type AddressBook -> AddressBook . If we pass the result of this function to the head function, we get our result of type Maybe Entry .

Putting these facts together, a reasonable type signature for our function, which we will call findEntry , is:

findEntry :: String -> String -> AddressBook -> Maybe Entry

This type signature says that findEntry takes two strings, the first and last names, and a AddressBook , and returns an optional Entry . The optional result will contain a value only if the name is found in the address book.

And here is the definition of findEntry :

findEntry firstName lastName book = head $ filter filterEntry book where filterEntry :: Entry -> Boolean filterEntry entry = entry . firstName == firstName && entry . lastName == lastName

Let’s go over this code step by step.

findEntry brings three names into scope: firstName , and lastName , both representing strings, and book , an AddressBook .

The right hand side of the definition combines the filter and head functions: first, the list of entries is filtered, and the head function is applied to the result.

The predicate function filterEntry is defined as an auxiliary declaration inside a where clause. This way, the filterEntry function is available inside the definition of our function, but not outside it. Also, it can depend on the arguments to the enclosing function, which is essential here because filterEntry uses the firstName and lastName arguments to filter the specified Entry .

Note that, just like for top-level declarations, it was not necessary to specify a type signature for filterEntry . However, doing so is recommended as a form of documentation.

3.13 Infix Function Application

In the code for findEntry above, we used a different form of function application: the head function was applied to the expression filter filterEntry book by using the infix $ symbol.

This is equivalent to the usual application head (filter filterEntry book)

($) is just an alias for a regular function called apply , which is defined in the Prelude. It is defined as follows:

apply :: forall a b . ( a -> b ) -> a -> b apply f x = f x infixr 0 apply as $

So apply takes a function and a value and applies the function to the value. The infixr keyword is used to define ($) as an alias for apply .

But why would we want to use $ instead of regular function application? The reason is that $ is a right-associative, low precedence operator. This means that $ allows us to remove sets of parentheses for deeply-nested applications.

For example, the following nested function application, which finds the street in the address of an employee’s boss:

street ( address ( boss employee ))

becomes (arguably) easier to read when expressed using $ :

street $ address $ boss employee

3.14 Function Composition

Just like we were able to simplify the insertEntry function by using eta conversion, we can simplify the definition of findEntry by reasoning about its arguments.

Note that the book argument is passed to the filter filterEntry function, and the result of this application is passed to head . In other words, book is passed to the composition of the functions filter filterEntry and head .

In PureScript, the function composition operators are <<< and >>> . The first is “backwards composition”, and the second is “forwards composition”.

We can rewrite the right-hand side of findEntry using either operator. Using backwards-composition, the right-hand side would be

(head <<< filter filterEntry) book

In this form, we can apply the eta conversion trick from earlier, to arrive at the final form of findEntry :

findEntry firstName lastName = head <<< filter filterEntry where ...

An equally valid right-hand side would be:

filter filterEntry >>> head

Either way, this gives a clear definition of the findEntry function: “ findEntry is the composition of a filtering function and the head function”.

I will let you make your own decision which definition is easier to understand, but it is often useful to think of functions as building blocks in this way - each function executing a single task, and solutions assembled using function composition.

3.15 Tests, Tests, Tests …

Now that we have the core of a working application, let’s try it out using PSCi.

$ pulp repl > import Data.AddressBook

Let’s first try looking up an entry in the empty address book (we obviously expect this to return an empty result):

> findEntry "John" "Smith" emptyBook No type class instance was found for Data.Show.Show { firstName :: String , lastName :: String , address :: { street :: String , city :: String , state :: String } }

An error! Not to worry, this error simply means that PSCi doesn’t know how to print a value of type Entry as a String.

The return type of findEntry is Maybe Entry , which we can convert to a String by hand.

Our showEntry function expects an argument of type Entry , but we have a value of type Maybe Entry . Remember that this means that the function returns an optional value of type Entry . What we need to do is apply the showEntry function if the optional value is present, and propagate the missing value if not.

Fortunately, the Prelude module provides a way to do this. The map operator can be used to lift a function over an appropriate type constructor like Maybe (we’ll see more on this function, and others like it, later in the book, when we talk about functors):

> import Prelude > map showEntry (findEntry "John" "Smith" emptyBook) Nothing

That’s better - the return value Nothing indicates that the optional return value does not contain a value - just as we expected.

For ease of use, we can create a function which prints an Entry as a String, so that we don’t have to use showEntry every time:

> printEntry firstName lastName book = map showEntry (findEntry firstName lastName book)

Now let’s create a non-empty address book, and try again. We’ll reuse our example entry from earlier:

> book1 = insertEntry entry emptyBook > printEntry "John" "Smith" book1 Just ("Smith, John: 123 Fake St., Faketown, CA")

This time, the result contained the correct value. Try defining an address book book2 with two names by inserting another name into book1 , and look up each entry by name.

Exercises (Easy) Test your understanding of the findEntry function by writing down the types of each of its major subexpressions. For example, the type of the head function as used is specialized to AddressBook -> Maybe Entry . (Medium) Write a function which looks up an Entry given a street address, by reusing the existing code in findEntry . Test your function in PSCi. (Medium) Write a function which tests whether a name appears in a AddressBook , returning a Boolean value. Hint: Use PSCi to find the type of the Data.List.null function, which test whether a list is empty or not. (Difficult) Write a function removeDuplicates which removes duplicate address book entries with the same first and last names. Hint: Use PSCi to find the type of the Data.List.nubBy function, which removes duplicate elements from a list based on an equality predicate.

3.16 Conclusion

In this chapter, we covered several new functional programming concepts:

How to use the interactive mode PSCi to experiment with functions and test ideas.

The role of types as both a correctness tool, and an implementation tool.

The use of curried functions to represent functions of multiple arguments.

Creating programs from smaller components by composition.

Structuring code neatly using where expressions.

expressions. How to avoid null values by using the Maybe type.

type. Using techniques like eta conversion and function composition to refactor code into a clear specification.

In the following chapters, we’ll build on these ideas.

4. Recursion, Maps And Folds

4.1 Chapter Goals

In this chapter, we will look at how recursive functions can be used to structure algorithms. Recursion is a basic technique used in functional programming, which we will use throughout this book.

We will also cover some standard functions from PureScript’s standard libraries. We will see the map and fold functions, as well as some useful special cases, like filter and concatMap .

The motivating example for this chapter is a library of functions for working with a virtual filesystem. We will apply the techniques learned in this chapter to write functions which compute properties of the files represented by a model of a filesystem.

4.2 Project Setup

The source code for this chapter is contained in the two files src/Data/Path.purs and src/FileOperations.purs .

The Data.Path module contains a model of a virtual filesystem. You do not need to modify the contents of this module.

The FileOperations module contains functions which use the Data.Path API. Solutions to the exercises can be completed in this file.

The project has the following Bower dependencies:

purescript-maybe , which defines the Maybe type constructor

, which defines the type constructor purescript-arrays , which defines functions for working with arrays

, which defines functions for working with arrays purescript-strings , which defines functions for working with Javascript strings

, which defines functions for working with Javascript strings purescript-foldable-traversable , which defines functions for folding arrays and other data structures

, which defines functions for folding arrays and other data structures purescript-console , which defines functions for printing to the console

4.3 Introduction

Recursion is an important technique in programming in general, but particularly common in pure functional programming, because, as we will see in this chapter, recursion helps to reduce the mutable state in our programs.

Recursion is closely linked to the divide and conquer strategy: to solve a problem on certain inputs, we can break down the inputs into smaller parts, solve the problem on those parts, and then assemble a solution from the partial solutions.

Let’s see some simple examples of recursion in PureScript.

Here is the usual factorial function example:

fact :: Int -> Int fact 0 = 1 fact n = n * fact ( n - 1 )

Here, we can see how the factorial function is computed by reducing the problem to a subproblem - that of computing the factorial of a smaller integer. When we reach zero, the answer is immediate.

Here is another common example, which computes the Fibonnacci function:

fib :: Int -> Int fib 0 = 1 fib 1 = 1 fib n = fib ( n - 1 ) + fib ( n - 2 )

Again, this problem is solved by considering the solutions to subproblems. In this case, there are two subproblems, corresponding to the expressions fib (n - 1) and fib (n - 2) . When these two subproblems are solved, we assemble the result by adding the partial results.

4.4 Recursion on Arrays

We are not limited to defining recursive functions over the Int type! We will see recursive functions defined over a wide array of data types when we cover pattern matching later in the book, but for now, we will restrict ourselves to numbers and arrays.

Just as we branch based on whether the input is non-zero, in the array case, we will branch based on whether the input is non-empty. Consider this function, which computes the length of an array using recursion:

import Prelude import Data.Array ( null ) import Data.Array.Partial ( tail ) import Partial.Unsafe ( unsafePartial ) length :: forall a . Array a -> Int length arr = if null arr then 0 else 1 + length ( unsafePartial tail arr )

In this function, we use an if .. then .. else expression to branch based on the emptiness of the array. The null function returns true on an empty array. Empty arrays have length zero, and a non-empty array has a length that is one more than the length of its tail.

This example is obviously a very impractical way to find the length of an array in JavaScript, but should provide enough help to allow you to complete the following exercises:

Exercises (Easy) Write a recursive function which returns true if and only if its input is an even integer. (Medium) Write a recursive function which counts the number of even integers in an array. Hint: the function unsafePartial head (where head is also imported from Data.Array.Partial ) can be used to find the first element in a non-empty array.

4.5 Maps

The map function is an example of a recursive function on arrays. It is used to transform the elements of an array by applying a function to each element in turn. Therefore, it changes the contents of the array, but preserves its shape (i.e. its length).

When we cover type classes later in the book we will see that the map function is an example of a more general pattern of shape-preserving functions which transform a class of type constructors called functors.

Let’s try out the map function in PSCi:

$ pulp repl > import Prelude > map (

-> n + 1) [1, 2, 3, 4, 5] [2, 3, 4, 5, 6]

Notice how map is used - we provide a function which should be “mapped over” the array in the first argument, and the array itself in its second.

4.6 Infix Operators

The map function can also be written between the mapping function and the array, by wrapping the function name in backticks:

> (

-> n + 1) `map` [1, 2, 3, 4, 5] [2, 3, 4, 5, 6]

This syntax is called infix function application, and any function can be made infix in this way. It is usually most appropriate for functions with two arguments.

There is an operator which is equivalent to the map function when used with arrays, called <$> . This operator can be used infix like any other binary operator:

> (

-> n + 1) <$> [1, 2, 3, 4, 5] [2, 3, 4, 5, 6]

Let’s look at the type of map :

> :type map forall a b f. Functor f => (a -> b) -> f a -> f b

The type of map is actually more general than we need in this chapter. For our purposes, we can treat map as if it had the following less general type:

forall a b. (a -> b) -> Array a -> Array b

This type says that we can choose any two types, a and b , with which to apply the map function. a is the type of elements in the source array, and b is the type of elements in the target array. In particular, there is no reason why map has to preserve the type of the array elements. We can use map or <$> to transform integers to strings, for example:

> show <$> [1, 2, 3, 4, 5] ["1","2","3","4","5"]

Even though the infix operator <$> looks like special syntax, it is in fact just an alias for a regular PureScript function. The function is simply applied using infix syntax. In fact, the function can be used like a regular function by enclosing its name in parentheses. This means that we can used the parenthesized name (<$>) in place of map on arrays:

> (<$>) show [1, 2, 3, 4, 5] ["1","2","3","4","5"]

Infix function names are defined as aliases for existing function names. For example, the Data.Array module defines an infix operator (..) as a synonym for the range function, as follows:

infix 8 range as ..

We can use this operator as follows:

> import Data.Array > 1 .. 5 [1, 2, 3, 4, 5] > show <$> (1 .. 5) ["1","2","3","4","5"]

Note: Infix operators can be a great tool for defining domain-specific languages with a natural syntax. However, used excessively, they can render code unreadable to beginners, so it is wise to exercise caution when defining any new operators.

In the example above, we parenthesized the expression 1 .. 5 , but this was actually not necessary, because the Data.Array module assigns a higher precedence level to the .. operator than that assigned to the <$> operator. In the example above, the precedence of the .. operator was defined as 8 , the number after the infix keyword. This is higher than the precedence level of <$> , meaning that we do not need to add parentheses:

> show <$> 1 .. 5 ["1","2","3","4","5"]

If we wanted to assign an associativity (left or right) to an infix operator, we could do so with the infixl and infixr keywords instead.

4.7 Filtering Arrays

The Data.Array module provides another function filter , which is commonly used together with map . It provides the ability to create a new array from an existing array, keeping only those elements which match a predicate function.

For example, suppose we wanted to compute an array of all numbers between 1 and 10 which were even. We could do so as follows:

> import Data.Array > filter (

-> n `mod` 2 == 0) (1 .. 10) [2,4,6,8,10]

Exercises (Easy) Use the map or <$> function to write a function which calculates the squares of an array of numbers. (Easy) Use the filter function to write a function which removes the negative numbers from an array of numbers. (Medium) Define an infix synonym <$?> for filter . Rewrite your answer to the previous question to use your new operator. Experiment with the precedence level and associativity of your operator in PSCi.

4.8 Flattening Arrays

Another standard function on arrays is the concat function, defined in Data.Array . concat flattens an array of arrays into a single array:

> import Data.Array > :type concat forall a. Array (Array a) -> Array a > concat [[1, 2, 3], [4, 5], [6]] [1, 2, 3, 4, 5, 6]

There is a related function called concatMap which is like a combination of the concat and map functions. Where map takes a function from values to values (possibly of a different type), concatMap takes a function from values to arrays of values.

Let’s see it in action:

> import Data.Array > :type concatMap forall a b. (a -> Array b) -> Array a -> Array b > concatMap (

-> [n, n * n]) (1 .. 5) [1,1,2,4,3,9,4,16,5,25]

Here, we call concatMap with the function

-> [n, n * n] which sends an integer to the array of two elements consisting of that integer and its square. The result is an array of ten integers: the integers from 1 to 5 along with their squares.

Note how concatMap concatenates its results. It calls the provided function once for each element of the original array, generating an array for each. Finally, it collapses all of those arrays into a single array, which is its result.

map , filter and concatMap form the basis for a whole range of functions over arrays called “array comprehensions”.

4.9 Array Comprehensions

Suppose we wanted to find the factors of a number n . One simple way to do this would be by brute force: we could generate all pairs of numbers between 1 and n , and try multiplying them together. If the product was n , we would have found a pair of factors of n .

We can perform this computation using an array comprehension. We will do so in steps, using PSCi as our interactive development environment.

The first step is to generate an array of pairs of numbers below n , which we can do using concatMap .

Let’s start by mapping each number to the array 1 .. n :

> pairs n = concatMap (\i -> 1 .. n) (1 .. n)

We can test our function

> pairs 3 [1,2,3,1,2,3,1,2,3]

This is not quite what we want. Instead of just returning the second element of each pair, we need to map a function over the inner copy of 1 .. n which will allow us to keep the entire pair:

> :paste … pairs' n = … concatMap (\i -> … map (\j -> [i, j]) (1 .. n) … ) (1 .. n) … ^D > pairs' 3 [[1,1],[1,2],[1,3],[2,1],[2,2],[2,3],[3,1],[3,2],[3,3]]

This is looking better. However, we are generating too many pairs: we keep both [1, 2] and [2, 1] for example. We can exclude the second case by making sure that j only ranges from i to n :

> :paste … pairs'' n = … concatMap (\i -> … map (\j -> [i, j]) (i .. n) … ) (1 .. n) … ^D > pairs'' 3 [[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]]

Great! Now that we have all of the pairs of potential factors, we can use filter to choose the pairs which multiply to give n :

> import Data.Foldable > factors n = filter (\pair -> product pair == n) (pairs'' n) > factors 10 [[1,10],[2,5]]

This code uses the product function from the Data.Foldable module in the purescript-foldable-traversable library.

Excellent! We’ve managed to find the correct set of factor pairs without duplicates.

4.10 Do Notation

However, we can improve the readability of our code considerably. map and concatMap are so fundamental, that they (or rather, their generalizations map and bind ) form the basis of a special syntax called do notation.

Note: Just like map and concatMap allowed us to write array comprehensions, the more general operators map and bind allow us to write so-called monad comprehensions. We’ll see plenty more examples of monads later in the book, but in this chapter, we will only consider arrays.

We can rewrite our factors function using do notation as follows:

factors :: Int -> Array ( Array Int ) factors n = filter ( \ xs -> product xs == n ) $ do i <- 1 .. n j <- i .. n pure [ i , j ]

The keyword do introduces a block of code which uses do notation. The block consists of expressions of a few types:

Expressions which bind elements of an array to a name. These are indicated with the backwards-facing arrow <- , with a name on the left, and an expression on the right whose type is an array.

, with a name on the left, and an expression on the right whose type is an array. Expressions which do not bind elements of the array to names. The last line pure [i, j] is an example of this kind of expression.

is an example of this kind of expression. Expressions which give names to expressions, using the let keyword.

This new notation hopefully makes the structure of the algorithm clearer. If you mentally replace the arrow <- with the word “choose”, you might read it as follows: “choose an element i between 1 and n, then choose an element j between i and n , and return [i, j] ”.

In the last line, we use the pure function. This function can be evaluated in PSCi, but we have to provide a type:

> pure [1, 2] :: Array (Array Int) [[1, 2]]

In the case of arrays, pure simply constructs a singleton array. In fact, we could modify our factors function to use this form, instead of using pure :

factors :: Int -> Array ( Array Int ) factors n = filter ( \ xs -> product xs == n ) $ do i <- 1 .. n j <- i .. n [[ i , j ]]

and the result would be the same.

4.11 Guards

One further change we can make to the factors function is to move the filter inside the array comprehension. This is possible using the guard function from the Control.MonadZero module (from the purescript-control package):

import Control.MonadZero ( guard ) factors :: Int -> Array ( Array Int ) factors n = do i <- 1 .. n j <- i .. n guard $ i * j == n pure [ i , j ]

Just like pure , we can apply the guard function in PSCi to understand how it works. The type of the guard function is more general than we need here:

> import Control.MonadZero > :type guard forall m. MonadZero m => Boolean -> m Unit

In our case, we can assume that PSCi reported the following type:

Boolean -> Array Unit

For our purposes, the following calculations tell us everything we need to know about the guard function on arrays:

> import Data.Array > length $ guard true 1 > length $ guard false 0

That is, if guard is passed an expression which evaluates to true , then it returns an array with a single element. If the expression evaluates to false , then its result is empty.

This means that if the guard fails, then the current branch of the array comprehension will terminate early with no results. This means that a call to guard is equivalent to using filter on the intermediate array. Depending on the application, you might prefer to use guard instead of a filter . Try the two definitions of factors to verify that they give the same results.

Exercises (Easy) Use the factors function to define a function isPrime which tests if its integer argument is prime or not. (Medium) Write a function which uses do notation to find the cartesian product of two arrays, i.e. the set of all pairs of elements a , b , where a is an element of the first array, and b is an element of the second. (Medium) A Pythagorean triple is an array of numbers [a, b, c] such that a² + b² = c² . Use the guard function in an array comprehension to write a function triples which takes a number n and calculates all Pythagorean triples whose components are less than n . Your function should have type Int -> Array (Array Int) . (Difficult) Write a function factorizations which produces all factorizations of an integer n , i.e. arrays of integers whose product is n . Hint: for an integer greater than 1, break the problem down into two subproblems: finding the first factor, and finding the remaining factors.

4.12 Folds

Left and right folds over arrays provide another class of interesting functions which can be implemented using recursion.

Start by importing the Data.Foldable module, and inspecting the types of the foldl and foldr functions using PSCi:

> import Data.Foldable > :type foldl forall a b f. Foldable f => (b -> a -> b) -> b -> f a -> b > :type foldr forall a b f. Foldable f => (a -> b -> b) -> b -> f a -> b

These types are actually more general than we are interested in right now. For the purposes of this chapter, we can assume that PSCi had given the following (more specific) answer:

> :type foldl forall a b. (b -> a -> b) -> b -> Array a -> b > :type foldr forall a b. (a -> b -> b) -> b -> Array a -> b

In both of these cases, the type a corresponds to the type of elements of our array. The type b can be thought of as the type of an “accumulator”, which will accumulate a result as we traverse the array.

The difference between the foldl and foldr functions is the direction of the traversal. foldl folds the array “from the left”, whereas foldr folds the array “from the right”.

Let’s see these functions in action. Let’s use foldl to sum an array of integers. The type a will be Int , and we can also choose the result type b to be Int . We need to provide three arguments: a function Int -> Int -> Int , which will add the next element to the accumulator, an initial value for the accumulator of type Int , and an array of Int s to add. For the first argument, we can just use the addition operator, and the initial value of the accumulator will be zero:

> foldl (+) 0 (1 .. 5) 15

In this case, it didn’t matter whether we used foldl or foldr , because the result is the same, no matter what order the additions happen in:

> foldr (+) 0 (1 .. 5) 15

Let’s write an example where the choice of folding function does matter, in order to illustrate the difference. Instead of the addition function, let’s use string concatenation to build a string:

> foldl (\acc n -> acc <> show n) "" [1,2,3,4,5] "12345" > foldr (

acc -> acc <> show n) "" [1,2,3,4,5] "54321"

This illustrates the difference between the two functions. The left fold expression is equivalent to the following application:

((((("" <> show 1) <> show 2) <> show 3) <> show 4) <> show 5)

whereas the right fold is equivalent to this:

((((("" <> show 5) <> show 4) <> show 3) <> show 2) <> show 1)

4.13 Tail Recursion

Recursion is a powerful technique for specifying algorithms, but comes with a problem: evaluating recursive functions in JavaScript can lead to stack overflow errors if our inputs are too large.

It is easy to verify this problem, with the following code in PSCi:

> f 0 = 0 > f n = 1 + f (n - 1) > f 10 10 > f 100000 RangeError: Maximum call stack size exceeded

This is a problem. If we are going to adopt recursion as a standard technique from functional programming, then we need a way to deal with possibly unbounded recursion.

PureScript provides a partial solution to this problem in the form of tail recursion optimization.

Note: more complete solutions to the problem can be implemented in libraries using so-called trampolining, but that is beyond the scope of this chapter. The interested reader can consult the documentation for the purescript-free and purescript-tailrec packages.

The key observation which enables tail recursion optimization is the following: a recursive call in tail position to a function can be replaced with a jump, which does not allocate a stack frame. A call is in tail position when it is the last call made before a function returns. This is the reason why we observed a stack overflow in the example - the recursive call to f was not in tail position.

In practice, the PureScript compiler does not replace the recursive call with a jump, but rather replaces the entire recursive function with a while loop.

Here is an example of a recursive function with all recursive calls in tail position:

fact :: Int -> Int -> Int fact 0 acc = acc fact n acc = fact ( n - 1 ) ( acc * n )

Notice that the recursive call to fact is the last thing that happens in this function - it is in tail position.

4.14 Accumulators

One common way to turn a function which is not tail recursive into a tail recursive function is to use an accumulator parameter. An accumulator parameter is an additional parameter which is added to a function which accumulates a return value, as opposed to using the return value to accumulate the result.

For example, consider this array recursion which reverses the input array by appending elements at the head of the input array to the end of the result.

reverse :: forall a . Array a -> Array a reverse [] = [] reverse xs = snoc ( reverse ( unsafePartial tail xs )) ( unsafePartial head xs )

This implementation is not tail recursive, so the generated JavaScript will cause a stack overflow when executed on a large input array. However, we can make it tail recursive, by introducing a second function argument to accumulate the result instead:

reverse :: forall a . Array a -> Array a reverse = reverse' [] where reverse' acc [] = acc reverse' acc xs = reverse' ( unsafePartial head xs : acc ) ( unsafePartial tail xs )

In this case, we delegate to the helper function reverse' , which performs the heavy lifting of reversing the array. Notice though that the function reverse' is tail recursive - its only recursive call is in the last case, and is in tail position. This means that the generated code will be a while loop, and will not blow the stack for large inputs.

To understand the second implementation of reverse , note that the helper function reverse' essentially uses the accumulator parameter to maintain an additional piece of state - the partially constructed result. The result starts out empty, and grows by one element for every element in the input array. However, because later elements are added at the front of the array, the result is the original array in reverse!

Note also that while we might think of the accumulator as “state”, there is no direct mutation going on. The accumulator is an immutable array, and we simply use function arguments to thread the state through the computation.

4.15 Prefer Folds to Explicit Recursion

If we can write our recursive functions using tail recursion, then we can benefit from tail recursion optimization, so it becomes tempting to try to write all of our functions in this form. However, it is often easy to forget that many functions can be written directly as a fold over an array or similar data structure. Writing algorithms directly in terms of combinators such as map and fold has the added advantage of code simplicity - these combinators are well-understood, and as such, communicate the intent of the algorithm much better than explicit recursion.

For example, the reverse example can be written as a fold in at least two ways. Here is a version which uses foldr :

> import Data.Foldable > :paste … reverse :: forall a. Array a -> Array a … reverse = foldr (\x xs -> xs <> [x]) [] … ^D > reverse [1, 2, 3] [3,2,1]

Writing reverse in terms of foldl will be left as an exercise for the reader.

Exercises (Easy) Use foldl to test whether an array of boolean values are all true. (Medium) Characterize those arrays xs for which the function foldl (==) false xs returns true. (Medium) Rewrite the following function in tail recursive form using an accumulator parameter: import Prelude import Data.Array.Partial ( head , tail ) count :: forall a . ( a -> Boolean ) -> Array a -> Int count _ [] = 0 count p xs = if p ( unsafePartial head xs ) then count p ( unsafePartial tail xs ) + 1 else count p ( unsafePartial tail xs ) (Medium) Write reverse in terms of foldl .

4.16 A Virtual Filesystem

In this section, we’re going to apply what we’ve learned, writing functions which will work with a model of a filesystem. We will use maps, folds and filters to work with a predefined API.

The Data.Path module defines an API for a virtual filesystem, as follows:

There is a type Path which represents a path in the filesystem.

which represents a path in the filesystem. There is a path root which represents the root directory.

which represents the root directory. The ls function enumerates the files in a directory.

function enumerates the files in a directory. The filename function returns the file name for a Path .

function returns the file name for a . The size function returns the file size for a Path which represents a file.

function returns the file size for a which represents a file. The isDirectory function tests whether a function is a file or a directory.

In terms of types, we have the following type definitions:

root :: Path ls :: Path -> Array Path filename :: Path -> String size :: Path -> Maybe Number isDirectory :: Path -> Boolean

We can try out the API in PSCi:

$ pulp repl > import Data.Path > root / > isDirectory root true > ls root [/bin/,/etc/,/home/]

The FileOperations module defines functions which use the Data.Path API. You do not need to modify the Data.Path module, or understand its implementation. We will work entirely in the FileOperations module.

4.17 Listing All Files

Let’s write a function which performs a deep enumeration of all files inside a directory. This function will have the following type:

allFiles :: Path -> Array Path

We can define this function by recursion. First, we can use ls to enumerate the immediate children of the directory. For each child, we can recursively apply allFiles , which will return an array of paths. concatMap will allow us to apply allFiles and flatten the results at the same time.

Finally, we use the cons operator : to include the current file:

allFiles file = file : concatMap allFiles ( ls file )

Note: the cons operator : actually has poor performance on immutable arrays, so it is not recommended in general. Performance can be improved by using other data structures, such as linked lists and sequences.

Let’s try this function in PSCi:

> import FileOperations > import Data.Path > allFiles root [/,/bin/,/bin/cp,/bin/ls,/bin/mv,/etc/,/etc/hosts, ...]

Great! Now let’s see if we can write this function using an array comprehension using do notation.

Recall that a backwards arrow corresponds to choosing an element from an array. The first step is to choose an element from the immediate children of the argument. Then we simply call the function recursively for that file. Since we are using do notation, there is an implicit call to concatMap which concatenates all of the recursive results.

Here is the new version:

allFiles' :: Path -> Array Path allFiles' file = file : do child <- ls file allFiles' child

Try out the new version in PSCi - you should get the same result. I’ll let you decide which version you find clearer.

Exercises (Easy) Write a function onlyFiles which returns all files (not directories) in all subdirectories of a directory. (Medium) Write a fold to determine the largest and smallest files in the filesystem. (Difficult) Write a function whereIs to search for a file by name. The function should return a value of type Maybe Path , indicating the directory containing the file, if it exists. It should behave as follows: > whereIs "/bin/ls" Just (/bin/) > whereIs "/bin/cat" Nothing Hint: Try to write this function as an array comprehension using do notation.

4.18 Conclusion

In this chapter, we covered the basics of recursion in PureScript, as a means of expressing algorithms concisely. We also introduced user-defined infix operators, standard functions on arrays such as maps, filters and folds, and array comprehensions which combine these ideas. Finally, we showed the importance of using tail recursion in order to avoid stack overflow errors, and how to use accumulator parameters to convert functions to tail recursive form.

5. Pattern Matching

5.1 Chapter Goals

This chapter will introduce two new concepts: algebraic data types, and pattern matching. We will also briefly cover an interesting feature of the PureScript type system: row polymorphism.

Pattern matching is a common technique in functional programming and allows the developer to write compact functions which express potentially complex ideas, by breaking their implementation down into multiple cases.

Algebraic data types are a feature of the PureScript type system which enable a similar level of expressiveness in the language of types - they are closely related to pattern matching.

The goal of the chapter will be to write a library to describe and manipulate simple vector graphics using algebraic types and pattern matching.

5.2 Project Setup

The source code for this chapter is defined in the file src/Data/Picture.purs .

The project uses some Bower packages which we have already seen, and adds the following new dependencies:

purescript-globals , which provides access to some common JavaScript values and functions.

, which provides access to some common JavaScript values and functions. purescript-math , which provides access to the JavaScript Math module.

The Data.Picture module defines a data type Shape for simple shapes, and a type Picture for collections of shapes, along with functions for working with those types.

The module imports the Data.Foldable module, which provides functions for folding data structures:

module Data.Picture where import Prelude import Data.Foldable ( foldl )

The Data.Picture module also imports the Global and Math modules, but this time using the as keyword:

import Global as Global import Math as Math

This makes the types and functions in those modules available for use, but only by using qualified names, like Global.infinity and Math.max . This can be useful to avoid overlapping imports, or just to make it clearer which modules certain things are imported from.

Note: it is not necessary to use the same module name as the original module for a qualified import. Shorter qualified names like import Math as M are possible, and quite common.

5.3 Simple Pattern Matching

Let’s begin by looking at an example. Here is a function which computes the greatest common divisor of two integers using pattern matching:

gcd :: Int -> Int -> Int gcd n 0 = n gcd 0 m = m gcd n m = if n > m then gcd ( n - m ) m else gcd n ( m - n )

This algorithm is called the Euclidean Algorithm. If you search for its definition online, you will likely find a set of mathematical equations which look a lot like the code above. This is one benefit of pattern matching: it allows you to define code by cases, writing simple, declarative code which looks like a specification of a mathematical function.

A function written using pattern matching works by pairing sets of conditions with their results. Each line is called an alternative or a case. The expressions on the left of the equals sign are called patterns, and each case consists of one or more patterns, separated by spaces. Cases describe which conditions the arguments must satisfy before the expression on the right of the equals sign should be evaluated and returned. Each case is tried in order, and the first case whose patterns match their inputs determines the return value.

For example, the gcd function is evaluated using the following steps:

The first case is tried: if the second argument is zero, the function returns n (the first argument).

(the first argument). If not, the second case is tried: if the first argument is zero, the function returns m (the second argument).

(the second argument). Otherwise, the function evaluates and returns the expression in the last line.

Note that patterns can bind values to names - each line in the example binds one or both of the names n and m to the input values. As we learn about different kinds of patterns, we will see that different types of patterns correspond to different ways to choose names from the input arguments.

5.4 Simple Patterns

The example code above demonstrates two types of patterns:

Integer literals patterns, which match something of type Int , only if the value matches exactly.

, only if the value matches exactly. Variable patterns, which bind their argument to a name

There are other types of simple patterns:

Number , String , Char and Boolean literals

, , and literals Wildcard patterns, indicated with an underscore ( _ ), which match any argument, and which do not bind any names.

Here are two more examples which demonstrate using these simple patterns:

fromString :: String -> Boolean fromString "true" = true fromString _ = false toString :: Boolean -> String toString true = "true" toString false = "false"

Try these functions in PSCi.

5.5 Guards

In the Euclidean algorithm example, we used an if .. then .. else expression to switch between the two alternatives when m > n and m <= n . Another option in this case would be to use a guard.

A guard is a boolean-valued expression which must be satisfied in addition to the constraints imposed by the patterns. Here is the Euclidean algorithm rewritten to use a guard:

gcd :: Int -> Int -> Int gcd n 0 = n gcd 0 n = n gcd n m | n > m = gcd ( n - m ) m | otherwise = gcd n ( m - n )

In this case, the third line uses a guard to impose the extra condition that the first argument is strictly larger than the second.

As this example demonstrates, guards appear on the left of the equals symbol, separated from the list of patterns by a pipe character ( | ).

Exercises (Easy) Write the factorial function using pattern matching. Hint. Consider the two cases zero and non-zero inputs. (Medium) Look up Pascal’s Rule for computing binomial coefficients. Use it to write a function which computes binomial coefficients using pattern matching.

5.6 Array Patterns

Array literal patterns provide a way to match arrays of a fixed length. For example, suppose we want to write a function isEmpty which identifies empty arrays. We could do this by using an empty array pattern ( [] ) in the first alternative:

isEmpty :: forall a . Array a -> Boolean isEmpty [] = true isEmpty _ = false

Here is another function which matches arrays of length five, binding each of its five elements in a different way:

takeFive :: Array Int -> Int takeFive [ 0 , 1 , a , b , _ ] = a * b takeFive _ = 0

The first pattern only matches arrays with five elements, whose first and second elements are 0 and 1 respectively. In that case, the function returns the product of the third and fourth elements. In every other case, the function returns zero. For example, in PSCi:

> :paste … takeFive [0, 1, a, b, _] = a * b … takeFive _ = 0 … ^D > takeFive [0, 1, 2, 3, 4] 6 > takeFive [1, 2, 3, 4, 5] 0 > takeFive [] 0

Array literal patterns allow us to match arrays of a fixed length, but PureScript does not provide any means of matching arrays of an unspecified length, since destructuring immutable arrays in these sorts of ways can lead to poor performance. If you need a data structure which supports this sort of matching, the recommended approach is to use Data.List . Other data structures exist which provide improved asymptotic performance for different operations.

5.7 Record Patterns and Row Polymorphism

Record patterns are used to match - you guessed it - records.

Record patterns look just like record literals, but instead of values on the right of the colon, we specify a binder for each field.

For example: this pattern matches any record which contains fields called first and last , and binds their values to the names x and y respectively:

showPerson :: { first :: String , last :: String } -> String showPerson { first : x , last : y } = y <> ", " <> x

Record patterns provide a good example of an interesting feature of the PureScript type system: row polymorphism. Suppose we had defined showPerson without a type signature above. What would its inferred type have been? Interestingly, it is not the same as the type we gave:

> showPerson { first: x, last: y } = y <> ", " <> x > :type showPerson forall r. { first :: String, last :: String | r } -> String

What is the type variable r here? Well, if we try showPerson in PSCi, we see something interesting:

> showPerson { first: "Phil", last: "Freeman" } "Freeman, Phil" > showPerson { first: "Phil", last: "Freeman", location: "Los Angeles" } "Freeman, Phil"

We are able to append additional fields to the record, and the showPerson function will still work. As long as the record contains the first and last fields of type String , the function application is well-typed. However, it is not valid to call showPerson with too few fields:

> showPerson { first: "Phil" } Type of expression lacks required label "last"

We can read the new type signature of showPerson as “takes any record with first and last fields which are Strings and any other fields, and returns a String ”.

This function is polymorphic in the row r of record fields, hence the name row polymorphism.

Note that we could have also written

> showPerson p = p . last <> ", " <> p . first

and PSCi would have inferred the same type.

We will see row polymorphism again later, when we discuss extensible effects.

5.8 Nested Patterns

Array patterns and record patterns both combine smaller patterns to build larger patterns. For the most part, the examples above have only used simple patterns inside array patterns and record patterns, but it is important to note that patterns can be arbitrarily nested, which allows functions to be defined using conditions on potentially complex data types.

For example, this code combines two record patterns:

type Address = { street :: String , city :: String } type Person = { name :: String , address :: Address } livesInLA :: Person -> Boolean livesInLA { address : { city : "Los Angeles" } } = true livesInLA _ = false

5.9 Named Patterns

Patterns can be named to bring additional names into scope when using nested patterns. Any pattern can be named by using the @ symbol.

For example, this function sorts two-element arrays, naming the two elements, but also naming the array itself:

sortPair :: Array Int -> Array Int sortPair arr @ [ x , y ] | x <= y = arr | otherwise = [ y , x ] sortPair arr = arr

This way, we save ourselves from allocating a new array if the pair is already sorted.

Exercises (Easy) Write a function sameCity which uses record patterns to test whether two Person records belong to the same city. (Medium) What is the most general type of the sameCity function, taking into account row polymorphism? What about the livesInLA function defined above? (Medium) Write a function fromSingleton which uses an array literal pattern to extract the sole member of a singleton array. If the array is not a singleton, your function should return a provided default value. Your function should have type forall a. a -> Array a -> a

5.10 Case Expressions

Patterns do not only appear in top-level function declarations. It is possible to use patterns to match on an intermediate value in a computation, using a case expression. Case expressions provide a similar type of utility to anonymous functions: it is not always desirable to give a name to a function, and a case expression allows us to avoid naming a function just because we want to use a pattern.

Here is an example. This function computes “longest zero suffix” of an array (the longest suffix which sums to zero):

import Data.Array.Partial ( tail ) import Partial.Unsafe ( unsafePartial ) lzs :: Array Int -> Array Int lzs [] = [] lzs xs = case sum xs of 0 -> xs _ -> lzs ( unsafePartial tail xs )

For example:

> lzs [1, 2, 3, 4] [] > lzs [1, -1, -2, 3] [-1, -2, 3]

This function works by case analysis. If the array is empty, our only option is to return an empty array. If the array is non-empty, we first use a case expression to split into two cases. If the sum of the array is zero, we return the whole array. If not, we recurse on the tail of the array.

5.11 Pattern Match Failures and Partial Functions

If patterns in a case expression are tried in order, then what happens in the case when none of the patterns in a case alternatives match their inputs? In this case, the case expression will fail at runtime with a pattern match failure.

We can see this behavior with a simple example:

import Partial.Unsafe ( unsafePartial ) partialFunction :: Boolean -> Boolean partialFunction = unsafePartial \ true -> true

This function contains only a single case, which only matches a single input, true . If we compile this file, and test in PSCi with any other argument, we will see an error at runtime:

> partialFunction false Failed pattern match

Functions which return a value for any combination of inputs are called total functions, and functions which do not are called partial.

It is generally considered better to define total functions where possible. If it is known that a function does not return a result for some valid set of inputs, it is usually better to return a value with type Maybe a for some a , using Nothing to indicate failure. This way, the presence or absence of a value can be indicated in a type-safe way.

The PureScript compiler will generate an error if it can detect that your function is not total due to an incomplete pattern match. The unsafePartial function can be used to silence these errors (if you are sure that your partial function is safe!) If we removed the call to the unsafePartial function above, then the compiler would generate the following error:

A case expression could not be determined to cover all inputs. The following additional cases are required to cover all inputs: false

This tells us that the value false is not matched by any pattern. In general, these warnings might include multiple unmatched cases.

If we also omit the type signature above:

partialFunction true = true

then PSCi infers a curious type:

> :type partialFunction Partial => Boolean -> Boolean

We will see more types which involve the => symbol later on in the book (they are related to type classes), but for now, it suffices to observe that PureScript keeps track of partial functions using the type system, and that we must explicitly tell the type checker when they are safe.

The compiler will also generate a warning in certain cases when it can detect that cases are redundant (that is, a case only matches values which would have been matched by a prior case):

redundantCase :: Boolean -> Boolean redundantCase true = true redundantCase false = false redundantCase false = false

In this case, the last case is correctly identified as redundant:

Redundant cases have been detected. The definition has the following redundant cases: false

Note: PSCi does not show warnings, so to reproduce this example, you will need to save this function as a file and compile it using pulp build .

5.12 Algebraic Data Types

This section will introduce a feature of the PureScript type system called Algebraic Data Types (or ADTs), which are fundamentally related to pattern matching.

However, we’ll first consider a motivating example, which will provide the basis of a solution to this chapter’s problem of implementing a simple vector graphics library.

Suppose we wanted to define a type to represent some simple shape types: lines, rectangles, circles, text, etc. In an object oriented language, we would probably define an interface or abstract class Shape , and one concrete subclass for each type of shape that we wanted to be able to work with.

However, this approach has one major drawback: to work with Shape s abstractly, it is necessary to identify all of the operations one might wish to perform, and to define them on the Shape interface. It becomes difficult to add new operations without breaking modularity.

Algebraic data types provide a type-safe way to solve this sort of problem, if the set of shapes is known in advance. It is possible to define new operations on Shape in a modular way, and still maintain type-safety.

Here is how Shape might be represented as an algebraic data type:

data Shape = Circle Point Number | Rectangle Point Number Number | Line Point Point | Text Point String

The Point type might also be defined as an algebraic data type, as follows:

data Point = Point { x :: Number , y :: Number }

The Point data type illustrates some interesting points:

The data carried by an ADT’s constructors doesn’t have to be restricted to primitive types: constructors can include records, arrays, or even other ADTs.

Even though ADTs are useful for describing data with multiple constructors, they can also be useful when there is only a single constructor.

The constructors of an algebraic data type might have the same name as the ADT itself. This is quite common, and it is important not to confuse the Point type constructor with the Point data constructor - they live in different namespaces.

This declaration defines Shape as a sum of different constructors, and for each constructor identifies the data that is included. A Shape is either a Circle which contains a center Point and a radius (a number), or a Rectangle , or a Line , or Text . There are no other ways to construct a value of type Shape .

An algebraic data type is introduced using the data keyword, followed by the name of the new type and any type arguments. The type’s constructors are defined after the equals symbol, and are separated by pipe characters ( | ).

Let’s see another example from PureScript’s standard libraries. We saw the Maybe type, which is used to to define optional values, earlier in the book. Here is it’s definition from the purescript-maybe package:

data Maybe a = Nothing | Just a

This example demonstrates the use of a type parameter a . Reading the pipe character as the word “or”, its definition almost reads like English: “a value of type Maybe a is either Nothing , or Just a value of type a ”.

Data constructors can also be used to define recursive data structures. Here is one more example, defining a data type of singly-linked lists of elements of type a :

data List a = Nil | Cons a ( List a )

This example is taken from the purescript-lists package. Here, the Nil constructor represents an empty list, and Cons is used to create non-empty lists from a head element and a tail. Notice how the tail is defined using the data type List a , making this a recursive data type.

5.13 Using ADTs

It is simple enough to use the constructors of an algebraic data type to construct a value: simply apply them like functions, providing arguments corresponding to the data included with the appropriate constructor.

For example, the Line constructor defined above required two Point s, so to construct a Shape using the Line constructor, we have to provide two arguments of type Point :

exampleLine :: Shape exampleLine = Line p1 p2 where p1 :: Point p1 = Point { x : 0.0 , y : 0.0 } p2 :: Point p2 = Point { x : 100.0 , y : 50.0 }

To construct the points p1 and p2 , we apply the Point constructor to its single argument, which is a record.

So, constructing values of algebraic data types is simple, but how do we use them? This is where the important connection with pattern matching appears: the only way to consume a value of an algebraic data type is to use a pattern to match its constructor.

Let’s see an example. Suppose we want to convert a Shape into a String . We have to use pattern matching to discover which constructor was used to construct the Shape . We can do this as follows:

showPoint :: Point -> String showPoint ( Point { x : x , y : y }) = "(" <> show x <> ", " <> show y <> ")" showShape :: Shape -> String showShape ( Circle c r ) = ... showShape ( Rectangle c w h ) = ... showShape ( Line start end ) = ... showShape ( Text p text ) = ...

Each constructor can be used as a pattern, and the arguments to the constructor can themselves be bound using patterns of their own. Consider the first case of showShape : if the Shape matches the Circle constructor, then we bring the arguments of Circle (center and radius) into scope using two variable patterns, c and r . The other cases are similar.

showPoint is another example of pattern matching. In this case, there is only a single case, but we use a nested pattern to match the fields of the record contained inside the Point constructor.

5.14 Record Puns

The showPoint function matches a record inside its argument, binding the x and y properties to values with the same names. In PureScript, we can simplify this sort of pattern match as follows:

showPoint :: Point -> String showPoint ( Point { x , y }) = ...

Here, we only specify the names of the properties, and we do not need to specify the names of the values we want to introduce. This is called a record pun.

It is also possible to use record puns to construct records. For example, if we have values named x and y in scope, we can construct a Point using Point { x, y } :

origin :: Point origin = Point { x , y } where x = 0.0 y = 0.0

This can be useful for improving readability of code in some circumstances.

Exercises (Easy) Construct a value of type Shape which represents a circle centered at the origin with radius 10.0 . (Medium) Write a function from Shape s to Shape s, which scales its argument by a factor of 2.0 , center the origin. (Medium) Write a function which extracts the text from a Shape . It should return Maybe String , and use the Nothing constructor if the input is not constructed using Text .

5.15 Newtypes

There is an important special case of algebraic data types, called newtypes. Newtypes are introduced using the newtype keyword instead of the data keyword.

Newtypes must define exactly one constructor, and that constructor must take exactly one argument. That is, a newtype gives a new name to an existing type. In fact, the values of a newtype have the same runtime representation as the underlying type. They are, however, distinct from the point of view of the type system. This gives an extra layer of type safety.

As an example, we might want to define newtypes as type-level aliases for Number , to ascribe units like pixels and inches:

newtype Pixels = Pixels Number newtype Inches = Inches Number

This way, it is impossible to pass a value of type Pixels to a function which expects Inches , but there is no runtime performance overhead.

Newtypes will become important when we cover type classes in the next chapter, since they allow us to attach different behavio