Time: 2.5 hours
$ cd exercise1
$ npm run repl
> import Prelude
> :type 1.0
Number
> :type 1
Int
> :type true
Boolean
> :type "test"
String
> :type 'a'
Char
> :type [1, 2, 3]
Array Int
> :type [true, false]
Array Boolean
> :type [1, false]
Could not match type Int with Boolean.
myList :: Array Int
myList = [1, 2, 3, 4]
colors :: Array String
colors = ["red", "orange", "yellow", "green"]
Arrays always contain only a single type
(they're homogenous)
-- A record
user :: { name :: String, id :: Int }
user = {name: "Joe", id: 1}
-- Accessing a field
log user.name
-- Setting name to "Jane"
user {name = "Jane"}
Records ~= JavaScript objects
type User = { name :: String, id :: Int }
type Address =
{ street :: String
, city :: String
, state :: String
}
type AddressBook = Array Address
type UserId = String
type Log = Array String
add :: Int -> Int -> Int
add x y = x + y
noEnergy :: Creep -> Boolean
noEnergy creep = C.amtCarrying creep resource_energy == 0
findProduct :: ProductId -> ProductDirectory -> Maybe Product
findProduct id directory = -- body omitted
loadConfig :: FilePath -> Aff (Either (NonEmptyList Error) Config)
loadConfig cfgFile = do
-- body omitted
-- Calling functions:
add 1 2
noEnergy creep
findProduct id dir
loadConfig path
sum :: Int -> Int -> Int
sum x y = x + y
-- Equivalent:
sum 1 3
(sum 1) 3
addTwo :: Int -> Int
addTwo = sum 2
-- addTwo 6
-- 8
-- Common pattern: dropping the last parameters
expiredSessions :: Array Session -> Array Session
expiredSessions arr = filter isExpired arr
-- can be shortened to
expiredSessions :: Array Session -> Array Session
expiredSessions = filter isExpired
-- Works for any type of values in the array
length :: forall a. Array a -> Int
-- Works for many types of containers,
length :: forall a b f. Foldable f => Semiring b => f a -> b
map :: forall a b f. Functor f => (a -> b) -> f a -> f b
\user -> user.name
\id -> id == "teh_id"
val = Array.filter (\id -> id == "teh_id") ["an_id", "teh_id", "other_id"]
"everything after goes in parentheses"
-- These are equivalent:
main = log $ "User: " <> "Foo"
main = log ("User: " <> "Foo")
-- These are equivalent:
main = log $ "User: " <> show $ 1 + 5
main = log ("User: " <> show (1 + 5))
(<> is for appending)
Everything is in small libraries
What | Package | Module to import |
---|---|---|
Basic functions and types | purescript-prelude | Prelude |
Array functions | purescript-arrays | Data.Array |
List functions | purescript-lists | Data.List |
Record functions | purescript-record | Data.Record |
Tuples | purescript-tuples | Data.Tuple |
The stuff you thought would be in one of ^ | purescript-foldable-traversable | Data.Foldable |
Maybe ("option type") | purescript-maybe | Data.Maybe |
Either ("result type") | purescript-either | Data.Either |
Libraries are in psc-package and/or Bower
Library documentation is on Pursuit
doThing a b = "Doin my thing with " <> a <> ", " <> b
Warning 4 of 6:
in module Main
at src/Main.purs line 892, column 1 - line 892, column 54
No type declaration was provided for the top-level declaration of doThing.
It is good practice to provide type declarations as a form of documentation.
The inferred type of doThing was:
String -> String -> String
in value declaration doThing
sumOfList :: Int
sumOfList = ?sumThemThings [1, 2, 3, 4]
Error found:
in module Main
at src/Main.purs line 893, column 13 - line 893, column 27
Hole 'sumThemThings' has the inferred type
Array Int -> Int
You could substitute the hole with one of these values:
Data.Array.length :: forall a. Array a -> Int
Data.Foldable.length :: forall a b f. Foldable f => Semiring b => f a -> b
Data.Foldable.product :: forall a f. Foldable f => Semiring a => f a -> a
Data.Foldable.sum :: forall a f. Foldable f => Semiring a => f a -> a
Data.Semiring.one :: forall a. Semiring a => a
Data.Semiring.zero :: forall a. Semiring a => a
Unsafe.Coerce.unsafeCoerce :: forall a b. a -> b
in value declaration sumOfList
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)
-- With guards:
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)
isEmpty :: forall a. Array a -> Boolean
isEmpty [] = true
isEmpty _ = false
takeFive :: Array Int -> Int
takeFive [0, 1, a, b, _] = a * b
takeFive _ = 0
showPerson :: { first :: String, last :: String } -> String
showPerson { first: x, last: y } = y <> ", " <> x
showPerson :: { first :: String, last :: String } -> String
showPerson { first, last } = first <> ", " <> last
-- This is equivalent to
showPerson :: { first :: String, last :: String } -> String
showPerson { first: first, last: last } = first <> ", " <> last
isEmpty :: forall a. Array a -> Boolean
isEmpty [] = true
isEmpty _ = false
-- Or equivalently:
isEmpty :: forall a. Array a -> Boolean
isEmpty arr = case arr of
[] -> true
_ -> false
-- v--type
data Filter = ByTitle | ByDate | ByRelevance
-- ^ data constructors
-- v-- function takes in type Filter
applyFilter :: forall a. Filter -> Array a -> Array a
applyFilter ByTitle =
applyFilter ByDate =
applyFilter ByRelevance =
-- ^ but you can pattern-match on the data constructor
data Shape
= Circle Point Number
| Rectangle Point Number Number
| Line Point Point
| Text Point String
draw :: Shape -> Drawing
draw (Circle pt radius) =
draw (Rectangle pt width height) =
draw (Line startPt endPt) =
draw (Text pt text) =
data Maybe a = Nothing | Just a
data Either a b = Left a | Right b
show :: Maybe User -> String
show (Just user) = user.firstName <> user.lastName
show Nothing = "No user!"
decodeJSON :: String -> Either ValidationErrors User
show :: Either ValidationErrors User -> String
show (Right user) = user.firstName <> user.lastName
show (Left errors) = "Errors: " <> show errors
For ADTs with one constructor that takes one type
At run time is same as underlying type
newtype PhoneNumber = PhoneNumber String
newtype UserId = UserId Int
-- ~equivalent to this, except at run time
data PhoneNumber = PhoneNumber String
data UserId = UserId Int
-- Main.purs (PureScript)
foreign import pow :: Number -> Number -> Number
// Main.js (JavaScript)
exports.pow = function(x) {
return function(y) {
return Math.pow(x, y)
}
}
# Install JS dependency
npm install --save js-yaml
-- PureScript
module Kubernetes.Yaml where
foreign import safeLoadImpl :: String -> Effect Foreign
data YamlError = YamlError Error
safeLoad :: String -> Either (NonEmptyList YamlError) Foreign
safeLoad str = runPure $ do
loaded <- try (safeLoadImpl str)
pure (bimap (pure <<< YamlError) id loaded)
// JavaScript
exports.safeLoadImpl = function(str){
return function(){
const yaml = require('js-yaml')
return yaml.safeLoad(str)
}
}
-- PureScript
module FancyComponent where
toReact :: forall props. State -> Effect (ReactClass props)
toReact state = do
app <- start
{ initialState: state
, view
, foldp
, inputs: []
}
renderToReact app.markup app.input
// JavaScript
const FancyComponent = PS.FancyComponent.toReact(state)()
-- Main.purs (PureScript)
foreign import pow :: Number -> Number -> Number
// Main.js (JavaScript)
exports.pow = function(x) {
return function(y) {
return Math.pow(x, y)
}
}
Sort of like a Java interface
A type can have an instance for the type class (~implement the interface) and you can define functions that work generically with any value that has an instance for a type class.
Things that can be printed to a string
-- Examples (in REPL)
> show 1
"1"
> show false
"false"
> show [1,2]
"[1,2]"
-- Type class definition
class Show a where
show :: a -> String
-- Type class instance for Boolean
instance showBoolean :: Show Boolean where
show true = "true"
show false = "false"
-- So you can define functions like this:
--type class constraint vvvvvv
formatMsg :: forall a. Show a => a -> String
formatMsg val = "search-service: " <> show val
Things that can be tested for equality
E.g. Ints, Strings
-- Examples (in REPL)
> "foo" == "bar"
false
> 1 + 2 == 3
true
-- Type class definition
class Eq a where
eq :: a -> a -> Boolean
-- Type class instance for Int
instance eqInt :: Eq Int where
eq = refEq
-- Type class instance for String
instance eqString :: Eq String where
eq = refEq
Type classes define ways of piping and transforming data: