Tutorial: Learn PureScript from Scratch (2026)
PureScript is a strongly typed, pure functional language that compiles to JavaScript. I have used it for frontend applications with Halogen, backend services, and even Node.js scripts. The type system is similar to Haskell's: higher-kinded types, type classes, and algebraic data types provide compile-time guarantees that are rare in JavaScript land.
What sets PureScript apart from Haskell is its pragmatism: strict evaluation, JavaScript interop, and a simpler type class system. It brings Haskell-like safety to real-world JavaScript applications.
Type Classes
Type classes enable ad-hoc polymorphism. Show provides string conversion, Eq provides equality, Ord provides ordering, Functor provides mapping. Type class instances are explicit with instance. The compiler can derive instances automatically for many classes. Type classes constrain type variables in function signatures.
class Show a where
show :: a -> String
instance Show Int where
show x = showInt x
instance Show a => Show (Maybe a) where
show Nothing = "Nothing"
show (Just x) = "(Just " <> show x <> ")"
derive instance eqPoint :: Eq Point
derive instance ordPoint :: Ord Point
printShow :: forall a. Show a => a -> Effect Unit
printShow x = log (show x)
Monads
Monads structure effectful computations. Effect handles synchronous effects (console, random). Aff handles asynchronous effects (HTTP, filesystem). Maybe models optional values. Either models error handling. do notation sequences monadic computations. map transforms values inside a functor; bind chains monadic operations.
module Main where
import Prelude
import Effect (Effect)
import Effect.Console (log)
import Data.Maybe (Maybe(..))
import Data.Either (Either(..))
safeDivide :: Int -> Int -> Maybe Int
safeDivide _ 0 = Nothing
safeDivide x y = Just (x / y)
process :: Int -> Int -> Effect Unit
process x y = do
case safeDivide x y of
Just result -> log (show result)
Nothing -> log "division by zero"
main :: Effect Unit
main = process 10 2
Algebraic Data Types
PureScript's ADTs model complex domains precisely. data defines sum types with constructors. Product types use records with named fields. Pattern matching destructures ADTs exhaustively — the compiler warns about non-exhaustive patterns. Newtypes (newtype) wrap existing types with zero runtime cost, providing distinct compile-time identities.
data Shape
= Circle { radius :: Number }
| Rectangle { width :: Number, height :: Number }
| Triangle { a :: Number, b :: Number, c :: Number }
area :: Shape -> Number
area (Circle { radius: r }) = pi * r * r
area (Rectangle { width: w, height: h }) = w * h
area (Triangle { a, b, c }) = sqrt (s * (s - a) * (s - b) * (s - c))
where
s = (a + b + c) / 2.0
newtype Email = Email String
mkEmail :: String -> Maybe Email
mkEmail s =
if contains (Pattern "@") s then
Just (Email s)
else
Nothing
Records
Records in PureScript are structural and polymorphic. Row polymorphism lets functions operate on records with at least certain fields. Record update syntax record { key = value } creates a new record with modified fields. Wildcard update spreads remaining fields. Records can be destructured in patterns. The Type.Row module provides row manipulation.
type Person = { name :: String, age :: Int }
greet :: forall r. { name :: String | r } -> String
greet p = "Hello, " <> p.name
updateAge :: Person -> Person
updateAge p = p { age = p.age + 1 }
showPerson :: Person -> String
showPerson { name, age } = name <> " is " <> show age <> " years old"
person1 = { name: "Alice", age: 30, email: "a@b.com" }
-- Row polymorphic function works with extra fields
result = greet person1
-- Destructuring with defaults
getName :: forall r. { name :: String | r } -> String
getName { name: n } = n
Foreign Function Interface
FFI in PureScript lets you call JavaScript code with type safety. Declare foreign functions with foreign import. Provide the JavaScript implementation in a companion .js file. Types must match the runtime behavior. Effect types track side effects. The foreign module system ensures type-safe integration with existing JavaScript libraries.
// JavaScript implementation (Effect.js)
exports.logAndReturn = function (msg) {
return function () {
console.log(msg);
return msg;
};
};
exports.randomInt = function (min) {
return function (max) {
return function () {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
};
};
Halogen (UI Framework)
Halogen is a type-safe UI library for PureScript based on the component model. Components manage state, handle queries, and render HTML. The HTML DSL is type-checked — you cannot generate invalid HTML. Halogen uses a virtual DOM for efficient updates. The architecture separates state management from rendering, making complex UIs maintainable.
module Main where
import Prelude
import Halogen as H
import Halogen.HTML as HH
import Halogen.HTML.Events as HE
import Halogen.HTML.Properties as HP
component :: forall q i o m. H.Component q i o m
component =
H.mkComponent
{ initialState: const 0
, render
, eval: H.mkEval H.defaultEval { handleAction = handleAction }
}
data Action = Increment | Decrement
handleAction :: forall m. Action -> H.HalogenM Int Action () o m Unit
handleAction Increment = H.modify_ \s -> s + 1
handleAction Decrement = H.modify_ \s -> s - 1
render :: forall m. Int -> H.ComponentHTML Action () m
render state =
HH.div_
[ HH.button [ HE.onClick \_ -> Decrement ] [ HH.text "-" ]
, HH.text (show state)
, HH.button [ HE.onClick \_ -> Increment ] [ HH.text "+" ]
]
Frequently Asked Questions
PureScript vs Haskell?
PureScript has strict evaluation (better perf predictability), simpler type classes (no MPTCs/fundeps), and compiles to JS. Haskell has laziness, GADTs, type families, and a richer type system.
What is row polymorphism?
Rows describe record types with unknown fields. Functions with row-polymorphic types can operate on records with extra fields without losing type information about those fields.
How does PureScript handle side effects?
Effect tracks synchronous I/O, Aff tracks async I/O. Both are monads. Pure functions are deterministic — all side effects are explicit in the type.
What is the difference between Maybe and Either?
Maybe represents optional values (Just/Nothing). Either represents success/failure (Left/Right). Either carries error information; Maybe only signals absence.
Originally published on Ayodhyyya. Last updated June 1, 2026.