• Stars
    star
    132
  • Rank 274,205 (Top 6 %)
  • Language PureScript
  • License
    MIT License
  • Created over 7 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Polymorphic variants for PureScript

purescript-variant

Latest release Build status

Polymorphic variants for PureScript.

Install

bower install purescript-variant

Documentation

Data.Variant is an implementation of polymorphic variants in PureScript. What are polymorphic variants? Before we get to that, lets look at the dual, which you are likely familiar with if you've been using PureScript: records.

Another name for records might be polymorphic products. A product is simply data that holds inhabitants for more than one type at a time, Tuple a b being the canonical product.

data Tuple a b = Tuple a b

If I have a Tuple Int String, then I have available some Int value paired with a String value (or Int * String, thus a product). For convenience, we often like to use records, especially for models in our shiny web apps.

type User =
  { name :: String
  , age :: Int
  , email :: Email
  }

And maybe use it like so:

addJrSuffix :: User -> User
addJrSuffix user = user { name = user.name <> ", Jr." }

However this type signature is needlessly specific. In fact, all it cares about is the name field. We can express this sort of structural typing in PureScript via row types:

addJrSuffix :: forall r. { name :: String | r } -> { name :: String | r }
addJrSuffix hasName = hasName { name = hasName.name <> ", Jr." }

Now I can pass in anything that merely has a name :: String.

addJrSuffix { name: "Bob" }
addJrSuffix { age: 42, name: "Gerald" }

So records, or polymorphic products, let us pass in anything as long as it has the structure we specify, and even get the same structure back after we are done with it.

Let's flip back around to sum types (or variants), Either a b being the canonical dual of Tuple a b.

data Either a b = Left a | Right b

Where Tuple a b says we have an a paired with a b, Either a b says we have either an a or a b via the Left and Right constructors. We'd handle the possibilities by pattern matching on it with case.

This library just uses the same structural row system that we use with records (products) and applies them to variants (sums). Voila!

We lift values into Variant with inj by specifying a tag.

import Type.Proxy (Proxy(..))

someFoo :: forall v. Variant (foo :: Int | v)
someFoo = inj (Proxy :: Proxy "foo") 42

Proxy is just a way to tell the compiler what our tag is at the type level. I can stamp out a bunch of these with different labels:

someFoo :: forall v. Variant (foo :: Int | v)
someFoo = inj (Proxy :: Proxy "foo") 42

someBar :: forall v. Variant (bar :: Boolean | v)
someBar = inj (Proxy :: Proxy "bar") true

someBaz :: forall v. Variant (baz :: String | v)
someBaz = inj (Proxy :: Proxy "baz") "Baz"

We can try to extract a value from this via on, which takes a function to handle the inner value in case of success, and a function to handle the rest in case of failure.

fooToString :: forall v. Variant (foo :: Int | v) -> String
fooToString = on (Proxy :: Proxy "foo") show (\_ -> "not foo")

fooToString someFoo == "42"
fooToString someBar == "not foo"

We can chain usages of on and terminate it with case_ (for compiler-checked exhaustivity) or default (to provide a default value in case of failure).

_foo = Proxy :: Proxy "foo"
_bar = Proxy :: Proxy "bar"
_baz = Proxy :: Proxy "baz"

allToString :: Variant (foo :: Int, bar :: Boolean, baz :: String) -> String
allToString =
  case_
    # on _foo show
    # on _bar (if _ then "true" else "false")
    # on _baz (\str -> str)

someToString :: forall v. Variant (foo :: Int, bar :: Boolean | v) -> String
someToString =
  default "unknown"
    # on _foo show
    # on _bar (if _ then "true" else "false")

allToString someBaz == "Baz"
someToString someBaz == "unknown"

Handlers with on are also compositional! We can compose them together with function composition and reuse them in different contexts.

onFooOrBar :: forall v. (Variant v -> String) -> Variant (foo :: Int, bar :: Boolean | v) -> String
onFooOrBar = on _foo show >>> on _bar (if _ then "true" else "false")

allToString :: Variant (foo :: Int, bar :: Boolean, baz :: String) -> String
allToString =
  case_
    # onFooOrBar
    # on _baz (\str -> str)

Instead of chaining with just on, there is onMatch which adds record sugar.

onFooOrBar :: forall v. (Variant v -> String) -> Variant (foo :: Int, bar :: Boolean | v) -> String
onFooOrBar = onMatch
  { foo: show :: Int -> String
  , bar: if _ then "true" else "false"
  }

But note that polymorphic functions like show or id need to be either annotated or eta expanded due to record impredicativity.

onMatch can be used with case_ and default just like on, but there is also match for the common case of total matching.

allToString :: Variant (foo :: Int, bar :: Boolean, baz :: String) -> String
allToString = match
  { foo: \a -> show a
  , bar: \a -> if a then "true" else "false"
  , baz: \a -> a
  }

We can combine polymorphic variants with Functors as well using VariantF, which lives in Data.Functor.Variant. VariantF is just like Variant, except it's indexed by things of kind Type -> Type.

someFoo :: forall v. VariantF (foo :: Maybe | v) Int
someFoo = inj (Proxy :: Proxy "foo") (Just 42)

someBar :: forall v. VariantF (bar :: Tuple String | v) Int
someBar = inj (Proxy :: Proxy "bar") (Tuple "bar" 42)

someBaz :: forall v a. VariantF (baz :: Either String | v) a
someBaz = inj (Proxy :: Proxy "baz") (Left "Baz")

VariantF supports all the same combinators as Variant.

More Repositories

1

matches.js

Powerful pattern matching for Javascript
JavaScript
774
star
2

sparkler

Native pattern matching for JavaScript
JavaScript
696
star
3

adt.js

Algebraic data types for Javascript
JavaScript
220
star
4

purescript-run

An extensible-effects implementation
PureScript
158
star
5

purescript-spork

Elm-like for PureScript
PureScript
157
star
6

adt-simple

Algebraic data types for JavaScript using Sweet.js macros
JavaScript
94
star
7

purescript-tidy

A syntax tidy-upper for PureScript.
PureScript
92
star
8

purescript-psa

Error/Warning reporting frontend for the PureScript compiler
PureScript
88
star
9

purescript-routing-duplex

Unified parsing and printing for routes in PureScript
PureScript
86
star
10

lambda-chop

Sweet.js macros for lambdas with currying, bound functions, and placeholders.
JavaScript
83
star
11

purescript-checked-exceptions

Extensible checked exceptions with polymorphic variants
PureScript
80
star
12

example-functional-compiler

PureScript
59
star
13

purescript-heterogeneous

Maps and folds for heterogeneous data types.
PureScript
55
star
14

purescript-language-cst-parser

PureScript CST Parser written in PureScript
PureScript
49
star
15

tailrec.js

Dead simple auto-trampolining for Javascript
JavaScript
47
star
16

purescript-typelevel-eval

Higher order functional programming in PureScript's type system
PureScript
43
star
17

purescript-cst

A concrete-syntax tree and parser for the PureScript language
Haskell
40
star
18

purescript-convertable-options

Highly-overloaded APIs for PureScript
PureScript
35
star
19

purescript-call-by-name

Syntactically light-weight call-by-name arguments in PureScript. No guarantees. Completely gratuitous.
PureScript
30
star
20

purescript-dodo-printer

An adequate printer.
PureScript
30
star
21

purescript-tidy-codegen

Convenient codegen for PureScript
PureScript
28
star
22

grunt-sweet.js

Grunt task for Sweet.js
JavaScript
27
star
23

purescript-argparse-basic

A no frills CLI argument parser for PureScript.
PureScript
20
star
24

purescript-optimizer

Haskell
20
star
25

purescript-node-workerbees

Convenient multi-threading on Node with PureScript.
PureScript
17
star
26

purescript-run-streaming

Streaming effects for PureScript
PureScript
15
star
27

polykinds

Experimental polykinds implementation
Haskell
14
star
28

purescript-node-glob-basic

A very basic glob library for PureScript.
PureScript
11
star
29

derelicte

An AltJS lang implemented entirely with Sweet.js macros
JavaScript
9
star
30

purescript-free-semigroupoid

Free semigroupoids for PureScript
PureScript
9
star
31

jsScrollbar

A highly customizable javascript scrollbar
JavaScript
9
star
32

purescript-higher-order

PureScript
8
star
33

talks

PureScript
8
star
34

purescript-halogen-connect-experiment

PureScript
5
star
35

purescript-halogen-startapp

PureScript
5
star
36

backbone.ext

Extensions for Backbone.js
JavaScript
3
star
37

purescript-psa-utils

Utility library for purescript-psa
PureScript
2
star