Evaluation strategy
Unlike Haskell, PureScript is strictly evaluated.
As the evaluation strategy matches JavaScript, interoperability with existing code is trivial - a function exported from a PureScript module behaves exactly like any "normal" JavaScript function, and correspondingly, calling JavaScript via the FFI is simple too.
Keeping strict evaluation also means there is no need for a runtime system or overly complicated JavaScript output. It should also be possible to write higher performance code when needed, as introducing laziness on top of JavaScript comes with an unavoidable overhead.
Prelude/base
There is no implicit Prelude import in PureScript, the Prelude module is just like any other. Also, no libraries are distributed with the compiler at all.
The generally accepted "standard" Prelude is the purescript-prelude library.
Module Imports / Exports
Type classes in modules must be specifically imported using the class keyword.
module B where
import A (class Fab)There is no qualified keyword in PureScript. Writing import Data.List as List has the same effect as writing import qualified Data.List as List in Haskell.
Module imports and exports are fully documented on the Modules page.
Types
Explicit forall
Polymorphic functions in PureScript require an explicit forall to declare type variables before using them. For example, Haskell's list length function is declared like this:
length :: [a] -> IntIn PureScript this will fail with the error Type variable a is undefined. The PureScript equivalent is:
length :: forall a. Array a -> IntA forall can declare multiple type variables at once, and should appear before typeclass constraints:
ap :: forall m a b. (Monad m) => m (a -> b) -> m a -> m bNumbers
There is a native Number type which represents JavaScript's standard IEEE 754 float and an Int which is restricted to the range of 32-bit integers. In JavaScript, the Int values and operations are generated with a |0 postfix to achieve this, e.g. if you have variables x, y, and z of type Int, then the PureScript expression (x + y) * z would compile to ((x + y)|0 * z)|0.
Unit
PureScript has a type Unit used in place of Haskell's (). The Prelude module provides a value unit that inhabits this type.
[a]
PureScript does not provide syntactic sugar for list types. Construct list types using List from Data.List.
There is also an Array type for native JavaScript arrays, but this does not have the same performance characteristics as List. Array values can be constructed with [x, y, z] literals, but the type still needs to be annotated as Array a.
Records
PureScript can encode JavaScript-style objects directly by using row types, so Haskell-style record definitions actually have quite a different meaning in PureScript:
data Point = Point { x :: Number, y :: Number }In Haskell a definition like this would introduce several things to the current environment:
Point :: Number -> Number -> Point
x :: Point -> Number
y :: Point -> NumberHowever in PureScript this only introduces a Point constructor that accepts an object type. In fact, often we might not need a data constructor at all when using object types:
type PointRec = { x :: Number, y :: Number }Objects are constructed with syntax similar to that of JavaScript (and the type definition):
origin :: PointRec
origin = { x: 0, y: 0 }And instead of introducing x and y accessor functions, x and y can be read like JavaScript properties:
originX :: Number
originX = origin.xPureScript also provides a record update syntax similar to Haskell's:
setX :: Number -> PointRec -> PointRec
setX val point = point { x = val }A common mistake to look out for is when writing a function that accepts a data type like the original Point above—the object is still wrapped inside Point, so something like this will fail:
showPoint :: Point -> String
showPoint p = show p.x <> ", " <> show p.yInstead, we need to destructure Point to get at the object:
showPoint :: Point -> String
showPoint (Point obj) = show obj.x <> ", " <> show obj.yType classes
Arrow direction
When declaring a type class with a superclass, the arrow is the other way around. For example:
class (Eq a) <= Ord a where
...This is so that => can always be read as logical implication; in the above case, an Ord a instance implies an Eq a instance.
Named instances
In PureScript, instances must be given names:
instance arbitraryUnit :: Arbitrary Unit where
...Overlapping instances are still disallowed, like in Haskell. The instance names are used to help the readability of compiled JavaScript.
Deriving
Unlike Haskell, PureScript doesn't have deriving functionality when declaring data types. For example, the following code does not work in PureScript:
data Foo = Foo Int String deriving (Eq, Ord)However, PureScript does have StandaloneDeriving-type functionality:
data Foo = Foo Int String
derive instance eqFoo :: Eq Foo
derive instance ordFoo :: Ord FooExamples of type classes that can be derived this way include Eq, Functor,
and Ord. See
here
for a list of other type classes.
Using generics, it is also possible to use generic implementations for type
classes like Bounded, Monoid, and Show. See
here
for a list of other type classes that have generic implementations, as well as
an explanation of how to write generic implementations for your own type
classes.
Orphan Instances
Unlike Haskell, orphan instances are completely disallowed in PureScript. It is a compiler error to try to declare orphan instances.
When instances cannot be declared in the same module, one way to work around it is to use newtype wrappers.
Default members
At the moment, it is not possible to declare default member implementations for type classes. This may change in the future.
Type class hierarchies
Many type class hierarchies are more granular than in Haskell. For example:
Categoryhas a superclassSemigroupoidwhich provides(<<<), and does not require an identity.Monoidhas a superclassSemigroup, which provides(<>), and does not require an identity.Applicativehas a superclassApply, which provides(<*>)and does not require an implementation forpure.
Tuples
PureScript has no special syntax for tuples as records can fulfill the same role that n-tuples do with the advantage of having more meaningful types and accessors.
A Tuple type for 2-tuples is available via the purescript-tuples library. Tuple is treated the same as any other type or data constructor.
Composition operator
PureScript uses <<< rather than . for right-to-left composition of functions. This is to avoid a syntactic ambiguity with . being used for property access and name qualification. There is also a corresponding >>> operator for left-to-right composition.
The <<< operator is actually a more general morphism composition operator that applies to semigroupoids and categories, and the Prelude module provides a Semigroupoid instance for the -> type, which gives us function composition.
return
In the past, PureScript used return. However, it is now removed and replaced with pure. It was always an alias for pure, which means this change was implemented by simply removing the alias.
Array Comprehensions
PureScript does not provide special syntax for array comprehensions. Instead, use do-notation. The guard function from the Control.MonadPlus module in purescript-control can be used to filter results:
import Prelude (($), (*), (==), bind, pure)
import Data.Array ((..))
import Data.Tuple (Tuple(..))
import Control.MonadZero (guard)
factors :: Int -> Array (Tuple Int Int)
factors n = do
a <- 1 .. n
b <- 1 .. a
guard $ a * b == n
pure $ Tuple a bNo special treatment of $
GHC provides a special typing rule for the $ operator, so that the following natural application to the rank-2 runST function is well-typed:
runST $ do
...PureScript does not provide this rule, so it is necessary to either
- omit the operator:
runST do ... - or use parentheses instead:
runST (do ...)
Defining Operators
In Haskell, it is possible to define an operator with the following natural syntax:
f $ x = f xIn PureScript, you provide an operator alias for a named function. Defining functions using operators is removed since version 0.9.
apply f x = f x
infixr 0 apply as $Operator Sections
In Haskell, there is syntactic sugar to partially apply infix operators.
(2 ^) -- desugars to `(^) 2`, or `\x -> 2 ^ x`
(^ 2) -- desugars to `flip (^) 2`, or `\x -> x ^ 2`In PureScript, operator sections look a little bit different.
(2 ^ _)
(_ ^ 2)Extensions
The PureScript compiler does not support GHC-like language extensions. However, there are some "built-in" language features that are equivalent (or at least similar) to a number of GHC extensions. These currently are:
- DataKinds (see note below)
- EmptyDataDecls
- ExplicitForAll
- FlexibleContexts
- FlexibleInstances
- FunctionalDependencies
- KindSignatures
- MultiParamTypeClasses
- PartialTypeSignatures
- RankNTypes
- RebindableSyntax
- ScopedTypeVariables
Note on DataKinds: Unlike in Haskell, user-defined kinds are open, and they are not promoted, which means that their constructors can only be used in types, and not in values. For more information about the kind system, see https://github.com/purescript/documentation/blob/master/language/Types.md#kind-system
error and undefined
For error, you can use Effect.Exception.Unsafe.unsafeThrow, in the purescript-exceptions package.
undefined can be emulated with Unsafe.Coerce.unsafeCoerce unit :: forall a. a, which is in the purescript-unsafe-coerce package. See also https://github.com/purescript/purescript-prelude/issues/44.
Although note that these might have different behaviour to the Haskell versions due to PureScript's strictness.
Documentation comments
When writing documentation, the pipe character | must appear at the start of every comment line, not just the first. See the documentation for doc-comments for more details.
Where is ... from Haskell?
As PureScript has not inherited Haskell's legacy code, some operators and functions that are common in Haskell have different names in PureScript:
(>>)is(*>), asApplyis a superclass ofMonadso there is no need to have anMonad-specialised version.- Since 0.9.1, the
Preludelibrary does not contain(++)as a second alias forappend/(<>)(mappendin Haskell) anymore. mapMistraverse, as this is a more general form that applies to any traversable structure, not just lists. Also it only requiresApplicativerather thanMonad. Similarly,liftMismap.- Many functions that are part of
Data.Listin Haskell are provided in a more generic form inData.FoldableorData.Traversable. someandmanyare defined with the type of list they operate on (Data.ArrayorData.List).- Instead of
_foofor typed holes, use?foo. You have to name the hole;?is not allowed. - Ranges are written as
1..2rather than[1..2]