Skip to content
This repository has been archived by the owner on Feb 16, 2021. It is now read-only.

Flow vs PureScript, aka how to write some PureScript idioms with JavaScript + type annotations #71

Open
gcanti opened this issue Jul 19, 2016 · 0 comments

Comments

@gcanti
Copy link
Owner

gcanti commented Jul 19, 2016

Note. The term "FlowScript" here means JavaScript + Flow type annotations (+ PureScript idioms).

Functions

A basic example

PureScript

add :: Int -> Int -> Int
add x y = x + y

add 10 20

In FlowScript all functions must be curried

const add: (_: number) => (_: number) => number =
  x => y => x + y

add(10)(20)

Let's introduce some helper types in order to avoid such a boilerplate

// FunctionN, where N = function arity
type Function1<A, B> = (_: A) => B;
type Function2<A, B, C> = (_: A) => (_: B) => C;
type Function3<A, B, C, D> = (_: A) => (_: B) => (_: C) => D;

Now add is more readable:

const add: Function2<number, number, number> =
  x => y => x + y

A function with universally quantified type

PureScript

flip :: forall a b c. (a -> b -> c) -> b -> a -> c
flip f y x = f x y

In FlowScript, as a convention, every type parameter is uppercase

export function flip<A, B, C>(f: Function2<A, B, C>): Function2<B, A, C> {
  return y => x => f(x)(y)
}

const f: Function2<number, string, number> = n => s => s.length + n

let..in

PureScript

example :: Number -> Number -> Number -> Number
example x y z =
  let foo = x * y in
  let bar = y * z in
  foo + bar

in FlowScript lets are translated to consts

export const example: Function3<number, number, number, number> =
x => y => z => {
  const foo = x * y
  const bar = y * z
  return foo + bar
}

Data structures

type

PureScript

type Address =
  { street :: String
  , city   :: String
  , state  :: String
  }

type Entry =
  { firstName :: String
  , lastName  :: String
  , address   :: Address
  }

It's the same in FlowScript

type Address = {
  street: string,
  city: string,
  state: string
};

type Entry = {
  firstName: string,
  lastName: string,
  address: Address
};

data

data Maybe a = Nothing | Just a

x :: Maybe Bool
x = Just false

y :: Maybe Int
y = Just 1

FlowScript

export type Maybe<A> = { type: 'Nothing' } | { type: 'Just', value: A };

const x: Maybe<boolean> = { type: 'Just', value: false } // boilerplate

const y: Maybe<number> = { type: 'Just', value: 1 } // boilerplate

Again, let's introduce some helpers

// Maybe helpers, aka type constructors
export function Nothing(): Maybe<*> {
  return { type: 'Nothing' }
}

export function Just<A>(value: A): Maybe<A> {
  return { type: 'Just', value }
}

or even better

export function Nothing(): Maybe<*> {
  return Nothing.value
}

Nothing.value = { type: 'Nothing' }

export function Just<A>(value: A): Maybe<A> {
  return { type: 'Just', value }
}

Now building some Maybes is more handy:

const x: Maybe<boolean> = Just(false)

const y: Maybe<number> = Just(1)

Another example.

PureScript

type Point = { x :: Number, y :: Number }

data Shape
  = Circle Point Number
  | Rectangle Point Number Number
  | Line Point Point
  | Text Point String

FlowScript

export type Point = {
  x: number,
  y: number
};

export type Shape
  = { type: 'Circle', center: Point, radius: number }
  | { type: 'Rectangle', position: Point, height: number, width: number }
  | { type: 'Line', start: Point, end: Point }
  | { type: 'Text', position: Point, label: string };

newtype

No equivalent :(

Recursive data structures

PureScript

data List a = Nil | Cons a (List a)

FlowScript

type List<A> = { type: 'Nil' } | { type: 'Cons', head: A, tail: List<A> };

Type classes

PureScript

class Show a where
  show :: a -> String

FlowScript

export type Show<A> = {
  show: Function1<A, string>;
};

export function show<A>(dictShow: Show<A>): Function1<A, string> {
  return dictShow.show
}

Instances

PureScript

instance showBoolean :: Show Boolean where
  show true = "true"
  show false = "false"

instance showMaybe :: (Show a) => Show (Maybe a) where
  show Nothing = "Nothing"
  show (Just x) = "Just(" <> show x <> ")"

FlowScript

export const showBoolean: Show<boolean> = {
  show(x) {
    return x ? 'true' : 'false'
  }
}

export function showMaybe<A>(dictShow: Show<A>): Show<Maybe<A>> {
  return {
    show(x) {
      switch (x.type) {
        case 'Nothing' :
          return 'Nothing'
        case 'Just' :
          return 'Just(' + show(dictShow)(x.value) + ')'
      }
      throw new Error("Failed pattern match")
    }
  }
}

note how showMaybe takes an additional dictShow argument because of the (Show a) => contraint.

Using show

PureScript

show ( Just false ) -- "Just(false)"

FlowScript (a bit verbose...)

show(showMaybe(showBoolean))(Just(false)) // "Just(false)"
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

1 participant