Skip to content

Instantly share code, notes, and snippets.

@michaelficarra
Created October 23, 2015 16:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save michaelficarra/4473c9901120f2ff87e0 to your computer and use it in GitHub Desktop.
Save michaelficarra/4473c9901120f2ff87e0 to your computer and use it in GitHub Desktop.

Abstract

PureScript is a small, modern pure functional programming language that compiles to JavaScript. It is written in and inspired by Haskell, with an expressive static type system designed for natural, effortless interaction with existing JavaScript programs. In this gentle introduction to the PureScript programming language, we will examine its type system, syntax, idioms, and current best practices.

Notes

Slide 1: Getting Started With PureScript

  • Image credit: http://crowactive.com/wp-content/uploads/2010/05/crow6sm.jpg
  • PureScript is a small functional programming language that compiles to -- and, more importantly, easily interfaces with -- JavaScript.
  • We'll take a quick tour of the language and see what makes it special. Then, we'll see how we can start using it and learning about it in depth.

Slide 2: Literals

  • Functional programming languages are concerned with values. These are the values we use in PureScript. For those of you with a Haskell background, most of the syntax and many of the concepts will be pretty familiar, so bear with me. A notable difference to keep in mind is that PureScript is strict, whereas Haskell is lazy.
  • We have a number (specifically a double), a string, a Boolean, an array, a record, and a function. These values are internally represented and exposed through FFI as the associated JavaScript value.
  • The double-colon syntax specifies the type of each value. We'll be including explicit type signatures throughout these slides, but none of them are needed -- the PureScript compiler can almost always infer the type of each value. Though, it is convention to include explicit type signatures with each top-level declaration.

Slide 3: Function Definition & Application

  • Defining functions in PureScript is very easy. The sum function expects two parameters, a and b, and returns their sum.
  • When a PureScript function is given fewer arguments than it's expecting, it'll return a function that expects the remaining arguments. This is called auto-currying. The successor function takes advantage of this by being defined in terms of sum.
  • And of course, you can fully apply a function as we do in the definitions of sumSuccessors and seven.

Slide 4: Function Definition (cont.)

  • Functions don't have to be defined in terms of concrete types. The type of the return value of some functions is determined by the type of its parameters. This is called parametric polymorphism.
  • The identity function simply returns whatever it's given.
  • The const function returns its first parameter regardless of the value or type of its second parameter.
  • $ is an infix function, which means it's written between its two arguments when it's applied. The infixr declaration below declares that it associates to the right (r) with a very low precedence (0).

Slide 5: Records

  • Records are a free-form data structure very much like JavaScript objects. Unlike Haskell, we do not automatically create functions for accessing a record's fields. PureScript has a syntactic form for record field access using a dot.
  • hank here has name and birth year fields, and my laptop has name and manufacturing date fields. The order of the fields is inconsequential, just like JavaScript objects.
  • We can write a function that can say hello to either of these records because they have a structural similarity. We read the type signature of greet as "a function that takes a record with a name field and zero or more other fields called r, then produces a string". We call r a "row of fields", and this is called row polymorphism, one of PureScript's most notable features. In PureScript, type variables like r are always explicitly universally quantified, unlike in Haskell.
  • hankJr is defined in terms of hank, using a record update. This does not mutate hank. It can be thought of as an object with just a birth year own-property that has hank as its prototype.

Slide 6: Algebraic Data Types

  • In addition to the primitive data types, PureScript allows you to define your own data types. The data keyword introduces an Algebraic Data Type (or ADT).
  • The Ordering ADT has exactly three possible values (or inhabitants): Less, Equal, and Greater. We can use those values just as we do values of any other type. Here, the compare function returns an Ordering by returning one of those values.

Slide 7: Algebraic Data Types (cont.)

  • An ADT can be parameterised by another type. List is parameterised: when given a type t, it produces a type. EmptyList is an inhabitant of any List type. Cons is what we call a type constructor. It can be applied like a function to a value of type t and another value of type List t to produce a value of type List t. That may have been a bit confusing, so let's look at the examples.
  • listA is a value of type List Number. Remember, that is a single type. In this case, Cons must be applied to a Number and another List Number.
  • listB creates a similar structure, but with Strings instead of Numbers.
  • And listC is a member of any List type because EmptyList doesn't mention the type parameter t in its definition.

Slide 8: Type Aliases

  • Type aliases are a very simple feature.
  • Maybe is aliased as Option. An Array of Numbers is aliased as NumberArray. List Number is aliased as NumberList. And a record with numeric x and y fields is aliased as Point.
  • And just like ADTs, type aliases can be parameterised. Pair is shorthand for a record with left and right fields, and Predicate is an alias for any function that returns a Boolean.

Slide 9: Pattern Matching

  • Pattern matching is an extremely powerful feature. In a parameter list, wherever we would use a variable, we can instead use a pattern. When a program has multiple consecutive definitions, failed pattern matches fail through to later definitions, kind of like switch-case in C-like languages.
  • In the definition of map, we can see a pattern match on the value Nothing. In that case, the function returns a Nothing value. When the second parameter is not a Nothing, we know it must be a Just, and we can destructure the value out of the constructor. map-prime below is the same function, but implemented using pattern matching in a case instead of the parameter list. Pattern matching in a parameter list is only sugar for pattern matching in a case expression in the function's body.

Slide 10: Pattern Matching (cont.)

  • Fields of a record can be pattern matched. doubleZero's first definition matches a record with an x value of 0 and binds y to the record's y field and p to the record itself using an at-pattern. If that pattern succeeds, it'll return a record with the y value doubled. Otherwise, execution will fall through to the second definition, which simply returns its argument.
  • And finally, the underscore in const allows us to ignore a parameter without binding it to a name.

Slide 11: Program Structure

  • A typical PureScript program is split into one module per file.
  • The module name is specified at the top, followed by imports, type declarations, and then value declarations.

Slide 12: Program Structure (cont.)

  • If you're like me, you're going to want to be a bit more explicit about what you import and export.
  • Explicit exports may be listed after the module name, and explicit imports after the imported module name.
  • Use the qualified keyword to import a whole module under a given name.
  • Modules that are intended to be executable will export a special function named main. This particular main function has no effects.

Slide 13: Hello, World!

  • Even though it's a bit verbose, this would be an idiomatic hello world program.

Slide 14: Effects

  • What makes PureScript "pure" is that it tracks effects in the type system.
  • The Eff constructor pairs a row of effects with a type. When an Eff is evaluated, a value of that type is produced, and the effects can be observed.
  • For instance, the random value includes the RANDOM effect, which indicates that it affects the random number generator, and produces a Number.
  • log is a function that generates an Eff from a String. The resultant Eff includes the CONSOLE effect, which means that it will print to the console, and produces a Unit value. Unit values are used to indicate that the effects are the primary goal of the function. This function is similar to the console.log you already know, which is evaluated only for its effects and always returns null.
  • print is similar to log, but it's able to converts its argument to a String first.
  • This main function composes two effects using this operator that's pronounced bind. The important thing to know about this operator is that it passes the random number that was generated when evaluating random as the input to the print function. Notice that each individual effect is represented in the row of effects returned by main.

Slide 15: Composing Effects

  • When we want to compose additional effectful functions, it's convenient to use do notation. This makes a sequence of effectful function calls look more imperative.
  • Remember that bind passes along the generated value to the following function. This do sugar allows us to save that value to a reference using the leftward facing arrow, which is pronounced "gets". In this case, we do that with a and b.
  • When the value is a Unit, we choose to just ignore it by omitting the arrow.

Slide 16: Type Classes

  • Type classes are an unfortunately named feature, but they're incredibly useful for code re-use, enabling ad-hoc polymorphism. This means that functions can be defined in terms of the type classes that a type implements, much like how interfaces are used in programming languages that have interfaces.
  • This is a type class in the standard library called Show. Any type a that implements Show must provide the definition for a show function that can generate a string for each of its values. This type class is what allowed the print function from earlier to convert its argument to a string.
  • Boolean implements Show very simply using a piece-wise definition: return the string "true" for true and the string "false" for false.
  • Arrays can implement show as long as their parameter implements Show. The part of the type signature before the fat arrow is called a constraint. This one requires a to implement Show. This is necessary because in the recursive case we need to call show on x, which is an a.

Slide 17: JavaScript Interoperability (FFI)

  • Finally, how can we interact with existing JavaScript?
  • Use the foreign keyword to import in-scope names. You'll need to provide an appropriate type signature for each foreign value. parseFloat is a function from String to Number.
  • Then, we define these values in a JavaScript file, attaching them to the exports object as we would in any CommonJS module.
  • Remember, since PureScript curries its functions, we need to return a new function for each argument. So pow takes a base, then returns a function that takes the exponent and returns the result.

Slide 18: JavaScript Interoperability (cont.)

  • We can also depend on foreign types and foreign effects. Here we're importing the values setTimeout and clearTimeout along with the effects and types they need. Note that the names we choose for the foreign types and effects are arbitrary.
  • setTimeout takes an effectful function with a meaningless return value and a delay in milliseconds, and returns a value that represents both the effect that setTimeout has on the event loop and the unique identifier for this event.
  • clearTimeout takes one of those unique TimeoutID identifiers, and returns a value that represents the effect it has on the event loop.

Slide 19: Installation

  • So at this point, you can't wait to start using PureScript right now.
  • You can either grab a pre-compiled binary off Github or install it through one of these three package managers.

Slide 20: Starting a Project

  • To get a project started, I recommend using pulp, which is a project management tool designed specifically for PureScript.
  • You can also use the Yeoman generator, which will create a Gulpfile along with the basic project structure.
  • And of course you can just create the source tree and build files yourself.

Slide 21: Building

  • Building with pulp and gulp is easy.
  • If you're doing it yourself, you'll need to use the psc and psc-bundle binaries directly.
  • psc compiles all the PureScript and FFI files for your project and your dependencies.
  • psc-bundle then combines the individual modules into a single executable JavaScript file.

Slide 22: Managing Dependencies

  • Dependencies are managed through bower. You can get bower through npm.
  • Use the --save flag when installing runtime dependencies, and the --save-dev flag when installing development dependencies.
  • To publish, you'll need to push up a git tag. You only have to register with bower the first time you publish. Publishing further versions is as easy as pushing a new git tag.

Slide 23: Testing

  • Before you publish, you're going to want to make sure your library is well tested. We're going to write some property-base tests.
  • It's a convention to define our tests in the Test namespace. We import our testing library, QuickCheck. We're going to test some invariants about the Data.Array.sort function.
  • The first invariant is idempotence: sorting an array twice should be the same as sorting it once.
  • The second invariant is length preservation: sorting an array should not change its length.
  • We export a main function that executes these tests in sequence.

Slide 24: Testing (cont.)

  • First we make sure we have the quickcheck dependency installed using bower install, then we can run our tests with pulp.
  • QuickCheck will run 100 random inputs through each of our tests, and let us know if any of our invariants have been violated. These 100 random inputs could easily be a million by just telling QuickCheck how many tests you want.

Slide 25: Unit Tests

  • For those of you that prefer making individual assertions about your program's results, purescript-spec provides a familiar unit testing interface.
  • Quickcheck tests can be run alongside the unit tests using a purescript-spec extension.
  • The purescript-spec test runner also supports running tests in a browser environment.

Slide 26: PSCI

  • An easy way to get started playing around with PureScript, once you have it installed, is through PSCi, the PureScript REPL.
  • Here, you can evaluate PureScript expressions, browse your loaded modules, and even ask the compiler to show you the types it infers for the expressions you enter.

Slide 27: Pursuit

  • Pursuit is a search engine and package browser for PureScript libraries.
  • I find it particularly useful for quickly looking up functions by their type signatures.

Slide 28: PureScript wiki

  • The PureScript wiki has detailed information about each individual feature of the language. I recommend reading the FFI guide for some helpful FFI tips.
  • If you need help with anything, the community is pretty active on the #purescript IRC channel on freenode.

Slide 29: purescript-demo-mario

  • If you want to see everything put together into a real program, I recommend you look through the source of this very simple Mario game.
  • It allows you to run and jump as Mario in a web browser, and it's written entirely in PureScript & HTML.
  • It's intended to be both a beginner PureScript learning resource and an example of how to use PureScript in the browser.

Slide 30: purescript-express

  • Once you've done that, I recommend you play around with the PureScript adaptor for the Express web framework.
  • It comes with a decent example, and should allow you to actually build something useful at the same time.

Slide 31: purescript-node-webkit

  • And we can even make desktop apps entirely in PureScript with nw.js through the purescript-node-webkit adapter.

Slide 32: PureScript by Example

  • If you want to go in-depth with PureScript, and learn more about functional programming patterns in general, check out PureScript by Example on Leanpub.
  • It was written by Phil Freeman, the original author of the PureScript compiler.

Slide 33: Big Changes in 0.7.x

  • And lastly, don't mistake my excitement for PureScript with an endorsement to go out and start running it on your pacemakers. It's still quite young and under heavy development.
  • There are many big changes in these early pre-1.0 releases. Some features we're looking forward to in 0.7.1 and beyond: generic deriving, exhaustivity checker, improved error messages and warnings, JVM/C++11 backends, parallel builds, provenance for type variables in errors, ECMAScript 6 in FFI, and data kinds. One of the notable features PureScript is missing is constraint inference, so if you're writing polymorphic functions with constraints, you'll need to explicitly annotate them.

Slide 34: Conclusion

  • I think PureScript is really cool, and it has tons of potential. JavaScript is everywhere, and we need a sane way to work with it. I believe its approach of using row types to represent JavaScript interfaces, while stealing all the good parts from Haskell, will lead to a very successful language.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment