forml-0.1.1: src/forml/prelude.forml
-- Prelude
-- -------
-- If you are familiar with Haskell or ML, the prelude may serve as a
-- good introduction to forml.
-- Forml's syntax is quite liberal; to illustrate, the prelude will
-- maintain an intentionally inconsistent style throughout.
-- First, we need to create a namespace with the `module` keyword.
module prelude
JS a = {} -> a
inline object? x = do! `typeof x === "object"`
inline array? = do! `is_array`
inline string? x = do! `typeof x === "string"`
inline
num? x = do! `typeof x == "number"`
inline type? x = do! `typeof x`
inline
not: Bool -> Bool
not x = do! `!x`
not false
object? {}
array? []
not (array? {})
not (object? 0)
-- Side effects can only happen in Javascript, so we use a monadic container to
-- compose these bits.
inline
(>>=): JS a -> (a -> JS b) -> JS b
(>>=) x y = y (run x)
inline
(>>): JS a -> JS b -> JS b
(>>) x y = `run x; return (run y)`
inline return:
a -> JS a
return x = `x`
do! x <- `1 + 4`
y <- `2 + 3`
ans <- `x + y`
return (10 == ans)
5 == do! return 5
10 == do! `5 + 5`
inline log: a -> JS {}
| x = `console.log(x)`
-- Testing to verify that escaping Javascript & utilizing escaped
-- Javascript with `do` sugar and composition in general works as
-- expected.
var x = 0
y = do z <- `x = 1`
return z
x == 0
-- Numbers
-- -------
-- Some basic aliases to native javascript infix functions. These are type
-- annotated to constrain inferrence - otherwise, these functions would all be
-- inferred as `a -> b -> c`.
inline (&&): Bool -> Bool -> Bool | x y = do! `x && y`
inline (||): Bool -> Bool -> Bool | x y = do! `x || y`
inline (*): Num -> Num -> Num | x y = do! `x * y`
inline (/): Num -> Num -> Num | x y = do! `x / y`
inline (%): Num -> Num -> Num | x y = do! `x % y`
inline (+): Num -> Num -> Num | x y = do! `x + y`
inline (-): Num -> Num -> Num | x y = do! `x - y`
inline (<=): Num -> Num -> Bool | x y = do! `x <= y`
inline (>=): Num -> Num -> Bool | x y = do! `x >= y`
inline (<): Num -> Num -> Bool | x y = do! `x < y`
inline (>): Num -> Num -> Bool | x y = do! `x > y`
-- Equality is overloaded to match records and arrays. By constraining the type of
-- this operator, we need only dispatch on the type of the first element,
-- constraining
inline x != y = not (x is y)
inline x /= y = not (x == y)
(==): a -> a -> Bool
| x y when do! `x === y` = true
| x y when object? x =
do! `var result = true;
for (key in x) {
result = result && y.hasOwnProperty(key) && _eq_eq(x[key])(y[key]);
};
var z = Object.keys(x).length
=== Object.keys(y).length;
result && z`
| x y when array? x =
do! `var result = true;
for (z in x) {
result = result && _eq_eq(x[z])(y[z]);
};
result && x.length == y.length`
| x y = false
-- And a few simple tests to verify the correctness of these
-- implementations. This is not meant to be exhaustive, only a smoke
-- test against regressions.
(3 * 4) + 5 * 4 == 64 / 2
4 - 1 != 5 - 10
(10 >= 5 + 5) != (4 + 5 <= 10 - 2)
({test: 1} == {test: 1}) == true
({test: 1} != {test: 1}) == false
-- Fibonacci function
fib 0 = 0 | 1 = 1 | n = fib(n - 1) + fib(n - 2)
-- Speed
-- -----
-- Due to the nested recursion, `fib` is an excellent function for testing the
-- runtime speed versus raw javascript. `fast_fib` is a trivial javascript
-- implementation of the same function, recursed itself to remove any potential
-- overhead from forml's dispatch mechanism.
module speedtest
private inline get_time = `new Date().getTime()`
time js =
do start <- get_time
js
stop <- get_time
return (stop - start)
private inline
fast_fib =
do! `var f = function(n) {
return 0 === n ? 0 : 1 === n ? 1 : f(n - 1) + f(n - 2)
}; f`
fast_fib 7 == fib 7
-- With this, we can set up a simple canary to let us know if the prelude is
-- suddenly dramatically slower than it previously was; in this case, we fail a test
-- if the forml version isn't at least 90% as fast as the native javascript
-- version.
floor: Num -> Num = do! `Math.floor`
do! fast_time <- time yield fast_fib 30
slow_time <- time yield fib 30
return (floor (fast_time / slow_time * 100) >= 80)
-- Function Combinators
-- --------------------
-- Simple left & right pipes, ala F#.
inline x <| y = x y
inline x |> y = y x
3 |> (λy = y + 1) |> λy = y + 1 == 5
(λy x z = x + y + z + 1) 1 <| 3 <| 4 == 9
-- Alternatively, there is a right associative version of `<|`, ala
-- haskell. All operators which end with a `:` are right associative.
inline x <: y = x y
(λx = x - 3) <: (λx = x - 3) <: 5 + 5 == 4
-- Function composition
inline x .: y = λz = x y(z)
inline id x = x
inline flip(f) x y = f(y, x)
inline x ' y = y x
((λx = x + 1) .: (λx = x * 2) .: λx = x - 3) 4 == 3
id [1, 2, 3] == [1, 2, 3]
flip (λx y = x - y) 3 5 == 2
-- Strings
-- -------
module string
inline
lstrip: String -> String
lstrip x = do! `x.replace(/^\s+/, '')`
inline rstrip: String -> String | x = do! `x.replace(/\s+$/, '')`
inline strip: String -> String | = lstrip .: rstrip
length: String -> Num | n = do! `n.length`
inline
(+++): a -> b -> String
x +++ y = do! `"" + x + y`
strip " test " is "test"
-- interpolated strings
let x = "test"
in "hello `x`" is "hello test"
open string
-- Tests
-- -----
-- By invoking javascript, we can listen for exceptions.
err x = do! `try {
x();
return false;
} catch (e) {
return e;
}`
-- Option
-- ------
module option
Option a = {some: a} | {none}
option b {none} = b
| _ {some: x} = x
option 3 {some: 2} == 2
module html
HTML = { element: a
inner: JS String
on_click: JS {} -> JS {}
set: b -> JS {}
add_class: String -> JS {}
add_style: String -> String -> JS {} }
get:
String -> JS HTML
| x = do el <- `$(x).get(0)`
return { element = el
inner = `el.innerHTML`
on_click y = `el.onclick = y`
set y = `el.innerHTML = y`
add_class y =
`el.setAttribute("class", el.getAttribute("class") + " " + y)`
add_style y z = `el.style[y] = z` }
inline ($=):
String -> a -> JS {}
x $= y =
`if (typeof $ != "undefined") {
var xx = $(x).get(0);
if (typeof xx != "undefined") {
xx.innerHTML = y;
}
}`
inline ($|):
String -> String -> String -> JS {}
selector $| rule value = `$(selector).css(rule, value)`
inline
($.): String -> String -> JS {}
($.) x y = `$(x).addClass(y)`
inline ($+): String -> String -> JS {}
| x y = `$(x).append(y)`
div: String -> JS String
| x = yield "<div id='`x`'/>"
move: HTML -> HTML -> JS a
| x y = `$(y.element).append($(x.element).detach())`
on_load: JS {} -> JS {}
| x = `window.addEventListener("load", x, false)`
inline stringify: a -> String | x = do! `JSON.stringify(x)`
inline (!!): Array a -> Num -> a | x y = do! `x[y]`
with_page: JS<a> -> JS<a>
| x = do old <- `$("body").clone()`
xx <- x
`$("body").replaceWith(old)`
return xx
-- do! with_page do "body" $= "Test"
-- text <- get "body"
-- text <- text.inner
-- return (text == "Test")
do! d <- div "test1"
"body" $+ d
"#test1" $= "I am a test!"
text <- get "#test1"
text <- text.inner
"#test1" $= ""
return (text == "I am a test!")
inline split: String -> String -> Array String | y x = do! `x.split(y)`
-- This is the initializer for prelude's console test suite.
console_runner: JS {} =
var is_error = 0
var get_line x = x |> split "__::__"
|> \x = x !! 0
|> split "_"
|> \x = x !! 0
|> \x = do! `parseInt x`
|> \x = x + 1
|> stringify
var report_failed spec results =
let items = spec.results_.items_
desc = spec.description.split "__::__" !! 1
exp = stringify (items !! 0).expected
act = stringify (items !! 0).actual
line = get_line spec.description
do if is_error == 0
then log "\r "
else return {}
`is_error = 1`
log "Test failed at line `line`
`desc`
Expected `exp`
Actual `act`"
log ""
var report spec =
do! results <- `spec.results()`
passed <- `results.passed()`
if passed
then return {}
else report_failed spec results
var reporter = { reportSpecResults: report
reportRunnerResults: `phantom.exit(is_error)` }
do env <- `jasmine.getEnv()`
`env.addReporter(reporter)`
`env.execute()`
push : a -> Array a -> JS (Array a)
push xs x = `xs.push(x)`
private console_reporter = {
total = 0
failed = 0
messages = []
reportSpecResults spec =
do! results <- `spec.results()`
passed <- `results.passed()`
`console_reporter.total++`
if not passed then do
`console_reporter.failed++`
push console_reporter.messages spec.description
return {}
else return {}
reportRunnerResults =
do if console_reporter.failed > 0
log console_reporter.messages
else
return {}
log "`console_reporter.total - console_reporter.failed`/`console_reporter.total` tests passed"
}
-- This is the initializer for prelude's HTML test suite.
table_of_contents =
do "code" $. "prettyprint"
"code" $. "lang-hs"
".test" $| "position" <| "relative"
".test" $| "left" <| "50px"
`prettyPrint()`
trivial <- `new jasmine.TrivialReporter()`
forml <- `new jasmine.FormlReporter()`
env <- `jasmine.getEnv()`
`env.addReporter trivial`
`env.addReporter forml`
`env.addReporter console_reporter`
`env.execute()`
reporter <- get ".jasmine_reporter"
body <- get "#test_suite"
move reporter body
module array
inline push! : x -> Array x -> Array x
| x xs = do! `xs.unshift(x); xs`
-- Lists
-- -----
-- A simple implementation of a library for manipulating linked lists.
module list
List a = { head: a, tail: List a }
| { nil }
-- This complex type can be hidden behind a constructor function which
-- works more like a traditional cons.
inline
x :: y = { head = x, tail = y }
-- The compiler itself also supports the list sugar `[: 1, 2, 3 ]` (or
-- `[: 1, 2, 3 :]`, if you prefer symmetry). All of these lists are
-- synonyms.
var xs = [:
[:1,2,3]
[:1,2,3:]
(1::2::3::{nil})
{ head = 1
tail = { head = 2
tail = { head = 3
tail = {nil} }}}
]
all? (λy = [:1,2,3] == y) xs
-- Simple implementations of list accessors, which illustrate
-- incomplete definition.
empty? { nil } = true | _ = false
head { head: x, tail: _ } = x
| { nil } = error "Head called on empty list"
tail { head: _, tail: x } = x
| { nil } = error "Tail called on empty list"
last { head: x, tail: { nil } } = x
last { head: _, tail: x } = last x
last { nil } = error "Last called on empty list"
take 0 x = { nil } | n x = head x :: take (n - 1) (tail x)
drop 0 x = x | n x = drop (n - 1) (tail x)
x .. y =
let f xx z when xx >= x = f (xx - 1) (xx :: z)
f _ z = z
f y {nil}
1 .. 5 == [:1,2,3,4,5]
-- Project Euler problem 1, modified to protect the innocent:
288045 == 1 .. 1111 |> filter (λx = x % 3 == 0 || x % 5 == 0) |> sum
take_while f { nil } = { nil }
| f x when f (head x) = head x :: filter f (tail x)
| _ _ = { nil }
drop_while f { nil } = { nil }
| f x when f (head x) = drop_while f (tail x)
| _ x = x
not (empty? (1 :: {nil}))
empty? [:]
head [: 1, 2 ] is 1
tail [: 1, 2 ] is [:2]
last [: 1, 2 ] is 2
err (lazy head {nil}) is "Head called on empty list"
err (lazy tail {nil}) is "Tail called on empty list"
err (lazy last {nil}) is "Last called on empty list"
take(2, [: 1, 2, 3 ]) == [: 1, 2 ]
drop(2, [: 1, 2, 3 ]) == [: 3 ]
take_while (λx = x < 0) [:] == [:]
take_while (λx = x > 0) [: 2, 1, 0 ] == [: 2, 1 ]
drop_while (λx = x < 0) {nil} == {nil}
drop_while (λx = x > 0) [: 2, 1, 0 ] == [: 0 ]
-- Cardinality
length
| {nil} = 0
| {head: _, tail: x} = 1 + length x
length [: 1, 2, 3, 4 ] == 4
-- Generators
init 0 _ = {nil}
| n x = x :: init (n - 1) x
length (init 30 0) == 30
tail (init 3 0) == init 2 0
-- List concatenation
(++)
| {nil} y = y
| {head: y, tail: ys} xs = y :: (ys ++ xs)
{nil} ++ (1 :: 2 :: {nil}) == 1 :: 2 :: {nil}
[: 1, 2 ] ++ [: 3, 4 ] == [: 1, 2, 3, 4 ]
-- Filter.
filter: (a -> Bool) -> List a -> List a
| f {nil} = {nil}
| f x when f (head x) = head x :: filter f (tail x)
| f x = filter f <| tail x
[: 1, 2, 3, 4 ] 'filter (λx = x > 2) == [: 3, 4 ]
-- Map
map(f, {nil}) = {nil}
map(f, x) = f(head(x)) :: map(f, tail(x))
[: 2, 3, 4 ] == [: 1, 2, 3 ] |> map λx = x + 1
-- Reverse
reverse y =
let r rest [:] = rest
r rest { head: x, tail: xs } =
r (x :: rest) xs
in r {nil} y
reverse [: 1, 2, 3, 4 ] == [: 4, 3, 2, 1 ]
var test_x = [: 1, 2, 3, 4 ]
test_x == reverse <| reverse test_x
take' x y =
let t(0, _, acc) = acc
t(n, x, acc) = t(n - 1, tail x, head x :: acc)
in reverse t(x, y, {nil})
var x = [: 1, 2, 3, 4, 5, 6, 7, 8 ]
take 5 x == take' 5 x
-- Folds
foldl f x xs =
var g y {nil} = y
| y { head: z, tail: zs } = g (f y z) zs
g x xs
foldl1 _ {nil} = error "Foldl1 called on empty list"
| f x = foldl f (head x) (tail x)
foldr f x =
var g {nil} = x
g { head: y, tail: ys } = f y (g ys)
g
foldr1 _ {nil} = error "Foldr1 called on empty list"
| f x = foldr f (head x) (tail x)
all? f = foldl1 (λx y = x && y) .: map f
any? f = foldl1 (λx y = x || y) .: map f
sum = foldl1 λ(x, y) = x + y
product = foldl1 λ(x, y) = x * y
concat = foldl1 λx y = x ++ y
concat_map f xs = concat (map f xs)
maximum = foldl1 λ(x, y) when x > y = x
|(_, y) = y
minimum = foldl1 λx y = if x > y then y else x
foldl (λx y = x + y) 0 [: 1, 2, 3, 4 ] == 10
foldr (λx y = x + y) 0 [: 1, 2, 3, 4 ] == 10
all? id [:true,true]
not (all? id [:true,false])
any? id [:true,false]
not (any? id [:false,false])
sum [:1,2,3] == 6
product [:1,2,4] == 8
let x = [:1,2], y = [:3,4]
concat [:x,y] == [:1,2,3,4]
concat_map (λx = [:x,x+1]) [:1,2] == [:1,2,2,3]
minimum [: 1, 2, 3 ] == 1
maximum [: 1, 2, 3 ] == 3
-- Sequences
-- ---------
-- Sequences are an abstract data type which resembles a list, except that elements
-- Sequences may be infinite, as long as you never try to read every element.
-- However, the current implementation is stack-consuming and thus limited by the
-- underlying javascript runtime [TODO].
module seq
open list
open speedtest
open html
Seq a = { seq: JS { elem: a, next: Seq a}}
| { end }
inline seq x = {seq: x}
iterate f x = seq yield { elem: x, next: iterate f (f x) }
from_list {nil} = {end}
| { head: x, tail: xs } =
{ seq: yield { elem: x, next: from_list xs }}
to_list { end } = {nil}
| { seq: x } = do! { elem: y, next: ys } <- x
return (y :: to_list ys)
take _ {end} = {end}
| 0 _ = {end}
| x { seq: y } = seq do { elem: z, next: zs } <- y
return { elem: z, next: take (x - 1) zs }
to_list (from_list [:1,2]) == [:1,2]
iterate (λx = x + 1) 0 |> take 100 |> to_list |> length == 100
zip_with:
(a -> b -> c) -> Seq a -> Seq b -> Seq c
| _ {end} _____ = {end}
| _ _____ {end} = {end}
| f {seq: a} {seq: b} = seq do
{ elem: xx, next: xs } <- a
{ elem: yy, next: ys } <- b
return { elem: f xx yy, next: zip_with f xs ys }
inline x ::: y = seq yield { elem: x, next: do! y }
tail:
Seq a -> Seq a
| {end} = error "tail called on empty seq"
| {seq: t} = do! t >>= λx = return x.next
to_lazy
| {end} = {end}
| {seq: y} = seq lazy do!
{elem: x, next: xs} <- y
return {elem: x, next: to_lazy xs}
-- Infinite sequence example
fibs = to_lazy <: 1 ::: return <: 1 ::: yield zip_with (λx y = x + y) fibs <: tail fibs
-- Since the `lazy` keyword memoizes it's argument, this fibonacci implementation
-- is ridiculously faster than the recursive version. For example, the runtime
-- speed of this fib implementation compared to the native recursive one is <span
-- id="speedtest">__________</span>%.
do! fast_time <- time yield fast_fib 30
slow_time <- time yield fibs |> take 30 |> to_list |> last
let ratio = floor (fast_time / slow_time * 100)
"#speedtest" $= ratio
return (ratio >= 2)
fibs |> take 5 |> to_list == [:1,1,2,3,5]
fast_fib 15 == fibs |> take 15
|> to_list
|> last
-- FormlZ
-- -------
-- Demonstration of some classic FP data structures. This form of polymorphism can
-- be simulated in Forml the same way they are implemented in Haskell - as a function
-- dictionary, the only difference being that you must explicitly bind the dictionary
-- instance to a symbol (as opposed to it being referenced by the type variable's
-- instantiation).
module "Forml Z"
open list
Functor f =
{ map: (a -> b) -> f a -> f b }
Monad m =
{ (>>=): m a -> (a -> m b) -> m b
ret: a -> m a }
map z x f = z.map f x
bind { ret: f, _ } x = f x
list_functor =
{ map _ {nil} = {nil}
| f { head: x, tail: xs } =
{ head: f x
tail: list_functor.map f xs }}
list_monad =
{ (>>=) x g = concat_map g x
return x = [:x] }
js_monad =
{ (>>=) x f = x >>= f
return x = return x }
let (>>>=) x g = concat_map g x
z = 1 .. 3 >>>= λx = [:x, x + 1, x + 2]
z == [:1,2,3,2,3,4,3,4,5]