As an introductory example, here is the usual "Hello World" written in PureScript:
module Main where
import Control.Monad.Eff.Console
main = log "Hello, World!"The following code defines a Person data type and a function to generate a string representation for a Person:
data Person = Person { name :: String, age :: Number }
showPerson :: Person -> String
showPerson (Person o) = o.name ++ ", aged " ++ show o.age
examplePerson :: Person
examplePerson = Person { name: "Bonnie", age: 26 }Line by line, this reads as follows:
Personis a data type with one constructor, also calledPerson- The
Personconstructor takes an object with two properties,namewhich is aString, andagewhich is aNumber - The
showPersonfunction takes aPersonand returns aString showPersonworks by case analysis on its argument, first matching the constructorPersonand then using string concatenation and object accessors to return its result.examplePersonis a Person object, made with thePersonconstructor and given the String "Bonnie" for the name value and the Number 26 for the age value.
The type system defines the following types:
- Primitive Types: Number, String, Boolean
- Integers
- Arrays
- Records
- Tagged Unions
- Newtypes
- Functions
- Polymorphic Types
- Constrained Types
- Type Synonyms
- Rows
The primitive types String, Number and Boolean correspond to their Javascript equivalents at runtime.
The Int type represents integer values. The runtime representation is also a normal JavaScript Number; however, operations like (+) on Int values are defined differently in order to ensure that you always get Int values as a result.
PureScript arrays correspond to Javascript arrays at runtime, but all elements in an array must have the same type.
PureScript records correspond to Javascript objects. They may have zero or more named fields, each with their own types.
Tagged unions consist of one or more constructors, each of which takes zero or more arguments.
Tagged unions can only be created using their constructors, and deconstructed through pattern matching (see later).
For example::
data Foo = Foo | Bar String
runFoo Foo = "It's a Foo"
runFoo (Bar s) = "It's a Bar. The string is " ++ s
test = runFoo Foo ++ runFoo (Bar "Test")In the example, Foo is a tagged union type which has two constructors. Its first constructor Foo takes no arguments, and its second Bar takes one, which must be a String.
runFoo is an example of pattern matching on a tagged union type to discover its constructor, and the last line shows how to construct values of type Foo.
Newtypes are like data types (which are introduced with the data keyword), but are restricted to a single constructor which contains a single argument. Newtypes are introduced with the newtype keyword::
newtype Percentage = Percentage NumberThe representation of a newtype at runtime is the same as the underlying data type. For example, a value of type Percentage is just a JavaScript number at runtime.
Newtypes can be assigned their own type class instances, so for example, Percentage can be given its own Show instance::
instance showPercentage :: Show Percentage where show (Percentage n) = show n ++ "%"
Functions in PureScript are like their Javascript counterparts, but always have exactly one argument.
Expressions can have polymorphic types:
identity x = xidentity is inferred to have (polymorphic) type forall t0. t0 -> t0. This means that for any type t0, identity can be given a value of type t0 and will give back a value of the same type.
A type annotation can also be provided::
identity :: forall a. a -> a
identity x = xPolymorphism is not limited to abstracting over types. Values may also be polymorphic in types with other kinds, such as rows or effects (see "Kind System").
For example, the following function accesses two properties on a record::
addProps o = o.foo + o.bar + 1The inferred type of addProps is::
forall r. { foo :: Int, bar :: Int | r } -> NumberHere, the type variable r has kind # * - it represents a row of types. It can be instantiated with any row of named types.
In other words, addProps accepts any record which has properties foo and bar, and any other record properties.
Therefore, the following application compiles::
addProps { foo: 1, bar: 2, baz: 3 }even though the type of addProps does not mention the property baz. However, the following does not compile::
addProps { foo: 1 }since the bar property is missing.
It is also possible for the forall quantifier to appear on the left of a function arrow, inside types record fields and data constructors, and in type synonyms.
In most cases, a type annotation is necessary when using this feature.
As an example, we can pass a polymorphic function as an argument to another function::
poly :: (forall a. a -> a) -> Boolean
poly f = (f 0 < 1) == f trueNotice that the polymorphic function's type argument is instantiated to both Number and Boolean.
An argument to poly must indeed be polymorphic. For example, the following fails::
test = poly (\n -> n + 1)since the skolemized type variable a does not unify with Int.
A row of types represents an unordered collection of named types without duplicates.
Rows have kind # k for some kind k, and so values do not have types which are rows. Rather, rows can be used in type signatures to define record types or other type where labelled, unordered types are useful.
To denote a closed row, separate the fields with commas, with each label separated from its type with a double colon::
(name :: String, age :: Number)It may be necessary, depending on the context, to surround a row in parentheses. (However, see #
To denote an open row (i.e. one which may unify with another row to add new fields), separate the specified terms from a row variable by a pipe::
(name :: String, age :: Number | r)For convenience, it is possible to declare a synonym for a type using the type keyword. Type synonyms can include type arguments.
For example::
type Foo = { foo :: Number, bar :: Number }
addFoo :: Foo -> Number
addFoo o = o.foo + o.bar
type Bar a = { foo :: a, bar :: a }
combineBar :: forall a b. (a -> a -> b) -> Bar a -> b
combineBar f o = f o.foo o.barPolymorphic types may be predicated on one or more constraints. See the chapter on type classes for more information.
Most types can be inferred (not including Rank N Types and constrained types), but annotations can optionally be provided using a double-colon::
one = 1 :: Int
-- Either of the following will type check, because the first is a more general form of the second
x = one :: forall a. (Semiring a) => a
x = one :: IntThe kind system defines the following kinds:
*, the kind of types.!, the kind of effects.- Arrow kinds
k1 -> k2 - Row kinds
# k
The kind # k of rows is used to classify labelled, unordered collections of types of kind k.
For example # * is the kind of rows of types, as used to define records, and # ! is the kind of rows of effects, used to define the monad Eff of extensible effects.
A type variable can refer to not only a type or a row, but a type constructor, or row constructor etc., and type variables with those kinds can be bound inside a forall quantifier.
Syntax is whitespace sensitive. The general rule of thumb is that declarations which span multiple lines should be indented past the column on which they were first defined on their subsequent lines.
That is, the following is valid:
foo = bar +
bazBut this is not:
foo = bar +
bazA single line comment starts with --:
-- This is a commentMulti-line comments are enclosed in {- and -}. These can be nested:
{-
Comment
{- nested comment -}
continued comment
-}Values at the top level of a module are defined by providing a name followed by an equals sign and then the value to associate:
one = 1Functions can also be defined at the top level by providing a list of patterns on the left hand side of the equals sign:
add x y = x + ySee the section on pattern matching for more details about the kinds of patterns that can be used here.
Functions using pattern matching may be defined multiple times to handle different pattern matches:
isEmpty [] = true
isEmpty _ = falseThis does not mean functions can be arbitrarily overloaded with different numbers or types of arguments though.
Guards can also be used in these definitions:
isEmptyAlt xs | length xs == 0 = true
isEmptyAlt _ = falseA top level declaration is generally defined with a type signature:
multiply :: Number -> Number -> Number
multiply x y = x * yType signatures are not required for top-level declarations in general, but is good practice to do so. See the section on types for more details.
Function application is indicated by just the juxtaposition of a function with its arguments:
add 10 20PureScript functions are defined as curried, so partial application has no special syntax:
add10 = add 10Numeric literals can be integers (type Int) or floating point numbers (type Number). Floating point numbers are identified by a decimal point. Integers in hexadecimal notation should be preceded by the characters 0x:
16 :: Int
0xF0 :: Int
16.0 :: FloatString literals are enclosed in double-quotes and may extend over multiple lines. Line breaks should be surrounded by slashes as follows:
"Hello World"
"Hello \
\World"Line breaks will be omitted from the string when written this way. If line breaks are required in the output, they can be inserted with \n, or by using an alternate string syntax, where the string is enclosed in triple double-quotes. This also allows the use of double quotes within the string with no need to escape them:
jsIsHello :: String
jsIsHello = """
function isHello(greeting) {
return greeting === "Hello";
}
"""The boolean literals are true and false.
Function values (sometimes called lambdas) are introduced by using a backslash followed by a list of argument names:
\a b -> a + bwhich would correspond to the following JavaScript:
function (a) {
return function (b) {
return a + b;
}
}Array literals are surrounded by square brackets, as in JavaScript:
[]
[1, 2, 3]Record literals are surrounded by braces, as in JavaScript:
{}
{ foo: "Foo", bar: 1 }Record literals with wildcards can be used to create a function that produces the record instead:
{ foo: _, bar: _ }is equivalent to:
\foo bar -> { foo: foo, bar: bar }To access a property of a record, use a dot followed by the property name, as in JavaScript:
rec.propertyNameThere are also partially applied accessors, where an underscore is followed by a property name:
_.propertyNameThis is equivalent to:
\rec -> rec.propertyNameProperties on records can be updated using the following syntax:
rec { key1 = value1, ..., keyN = valueN }For example, the following function increments the foo property on its argument:
\rec -> rec { foo = rec.foo + 1 }Wildcards can also be used in updates to produce a partially applied update:
rec { foo = _ }This is equivalent to:
\foo -> rec { foo = foo }An underscore can also appear in the object position in an updater:
_ { foo = 1 }This is equivalent to:
\rec -> rec { foo = 1 }Operators in PureScript are just regular functions. The Prelude defines a number of operators which correspond to JavaScript operators.
| Function | JS Operator | Meaning |
|---|---|---|
negate |
- |
Numeric negation |
not |
! |
Boolean negation |
complement |
~ |
Binary negation |
| Function | JS Operator | Meaning |
|---|---|---|
+ |
+ |
Numeric addition |
- |
- |
Numeric subtraction |
* |
* |
Numeric multiplication |
/ |
/ |
Numeric division |
% |
% |
Numeric modulus |
== |
== |
Equality check |
/= |
!= |
Inequality check |
< |
< |
Less than |
<= |
<= |
Less than or equal |
> |
> |
Greater than |
>= |
>= |
Greater than or equal |
&& |
&& |
Boolean AND |
| ` | ` | |
& |
& |
Binary AND |
| ` | ` | ` |
^ |
^ |
Binary XOR |
shl |
<< |
Shift Left |
shr |
>> |
Shift Right |
zshr |
>>> |
Zero-fill Shift Right |
++ |
+ |
String concatenation* |
Many of these operators are defined in type classes and work with lots of different types. For example, + and * work with not only Int or Number, but any Semiring (see "Type classes").
Operators can be used as normal values by surrounding them with parentheses:
and = (&&)Binary operators can be partially applied by listing them with one operand surrounded by parentheses:
half = (/ 2)
double = (2 *)Functions can be used as infix operators when they are surrounded by backticks:
foo x y = x * y + y
test = 10 `foo` 20Operator sections also work for functions used this way:
fooBy2 = (`foo` 2)User-defined infix operators can be defined by enclosing names in parentheses.
For example, to create an infix synonym for the Data.Array.range function:
(..) = Data.Array.rangeThis function can be used as follows::
oneToTen = 1 .. 10The associativity and precedence level of operators can be defined with the infix (no associativity), infixl (left associative), and infixr (right associative) top-level declarations:
infix 5 ..
infixl 7 %%
infixr 9 ^^See [[Understanding fixity declarations]] for more information about these declarations.
The if, then and else keywords can be used to create conditional expressions similar to a JavaScript ternary expression. The else block is always required:
conditional = if 2 > 1 then "ok" else "oops"The let keyword a collection of local declarations, which may be mutually recursive, and which may include type declarations::
factorial :: Int -> Int
factorial =
let
go :: Int -> Int -> Int
go acc 1 = acc
go acc n = go (acc * n) (n - 1)
in
go 1The where keyword can also be used to introduce local declarations at the end of a value declaration:
factorial :: Number -> Number
factorial = go 1
where
go :: Number -> Number -> Number
go acc 1 = acc
go acc n = go (acc * n) (n - 1)The do keyword introduces simple syntactic sugar for monadic expressions.
Here is an example, using the monad for the Maybe type:
maybeSum :: Maybe Number -> Maybe Number -> Maybe Number
maybeSum a b = do
n <- a
m <- b
let result = n + m
return resultmaybeSum takes two values of type Maybe Number and returns their sum if neither number is Nothing.
When using do notation, there must be a corresponding instance of the Monad type class for the return type.
Statements can have the following form:
a <- xwhich desugars tox >>= \a -> ...xwhich desugars tox >>= \_ -> ...or justxif this is the last statement.- A let binding
let a = x. Note the lack of theinkeyword.
The example maybeSum desugars to::
maybeSum a b =
a >>= \n ->
b >>= \m ->
let result = n + m
in return resultPureScript has basic support for type classes via the class and instance keywords.
Types appearing in class instances are must be of the form String, Number, Boolean, or C t1 ... tn where C is a type constructor (including [] and -> and t_i are types of the same form).
Type class instances are resolved based on the order in which they appeared in the source files. Overlapping instances will result in a compilation error.
Here is an example of the Show typeclass, with instances for String, Booleans and []::
class Show a where show :: a -> String
instance showString :: Show String where show s = s
instance showBoolean :: Show Boolean where show true = "true" show false = "false"
instance showArray :: (Show a) => Show [a] where show xs = "[" ++ joinWith ", " (map show xs) ++ "]"
example = show [true, false]
Superclass implications can be indicated in a class declaration with a backwards fat arrow <=::
class (Monad m) <= MonadFail m where fail :: forall a. String -> m a
Superclass instances will be used when searching for an instance of a subclass. For example, in the code below, the Monad constraint introduced by the return function can be discharged since Monad is a superclass of MonadFail::
assert :: forall m. (MonadFail m) => Boolean -> m Unit assert true = return unit assert false = fail "Assertion failed"
A constrained type will never be inferred for a top-level declaration without an explicit type signature.
Pattern matching deconstructs a value to bring zero or more expressions into scope. Pattern matches are introduced with the case keyword.
Pattern matches have the following general form::
case value of
pattern -> result
...
pattern -> resultPattern matching can also be used in the declaration of functions, as we have already seen::
fn pattern_1 ... pattern_n = resultPatterns can also be used when introducing functions. For example::
example x y z = x * y + zThe following pattern types are supported:
- Wildcard pattern
- Literal patterns
- Variable pattern
- Array patterns
- Constructor patterns
- Record patterns
- Named patterns
- Guards
Patterns need not be exhaustive. A pattern match failed exception will be thrown at runtime if no pattern matches the input. Patterns which are not exhaustive will generate warnings at compile time, however.
The wilcard _ matches any input and brings nothing into scope::
f _ = 0Literal patterns are provided to match on primitives::
f true = 0
f false = 1
g "Foo" = 0
g _ = 1
h 0 = 0
h _ = 1A variable pattern matches any input and binds that input to its name::
double x = x * 2
Array patterns match an input which is an array, and bring its elements into scope. For example::
f [x] = x
f [x, y] = x * y
f _ = 0Here, the first pattern only matches arrays of length one, and brings the first element of the array into scope.
The second pattern matches arrays with two elements, and brings the first and second elements into scope.
Constructor patterns match a data constructor and its arguments::
data Foo = Foo String | Bar Number Boolean
foo (Foo s) = true
foo (Bar _ b) = bRecord patterns match an input which is a record, and bring its properties into scope::
f { foo = "Foo", bar = n } = n
f _ = 0The patterns above can be combined to create larger patterns. For example::
f { arr = [x, _], take = "firstOfTwo" } = x
f { arr = [_, x, _] take = "secondOfThree" } = x
f _ = 0Named patterns bring additional names into scope when using nested patterns. Any pattern can be named by using the @ symbol::
f a@[_, _] = true
f _ = falseHere, in the first pattern, any array with exactly two elements will be matched and bound to the variable a.
Guards are used to impose additional constraints inside a pattern using boolean-valued expressions, and are introduced with a pipe after the pattern::
evens :: List Int -> Int
evens Nil = 0
evens (Cons x xs) | x % 2 == 0 = 1 + evens xs
evens (Cons _ xs) = evens xsWhen using patterns to define a function at the top level, guards appear after all patterns::
greater x y | x > y = true
greater _ _ = falseAll code in PureScript is contained in a module. Modules are introduced using the module keyword::
module A where
id x = xWhen referencing values or data types in another module, names may be qualified by using a dot::
module B where
foo = A.idA module can be imported using the import keyword. This will create aliases for all of the values and types in the imported module::
module B where
import AAlternatively, a list of names to import can be provided in parentheses::
module B where
import A (runFoo)Values, type constructors and data constructors can all be explicitly imported. A type constructor should be followed by a list of associated data constructors to import in parentheses. A double dot (..) can be used to import all data constructors for a given type constructor::
module B where
import A (runFoo, Foo(..), Bar(Bar))Modules can also be imported qualified, which means that their names will not be brought directly into scope, but rather, aliased to a different module name. This can be helpful when avoiding naming conflicts::
module Main where
import Data.Array as Array
null = ...Here, the name null would ordinarily conflict with null from Data.Array, but the qualified import solves this problem. Data.Array.null can be referenced using Array.null instead.
Module exports can be restricted to a set of names by providing that set in parentheses in the module declaration::
module A (runFoo, Foo(..)) whereThe types of names which can be exported is the same as for module imports.
Currently, individual imported values and types cannot be re-exported by a module. However, as of PureScript release 0.7, imported modules can be re-exported in their entirety:
module A (module B) where
import BWhen re-exporting other modules, all local values and types can also be exported by specifying the module itself as an export:
module A (module A, module B) where
import B
data ...The foreign import keywords declare a value which is defined in Javascript, and its type::
foreign import pow :: Number -> Number -> NumberTo declare a new abstract type (with no constructors), use foreign import data and provide the kind::
foreign import data DOM :: *
foreign import document :: {
createElement :: String -> DOM
}Record literals are surrounded by braces, as in JavaScript:
author :: { name :: String, interests :: Array String }
author =
{ name: "Phil"
, interests: ["Functional Programming", "JavaScript"]
}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"]{ ... } is just syntactic sugar for the Object type constructor, so { language :: String } is the same as Object ( language :: String ).
The Object type constructor is parameterized by a row of types. In kind notation, Object has kind # * -> *. That is, it takes a row of types to a type.
( language :: String ) denotes a row of types (something of kind # *), so it can be passed to Object to construct a type, namely Object ( language :: String ).
It is possible to define an extensible record
type Lang l = { language :: String | l }that can then be extended like:
type Language = Lang ( country :: String )The Language type synonym would then be equivalent to { language :: String, country :: String }.
Record literals with wildcards can be used to create a function that produces the record instead:
{ foo: _, bar: _ }is equivalent to:
\foo bar -> { foo: foo, bar: bar }PureScript also provides a record update syntax similar to Haskell's:
setX :: Number -> Point -> Point
setX val point = point { x = val }