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.
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.
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.
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 bThere 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.
There is a native String type which is distinct from Array Char. A String consists of UTF-16 code units and may contain unpaired surrogates. Working with Unicode code points is supported by library functions. The set of supported escape sequences for string literals is different from both Haskell and JavaScript.
PureScript has a type Char which represents a UTF-16 code unit for compatibility with JavaScript. In contrast, the type Char in Haskell represents a Unicode code point.
PureScript has a type Unit used in place of Haskell's (). The Prelude module provides a value unit that inhabits this type.
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.
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.0 , y: 0.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.yWhen 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.
In PureScript, you can give names to instances:
instance arbitraryUnit :: Arbitrary Unit where
...Although instance names are optional, they can help the readability of compiled JavaScript. Overlapping instances are still disallowed, like in Haskell.
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.
Furthermore, using generics, it is also possible to use generic implementations for type classes like Bounded, Monoid, and Show. See the deriving guide for more information.
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.
At the moment, it is not possible to declare default member implementations for type classes. This may change in the future.
Many type class hierarchies are more granular than in Haskell. For example:
Categoryhas a superclassSemigroupoidwhich provides(<<<), and does not require an identity.Applicativehas a superclassApply, which provides(<*>)and does not require an implementation forpure.
In PureScript, number literals are not overloaded as in Haskell. That is, 1 is always an Int, and 1.0 is always a Number.
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. There is a shorthand a /\ b for Tuple a b in Data.Tuple.Nested.
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.
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.
PureScript does not provide special syntax for array comprehensions. Instead, use do-notation. The guard function from the Control.Alternative module in purescript-control can be used to filter results:
import Prelude (($), (*), (==), bind, pure)
import Data.Array ((..))
import Data.Tuple (Tuple(..))
import Control.Alternative (guard)
factors :: Int -> Array (Tuple Int Int)
factors n = do
a <- 1 .. n
b <- 1 .. a
guard $ a * b == n
pure $ Tuple a bGHC 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 ...)
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 $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)In Haskell, functions can have fixity declarations. For example in base:
infix 4 `elem`, `notElem`
infixl 7 `quot`, `rem`, `div`, `mod`However fixity declarations are only supported while declaring operator aliases for functions in PureScript. Functions are always left associative and have the highest precedence when used as infix operators, which means that:
x * y `mod` z
parses as:
((x * y) `mod` z)in Haskell, but as:
(x * (y `mod` z))in PureScript, and those expressions may yield different values:
> ((2 * 3) `mod` 5)
1> (2 * (3 `mod` 5))
6The 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:
- ApplicativeDo
- BlockArguments
- DataKinds (see note below)
- DeriveFoldable
- DeriveFunctor
- DeriveGeneric
- DeriveTraversable
- EmptyDataDecls
- ExplicitForAll
- FlexibleContexts
- FlexibleInstances
- FunctionalDependencies
- GeneralisedNewtypeDeriving
- InstanceSigs
- KindSignatures
- LambdaCase
- LiberalTypeSynonyms
- MonoLocalBinds
- MultiParamTypeClasses
- NamedFieldPuns
- NoImplicitPrelude
- NumericUnderscores
- PartialTypeSignatures
- PolyKinds
- RankNTypes
- RebindableSyntax
- RoleAnnotations
- ScopedTypeVariables
- StandaloneDeriving
- StandaloneKindSignatures
- TypeOperators
- TypeSynonymInstances
- UndecidableInstances
- UndecidableSuperClasses
- UnicodeSyntax
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
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 purescript/purescript-prelude#44.
Although note that these might have different behaviour to the Haskell versions due to PureScript's strictness.
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.
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. - Haskell's
<&>operator (fromData.Functor) is equivalent to Purescript's<#>(operator alias tomapFlipped). 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]. There's a difference, though: in Haskell[2..1]is an empty list, whereas in PureScript2..1is[2,1]