Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Getting Started with PureScript

Getting Started with PureScript

## 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

View the rendered Markdown at https://gist.github.com/michaelficarra/4473c9901120f2ff87e0.

### 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 `String`s instead of `Number`s.
* 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.

Michael Ficarra

July 15, 2015

More Decks by Michael Ficarra

Other Decks in Programming


    Michael Ficarra

    View Slide

  2. Literals
    a = 0.0 :: Number
    b = "a" :: String
    c = true :: Boolean
    d = [0.0] :: Array Number
    e = {foo: 0.0} :: { foo :: Number }
    f = (\a -> a) :: forall a. a -> a

    View Slide

  3. Function Definition & Application
    sum :: Int -> Int -> Int
    sum a b = a + b
    successor :: Int -> Int
    successor = sum 1
    sumSuccessors :: Int -> Int -> Int
    sumSuccessors a b = sum (successor a) (successor b)
    seven :: Int
    seven = sumSuccessors 2 3

    View Slide

  4. Function Definition (cont.)
    identity :: forall t. t -> t
    identity a = a
    const :: forall a b. a -> b -> a
    const x y = x
    ($) :: forall a b. (a -> b) -> a -> b
    ($) f x = f x
    infixr 0 $

    View Slide

  5. Records
    hank :: { name :: String, birthYear :: Int }
    hank = {name: "Hank", birthYear: 1985}
    laptop :: { name :: String, mfgDate :: Int }
    laptop = {name: "Hotaru", mfgDate: 2014}
    greet :: forall r. { name :: String | r } -> String
    greet namedThing = "Hello, " ++ namedThing.name
    hankJr :: { name :: String, birthYear :: Int }
    hankJr = hank { birthYear = 2010 }

    View Slide

  6. Algebraic Data Types
    data Ordering = Less | Equal | Greater
    compare :: Number -> Number -> Ordering
    compare a b =
    if a < b then Less
    else if a == b then Equal
    else Greater

    View Slide

  7. Algebraic Data Types (cont.)
    data List t = EmptyList | Cons t (List t)
    listA :: List Number
    listA = Cons 0.0 (Cons 1.0 (Cons 2.0 EmptyList))
    listB :: List String
    listB = Cons "x" (Cons "y" EmptyList)
    listC :: forall a. List a
    listC = EmptyList

    View Slide

  8. Type Aliases
    type Option = Maybe
    type NumberArray = Array Number
    type NumberList = List Number
    type Point = { x :: Number, y :: Number }
    type Pair a b = { left :: a, right :: b }
    type Predicate a = a -> Boolean

    View Slide

  9. Pattern Matching
    data Maybe t = Nothing | Just t
    map :: forall a b. (a -> b) -> Maybe a -> Maybe b
    map f Nothing = Nothing
    map f (Just x) = Just (f x)
    map' f maybe = case maybe of
    Nothing -> Nothing
    (Just x) -> Just (f x)

    View Slide

  10. Pattern Matching (cont.)
    type Point = { x :: Number, y :: Number }
    doubleZero :: Point -> Point
    doubleZero [email protected]{ x: 0, y: y } = p { y = y * 2.0 }
    doubleZero p = p
    const :: forall a b. a -> b -> a
    const x _ = x

    View Slide

  11. Program Structure
    module Namespace.ModuleName where
    import OtherNamespace.OtherModule
    data Type0 t
    = Constructor0 t
    | Constructor1 t t
    value0 = Constructor1 0 1
    value1 = Constructor0 "string"

    View Slide

  12. Program Structure (cont.)
    module ModuleName (value0, main, Type0(..)) where
    import OtherModule (value1, Type1(..))
    import qualified Prelude as P
    data Type0 t = Constructor0 t | Constructor1 t t
    value0 :: Type0 Int
    value0 = Constructor1 0 1
    main :: Eff () Unit
    main = P.return P.unit

    View Slide

  13. Hello, World!
    module Main (main) where
    import Control.Monad.Eff (Eff(..))
    import qualified Control.Monad.Eff.Console as Console
    main :: Eff (console :: Console.CONSOLE) Unit
    main = Console.log "Hello, World!"

    View Slide

  14. Effects
    module EffExample (main) where
    import Control.Monad.Eff (Eff(..))
    import Control.Monad.Eff.Random (random, RANDOM())
    -- random :: Eff (random :: RANDOM) Number
    import Control.Monad.Eff.Console (log, print, CONSOLE())
    -- log :: String -> Eff (console :: CONSOLE) Unit
    -- print :: ... -> Eff (console :: CONSOLE) Unit
    main :: Eff (console :: CONSOLE, random :: RANDOM) Unit
    main = random >>= print

    View Slide

  15. Composing Effects
    main :: Eff (console :: CONSOLE, random :: Random) Unit
    main = do
    a <- random
    b <- random
    log "First random number:"
    print a
    log "Second random number:"
    print b

    View Slide

  16. Type Classes
    class Show a where
    show :: a -> String
    instance showBoolean :: Show Boolean where
    show true = "true"
    show false = "false"
    instance showArray :: (Show a) => Show (Array a) where
    show array = "[" <> map show array <> ]"

    View Slide

  17. JavaScript Interoperability (FFI)
    -- MyModule.purs
    foreign import parseFloat :: String -> Number
    foreign import pow :: Number -> Number -> Number
    // MyModule.js
    exports.parseFloat = parseFloat;
    exports.pow =
    function pow(base) {
    return function(exponent) {
    return Math.pow(base, exponent);

    View Slide

  18. JavaScript Interoperability (cont.)
    foreign import data TimeoutID :: *
    foreign import data TIMER :: !
    foreign import setTimeout :: forall e.
    Eff e Unit ->
    Number ->
    Eff (timer :: TIMER) TimeoutID
    foreign import clearTimeout ::
    TimeoutID ->
    Eff (timer :: TIMER) Unit

    View Slide

  19. Installation
    Precompiled Binaries
    $ cabal update
    $ cabal install purescript
    $ brew update
    $ brew install purescript
    $ npm install -g purescript

    View Slide

  20. Starting a Project
    $ npm install -g pulp
    $ pulp init
    Yeoman with Gulp
    $ npm install -g yo generator-purescript bower
    $ yo purescript
    $ mkdir -p src/Namespace/Namespace
    $ touch !$/ModuleName.purs

    View Slide

  21. Building
    $ pulp build
    $ gulp
    $ psc 'src/**/*.purs' --ffi 'src/**/*.js' \
    'bower_components/purescript-*/src/**/*.purs' \
    --ffi 'bower_components/purescript-*/src/**/*.js'
    $ psc-bundle --module ModuleName --main ModuleName.Main \
    'output/**/*.js' > dist/ModuleName.js

    View Slide

  22. Managing Dependencies
    Install Bower
    $ npm install -g bower
    $ bower init
    Install Dependencies
    $ bower install --save purescript-monoid
    $ bower install --save-dev purescript-quickcheck
    $ git tag -a v1.0.0 -m 'Version 1.0.0'
    $ git push --tags
    $ bower register

    View Slide

  23. Testing
    module Test.Main (main) where
    import Test.QuickCheck (quickCheck)
    import Data.Array (sort, length)
    sortIsIdempotent :: Array Number -> Boolean
    sortIsIdempotent a = sort (sort a) == sort a
    sortPreservesLength :: Array Number -> Boolean
    sortPreservesLength a = length (sort a) == length a
    main = do
    quickCheck sortIsIdempotent
    quickCheck sortPreservesLength

    View Slide

  24. Testing (cont.)
    $ bower install --save-dev purescript-quickcheck
    $ pulp test
    * Building project in /path/to/purescript-arrays
    * Build successful. Running tests...
    100/100 test(s) passed.
    100/100 test(s) passed.
    * Tests OK.

    View Slide

  25. Unit Tests
    main = runNode [consoleReporter] do
    describe "Data.Array" do
    describe "range" do
    it "creates a range" do
    range 3 5 `shouldEqual` [3, 4, 5]
    range 1 100 `shouldContain` 50
    describe "length" do
    it "calculates the length" do
    length [] `shouldEqual` 0
    length [0] `shouldEqual` 1
    length [[0, 0], [0, 0]] `shouldEqual` 2
    describe "cons" do
    pending "cons tests"

    View Slide

  26. View Slide

  27. View Slide

  28. View Slide

  29. View Slide

  30. View Slide

  31. View Slide

  32. View Slide

  33. Big Changes in 0.7.x
    ● (32-bit) Int type and integer literals
    ● character literals
    ● no more Prelude auto-import
    ● no more [a] notation for Array types
    ● no more (a:as) cons patterns
    ● no more inline FFI
    ● new typeclasses: Ring, Lattice, Bounded, Poset, ...
    ● effects in PSCi
    ● fix suggestions in error messages

    View Slide

  34. View Slide