The syntax between Elm and Purescript are mostly the same, to the point where most Elm code can be converted to Purescript with copy-pasting and find-replace/multi-cursor editing.
Many people who have used Elm who are curious about/have tried/are using Purescript have asked me to put together a "differences from Elm" post to help them, and which I would also have found helpful.
Type annotations are written using ::
(as :
is used for constructing list nodes).
a :: Int
a = 1
The type
keyword itself is used to define type aliases. You should be careful to use meaningful type aliases when writing your programs.
type Url = String
type Result a = Either Error a
The way "type" is used in Elm is used with data type declarations. There are two such cases:
As explained below, this is most often used when you have various sums and products you need to express, like so:
data Fruit
= Apple
| Banana
| Frikandel
data Coords = Coords Int Int
This is a special type of data type that has a single argument. These are especially useful when dealing with raw data, where you can write a validation function and not expose the constructor in exports.
module Url
( Url
, validateUrl
) where
newtype Url = ValidatedUrl String
validateUrl :: String -> Either Error ValidatedUrl
validateUrl = ???
There are many useful methods available for working with newtypes using the Newtype package.
I have a video I've made about this on Egghead that might be helpful to understand this.
They are the same, but libraries will have pipe for comments exported to documentation:
-- hello
{-
world
-}
-- | documentation comment
The type of booleans is Boolean
, with true
and false
being the actual values used, which are represented as the actual Javascript booleans in the runtime.
true :: Boolean
false :: Boolean
Normally one uses Int
for integers, and they are represented as any number literal. Javascript numbers are also available.
12 :: Int
123.123 :: Number -- note: this is a floating point JS number
Strings can be represented in the same way as in Elm.
"hello"
"""
multiline
"""
Because normal lists do not have a Javascript representation, arrays are used with square brackets in Purescript, and are represented in Javascript as arrays.
[1,2,3] :: Array -- an actual Javascript array
For normal lists, there is a Lists package with both strict and lazy lists.
These are the same as with Elm.
if condition
then one
else two
For many types, you may be interested in using the Control library, which provides methods such as when
.
While most languages and literature call these "Algebraic Data Types (ADT) of sums and products", it is easier to know of them as the two:
These may also be called Tagged union, variant, discriminated/disjoint union, or sum types. These represent a A + B
relationship of type constructors of a data type. For example:
data Fruit -- Fruit is the data type
= Apple -- Apple is a type constructor
| Banana -- Banana is a type constructor
These are most often known as a Product type. These represent a A * B
relationship between a type constructor and its arguments.
data MyBurger = Burger Bread Patty -- Burger is the type constructor, with Bread and Patty being its arguments
These two are then put together to form data types you will want to work with:
data Breakfast
= Toast
| Cereal Milk
Union types, usually known as untagged unions, are available in some languages and can be very useful. For example, Typescript comes with union types to provide you ways of working with various types that can be given to a function, as with Flowtype. You may soon find that you almost never need these though, as Elm has likely already shown.
Records in Purescript are also similar to Elm in usage.
a = { x: 3, y: 4}
type MyRecordAlias =
{ x :: Int
, y :: Int
}
These are the same as in Elm, but also allow for matching on patterns and using guards. Exhaustivity of patterns is checked by the compiler.
addTwo x = x + 2
go (Finished x) = x
go (Continue x) = -- ...
greaterThanTwo x
| x' > 2 = true
| otherwise = false
Operators are defined only as aliases for normal functions. There is also no restriction on what operators may be defined, as all Purescript modules (including the normal Prelude) are normal libraries.
applyFlipped :: forall a b. a -> (a -> b) -> b
applyFlipped x f = f x
infixr 0 apply as $
Functions can also be infixed in Purescript and can be useful.
Pattern "ppl" `indexOf` "Apple"
In Elm, function application is largely done using a left-to-right "pipe" operator. Many users prefer to use right-to-left operators, but any are suitable for use.
Elm | Purescript |
---|---|
|> |
# |
<| |
$ |
>> |
>>> |
<< |
<<< |
The same expressions are in Purescript, but many users will prefer using where
instead to put the binding at the end, or use do
notation with let
bindings.
fn1 =
let
a = 2
in
a + 2
fn2 =
a + 2
where
a = 2
fn3 = do
let a = 2
a + 2
Tuples are available through the Tuple library, as they do not have a Javascript representation and do not require a special library.
a = Tuple 1 2 -- using purescript-tuples
Modules are mostly similar but look a bit different:
module MyModule
( exported
) where
import Prelude -- only one top-level import is recommended
import Data.Array as A
import Data.List (head)
import Data.Map hiding (singleton)
Pursuit and Type holes with Type-Directed Search will be your best friends in finding what you need when writing Purescript, and will provide support that you don't normally see in many languages.
Pursuit is a search engine for published packages in the Purescript ecosystem, and provides search-by-name and search-by-type-signature. For example, you can search for encodeURI, and you can also search for (a -> b) -> f a -> f b
.
In the right-hand side of a =
for a definition, you can use type holes with any identifier prefixed with a question mark, like ?abc
. If a type matches this hole, a suggestion will be given by the compiler. For example:
a :: Array Int
a = ?whatGoesHere ((+) 1) [1,2,3]
Will give you a suggestion in this form:
Hole 'whatGoesHere' has the inferred type
(Int -> Int) -> Array Int -> Array Int
You could substitute the hole with one of these values:
Data.Functor.map :: forall a b f. Functor f => (a -> b) -> f a -> f b
As the IDE server for Purescript ships with the compiler, there is no need to worry about having your own IDE tools be out of date. There are many editor integrations for this server:
Type classes are a way to define methods that may be defined for a given type, which can be instantiated to the type. In this way, the types are like objects of a class, and can be found statically.
One example would be look at the Eq type class for defining equality of two elements of a type:
class Eq a where
eq :: a -> a -> Boolean
You can define an instance for this class for your own data type and use the eq
method readily:
data Coords = Coords Int Int
instance eqCoords :: Eq Coords where
eq (Coords x1 y1) (Coords x2 y2) = x1 == x2 && y1 == y2
a = eq (Coords 1 1) (Coords 1 1) -- true
b = eq (Coords 1 2) (Coords 3 4) -- false
Some of the basic type classes can be derived automatically, so writing that definition of Eq Coords
was actually unnecessary! Read more about this in this post.
You can read more about this in Purescript by Example.
That said, you don't have to use many type classes to start writing Purescript. You can get writing by mostly exploiting type holes and Type-Directed Search for them as written here.
In Elm, you might be used to writing decoders and encoders yourself and using them where necessary.
In Purescript, we actually use the record type definition directly to parse any record type alias easily. See my library simple-json and my talk about RowToList for more information.
Purescript by Example is a complete guide to Purescript if you want to learn more about the language, and the community is available through a number of media through FPChat.com Slack, Twitter #purescript, Github issues, and others.
I've also written many directions on PureScript-Resources if you're looking for quick answers on various topics.
You can also ping me on FPChat.com Slack in #purescript or on Twitter and I and many others will try to help you get things done.
- Why Bower?
Really, you don't need to use Bower at all. I've written about this here: https://purescript-resources.readthedocs.io/en/latest/intro.html#how-do-i-manage-my-dependencies-ew-do-i-have-to-use-bower
- Why do I have to read X from Haskell?
Inevitably, all packages in all communities, especially smaller ones like Purescript and Elm, suffer from not being completely fleshed out in documentation, technical manuals, etc. The benefit of Purescript is that because you are exposed to many of the common terminology and patterns effectively used in the Haskell world, you can readily use documentation and blog posts that have been written for similar Haskell libraries/features/techniques to Purescript ones.
You may even become a Haskell user over time, as was my case in starting to use Haskell after using Purescript.
As the joke goes though, "Haskell is an incubator for Purescript ideas".