class: center, middle
Better know a language
PureScript
by Taylor Fausak
on October 22, 2015
??? For more information about this talk, including a recording of it, please go to its event page on Google+ at:
--- # What is PureScript? According to the PureScript website, > PureScript is a small strongly typed programming language that compiles to > JavaScript. --- # What is PureScript? According to the `purescript` package, > A small strongly, statically typed programming language with expressive > types, inspired by Haskell and compiling to Javascript. --- # What is PureScript? Colloquially, > Haskell for JavaScript --- # Why PureScript? There are other Haskell-to-JavaScript projects, like - Emscripten: converts LLVM bytecode (from GHC) to JavaScript - Fay: compiles a subset of Haskell to JavaScript - GHCJS: compiles GHC Haskell to JavaScript - Haste: compiles Haskell to JavaScript - UHC: can compile Haskell to JavaScript back end There are also Haskell-like languages that compile to JavaScript, like - Elm: focuses on functional-reactive programming (FRP) in the browser - JMacro: generates JavaScript in Haskell - Roy: experimental Haskell-like language that compiles to JavaScript --- # Why PureScript? PureScript does not attempt to preserve Haskell's semantics. That means - There is no runtime. - It's strict instead of lazy. - It's foreign function interface (FFI) is easier to use. - It integrates well with existing JavaScript packages. --- # Why PureScript? Why should you use PureScript instead of some other Haskell-like thing? - Compared to projects like Emscripten and UHC, the output should be smaller and more readable. - Compared to languages that preserve Haskell's semantics like Fay, GHCJS, and Haste, there is no runtime. - Compared to code generators like JMacro, you don't have to get Haskell involved at all. - Compared to front end languages like Elm, you can run PureScript on Node.js. --- # What does PureScript look like? The classic "hello world" is short and sweet. ``` module Main where import Control.Monad.Eff.Console main = log "Hello from PureScript!" ``` "Eff" is short for "effect", meaning it's impure. --- # What does PureScript look like? This is how a real file might look. ``` module Main (main) where import Prelude import qualified Control.Monad.Eff as Eff import qualified Control.Monad.Eff.Console as Console main :: Eff.Eff (console :: Console.CONSOLE) Unit main = do Console.log "Hello from PureScript!" ``` It was typed before, but now it's explicit. --- # What does PureScript look like? The generated JavaScript is "human-readable". ``` js // Generated by psc version 0.7.4.1 "use strict"; var Control_Monad_Eff_Console = require("Control.Monad.Eff.Console"); var main = Control_Monad_Eff_Console.log("Hello from PureScript!"); module.exports = { main: main }; ``` --- # What do you get from PureScript? - Algebraic data types - Pattern matching - Type inference - Type classes - Higher kinded types - Rank-N types - Extensible records - Extensible effects - Modules - Simple FFI - No runtime system - Human-readable output --- # What do you get from PureScript? TL;DR > Haskell plus extensible records and effects (minus some esoteric stuff) --- # Extensible records This is also known as row polymorphism, structural typing, or static duck typing. And it's *the coolest thing* about PureScript. ``` fullName person = person.first ++ " " ++ person.last fullName { first: "Taylor", last: "Fausak" } -- "Taylor Fausak" ``` Writing functions like this in Haskell is annoying. It requires something like type classes or lenses. Extensible records make it almost trivial. --- # Extensible records This function can also be written with pattern matching. ``` fullName { first: f, last: l } = f ++ " " ++ l ``` --- # Extensible records This is the type signature for `fullName`. ``` fullName :: forall r. { first :: String, last :: String | r } -> String ``` It means that it's argument must have both "first" and "last" string fields. It may have more, but it must have at least those. And they must be the right type. --- # Extensible records Missing a field will give an error. ``` fullName { first: "Taylor" } -- Error checking that type -- { first :: Prim.String } -- subsumes type -- { last :: Prim.String, first :: Prim.String | r } ``` --- # Extensible records A field with the wrong type will give an error. ``` fullName { first: "Taylor", last: 123 } -- Error checking that type -- { last :: Prim.Int, first :: Prim.String } -- subsumes type -- { last :: Prim.String, first :: Prim.String | r } ``` --- # Extensible records Adding extra fields is fine. ``` fullName { first: "Taylor", last: "Fausak", age: 26 } -- "Taylor Fausak" ``` If you want to disallow extra fields, you can say that in with the type. ``` fullName :: forall r. { first :: String, last :: String | r } -> String fullName' :: { first :: String, last :: String } -> String ``` This means that the record must have *exactly* these fields and no more. --- # Extensible records With the new type signature, an extra field will give an error. ``` fullName' { first: "Taylor", last: "Fausak", age: 26 } -- Error checking that type -- { age :: Prim.Int, last :: Prim.String, first :: Prim.String } -- subsumes type -- { last :: Prim.String, first :: Prim.String } ``` --- # Extensible records Those type signatures quickly get annoying. Fortunately type aliases make it better. ``` type Person r = { first :: String, last :: String | r } fullName :: forall r. Person r -> String type Person' = { first :: String, last :: String } fullName' :: Person' -> String ``` --- # Extensible records With extensible records, you could accidentally allow a program to type check because you passed a record that just so happened to have the right fields. If you want stronger guarantees, you can use `newtype`. ``` newtype Person = Person { first :: String, last :: String } fullName :: Person -> String fullName (Person person) = person.first ++ " " ++ person.last fullName (Person { first: "Taylor", last: "Fausak" }) -- "Taylor Fausak" fullName { first: "Taylor", last: "Fausak" } -- Error checking that type -- { last :: Prim.String, first :: Prim.String } -- subsumes type -- Main.Person ``` --- # Extensible effects Extensible effects are like extensible records for the type system. We've actually already seen them. Remember this type signature? ``` main :: Eff.Eff (console :: Console.CONSOLE) Unit ``` It means that the `main` function uses the `CONSOLE` effect. Which basically means that it can print stuff out. This is way more granular than Haskell's `IO` type, which allows you to do pretty much anything. --- # Extensible effects While this granularity can be nice, it can also get out of control quickly. ``` main :: Eff ( console :: CONSOLE , random :: RANDOM , err :: EXCEPTION , dom :: DOM -- ad naseum ) Unit ``` This is why you'll often see PureScript programs without type signatures on `main`. I suppose this isn't any worse than Haskell's `main :: IO ()`, but it feels annoying to work with. It reminds me of Java's checked exceptions. --- # FFI: PureScript from JavaScript Calling PureScript from JavaScript is pretty straightforward. ``` module A where import Prelude add x y = x + y ``` The only gotcha is that all functions are curried. ``` js var A = require('A'); A.add(3)(4); // 7 ``` --- # FFI: JavaScript from PureScript For simple values, the FFI is nice. You just have to supply the type signature. ``` module URI where foreign import encodeURIComponent :: String -> String ``` ``` js // module URI exports.encodeURIComponent = encodeURIComponent; ``` ``` import URI encodeURIComponent "hello world" -- "hello%20world" ``` --- # FFI TL;DR: It's there. It works. Going from untyped to typed sucks. --- # Nits The package ecosystem is excessively granular. To get anything done, you need tens of lines of imports. ``` import Prelude import qualified Control.Monad.Aff as Aff import qualified Control.Monad.Eff as Eff import qualified Control.Monad.Eff.Class as Eff import qualified Control.Monad.Eff.Console as Console import qualified Data.Argonaut.Core as JSON import qualified Data.Argonaut.Parser as JSON import qualified Data.Argonaut.Printer as JSON import qualified Data.Array as Array import qualified Data.Either as Either import qualified Data.Foldable as Foldable import qualified Data.Foreign as Foreign import qualified Data.Maybe as Maybe import qualified Data.Maybe.Unsafe as Maybe import qualified Data.Nullable as Nullable import qualified Data.Options as Options import qualified Data.String as String import qualified Data.StrMap as StrMap import qualified Data.Tuple as Tuple import qualified Network.HTTP as Network ``` --- # Nits You are not required to lock down versions of transitive dependencies that you use. ``` js { "dependencies": { "purescript-arrays": "0.4.2" // implicit: "purescript-tuples": "^0.4.0" } } ``` ``` -- Which version? Not listed in dependencies. import Data.Tuple ``` This may actually be a problem with Bower, I'm not sure. --- # Nits The syntax is annoyingly close to Haskell in a lot of ways. No all the differences are bad, but they can be surprising if you already know Haskell. ``` import Data.Tuple (Tuple ()) -- Must include parentheses, otherwise tries to import type class. f x y = ... where z = ... -- Must *not* be indented! (!) x y = ... -- Can't be defined infix. g :: forall a. a -> a -- Always explicitly list type variables. class (Eq a) <= Ord a where -- Arrow goes other direction. instance arbitraryUnit :: Arbitrary Unit where -- Instances must have names. runST do -- No dollar sign required! h :: forall a. List a -> Unit -- More verbose type names (I like this one). ``` --- # Nits Error messages can be a bit obtuse. ``` > import Prelude > let f x y = x + y Error found: Error in module $PSCI: Error in value declaration f: Error at line 1, column 5 - line 1, column 17: No instance found for Prelude.Semiring _5 See https://github.com/purescript/purescript/wiki/Error-Code-NoInstanceFound for more information, or to contribute content related to this error. ``` On the other hand, wiki pages for errors are a great idea. --- # Nits Function application can use non-obvious operators. ``` f $ x x # f ``` Compared to `<|` and `|>` from Elm (and other), these are weird. --- class: center, middle # Real world --- # Real world For certain values of "real world". > [purescript-halogen-example](https://github.com/tfausak/purescript-halogen-example) --- class: center, middle
# Questions?