Forml

A contemporary programming language for the discriminating programmer, intended to approximate the safety of Haskell and the expressiveness of Ruby. Should such an approximation turn out to exist.

Features

  • Buzzwords: functional, strict, expressive, pure(ish), static(y), inferred, fast, fun. Strong like a gorilla, yet soft and yielding like a Nerf ball.

  • Targets Javascript, but please regard this as an implementation detail - forml is not intended as an answer to "the Javascript problem." Simple foreign function interface, which allows the introduction of untyped values for quick & dirty code integration, which can later be restricted via explicit typing.

  • Type system which is designed to be simple and catch obvious errors, not entirely exhaustive, dictatorial and somewhat combative (I'm looking at you, Haskell). Inferred, strucural types, partials records, ability to introduce unrestricted types via FFI.

  • Fast. Automatic tail call optimization, inline functions, designed for use with Google Closure Compiler advanced optimizations mode. See [Tests]((http://texodus.github.com/forml/prelude.html) for some simple benchmarks.

  • Flexible, expressive syntax. Lots of sugar for obvious tasks.

  • Inline testing, compiles to a Jasmine suite.

  • Heavily inspired by Haskell , F# , Coffeescript. , (_)(S)(OCA)ML , Clojure , JMacro , Ruby

Examples

Installation

(tested on Snow Leopard, Lion, Ubuntu). Note that Forml also requires Closure for optimizations and either Phantom.js or Node.js

Install the Haskell Platform, then

$ cabal install forml

To compile some forml files:

$ forml -o app test.forml test2.forml

will create an app.js and app.spec.js with the compiled code and test suite respectively.

Forml will by default try to minify/optimize your code with Closure, via the $CLOSURE environment variable, which should point to the closure jar. Failing this, forml will attempt to post your code to the Closure web service.

Additionally, forml will attempt to run the test suite with the phantomjs binary, which it expects to find on your PATH. You may optionally specifiy to run your suite via node.js with

$ forml -node-test test.forml

To compile literate forml (eg, Forml code embedded in Markdown):

$ forml test.lforml

To see the inferred types:

$ forml -t test.forml

To turn off optimizing (eg, Closure) or testing:

$ forml -no-test -no-opt test.forml

To watch a file for changes and incrementally compile:

$ forml -w test.forml

To generate documentation and test runner (like this file):

$ forml -docs test.forml

Be sure to check out forml-mode if you're into EMACS.

Tutorial

This is unfortunately not comprehensive, and presumes some working knowledge of ML or Haskell. Forml supports a flexible, forgiving syntax that supports many synonymous forms. This will be illustrated by adherring to an entirely random, arbitrary style throughout.

The basic unit of code organization in forml is the module, which is simply a collection of definitions in a namespace.

module readme

Within a module, the compiler recognizes strictly ordered logical sections divided by open statements and sub modules; within a section, however, declarations can be in any order. open statements create local aliases for their own public definitions, which will shadow previously defined symbols.

    open prelude
    open prelude.string
    

Definitions

Simple functions. Forml allows function application via spaces as in ML, or via ()'s in a more traditional c style -

    square x = x * x

    add(x, y) = x + y

With pattern matching

    fib 0 = 0 | 1 = 1 | n = fib (n - 1) + fib (n - 2)

Patterns can be separated with |, or by repeating the definition's name ala Haskell. Definitions can have optional type annotations, which may restrict the inferred type of the definition, but must not be more general

    private

    fib' : Num -> Num

    fib' 0 = 0
    fib' 1 = 1
    fib' n = fib' (n - 1) + fib' (n - 2)
    

Operators can be defined much like in Haskell. Precedence is currently fixed, though you can declare right associative operators by ending them with a : character.

Testing

Tests are a first class concept in forml - any unbound in a module (or in other words, any expression which isn't part of a definition), which is inferred as type Bool, is treated as a test, and is compiled to a Jasmine suite in a separate file from your definitions.

    fib' 7 == 13
    fib' 0 == 0
    
    

For example, this file is the result of running the forml compiler with the -docs flag for readme.forml, and incorporates both the compiled output and the Jasmine suite. You can execute this suite by clicking the RUN TESTS button, which will highlight the test results in this document

Modules

Namespaces are not symbols, so this won't work:

    prelude.log "Hello, World!"    -- Won't compile!

Instead, you must qualify the import and supply a symbol name to bind to. The alias will be typed to a record whose fields are the first-class definitions in the module.

    open prelude.array as array
    
    array.map fib [3,4,5] == [2,3,5]

Notice this means aliased modules can be passed as arguments

    mmap dict f xs = dict.map f xs

    mmap array fib [3,4,5] == [2,3,5]
    
    

Records

Forml has the basic primative types from Javascript: Num, String, Bool; plus a record, which is structurally typed (and implemented as a simple Javascript object, for the curious).

    person name address = { 
        name    = name
        address = address
        message = "`name` lives at `address`"
        say msg = "`name` says '`msg`'"
    }

    person("Andrew", "123 Fake St.").message
        is "Andrew lives at 123 Fake St."

    point = {x: 10, y: 10}
    
    20 == point.x + point.y

The sugared syntax .field represents an anonymous accessor function for convenient piping.

    person "Wilfred" "couch"
        |> .say "I'm lazy"

        == "Wilfred says 'I'm lazy'"

    var people = [
       person "Josh" "Jersey"
       person "John" "Egypt"
    ] in

    people 'map (.name) == [ "Josh", "John" ]
    

Records can be destructured in function argumentsn and can partially match with the _ character. This type of function will apply to any record with at least the keys in the partial match.

    magnitude {x: x, y: y, _} = sqrt (square x + square y)

    magnitude {x: 3, y: 4, other: "test"} == 5
    magnitude {x: 4, y: 3, test: "other"} == 5

Functions

Anonymous functions also follow Haskell, can be written with \ or λ, and allows pattern seperation via |

    map: (a -> b) -> Array a -> Array b
    map f xs = do! `xs.map(f)`

    let fil =
    
        λ x when x > 5 = x
        | 5            = 0
        | x            = 5

    map fil [2, 6, 3, 7, 5] is [5, 6, 5, 7, 0]

All functions are curried, and can be partiall applied - even operators.

    x +* y = (2 * x) + (2 * y)

    [1, 2, 3] 'map ((+*) 2) == [6, 8, 10]

    add_twelve x y = x + y + 12

    let f = add_twelve 5
    f 10 == 27

Interop & Side Effects

Forml technically allows unrestricted side effects, but by default wraps them in a JS a type, which can be composed with the >>= and >> operators, or a variation of Haskell's do notation.

    hello_world = do

        `console.log("Hello World")` -- Calls to Javascript always return type `JS a`
        x <- `Math.sqrt(9)`          -- `x` is inferred to be the unrestricted type `a`
        let z = x + 1                -- `x` is now restricted to type `Num`
        return (z + 1)                  -- type of `hello_world` is inferred to be `JS Num`

    8 == do! hello_world >>= λx = `x + 3`

Though this function is inferred to be a -> b, you can restrict it with a signature.

    inline
    sqrt: Num -> Num                 
    sqrt x = do! `Math.sqrt(x)`      -- `do!` realizes its argument immediately

Forml also supports additional keywords lazy and yield. Both take expressions as arguments (as opposed to do syntax), but return an unrealized type JS a, the difference being that lazy will only evaluate it's arguments once, then cache the result.

    let x = 0
        test = lazy do! `x = x + 1; x`

    in  1 == do! test >> test >> test >> test

Tail Call Optimization

This example will compile to a for loop, as it is tail recursive.
var is a synonym for let, and in is an optional binding separator.

    (**): 

         String -> Num -> String
         text   ** n    =

             var f(_, 0, acc) = acc
                 f(text, n, acc) =
                     f(text, n - 1, acc +++ text)
             
             in f(text, n, "")

    "hello" ** 3 == "hellohellohello"
    length ("a" ** 10000) == 10000

Inlines

Function inlining allows for macro like behavior, like lazy & conditional evaluation (the ' operator here is equivalent is "left-pipe" application, x |> f == x 'f).

    inline whenever x f =
         if x
         then f
         else {}

    error "I am executed conditionally" 'whenever (6 < 5) == {}

Arguments to an inline can be repeated, nested in yields, ifs or anonymous functions, even removed entirely from the code. Inlines can be used inside let bindings, too!

    let inline comment _ = true

    in  comment <: error "I'm compiled away"

    

Types, Aliases & Unions

Forml is strong, statically typed, and types are inferred and checked at compile time. Unlike in traditional Hindley Milner style inferrence, forml allows you to break the rules with some explicit type annotations.

    num_or_string:

          (Num | String) -> String
        | x when num? x   = "Num"
        | _               = "String"

Structural types look just like the records they represent.

    unwrap: {box: a} -> a
    unwrap {box: x} = x

Algebraic data types and type aliases are declared the same way (where the type keyword is optional). {nothing} here is shorthand for the record type {nothing: {}}, useful for enum types.

    Maybe a = {just: a} | {nothing}

Notice there are no explicit type constructors - in forml, types constructor functions are inferred from the fields of a record and applied automatically. For example, when Maybe a is in scope, any record type with the just or nothing keys will be inferred to be a type Maybe a.

    maybe x {just: y} = y
    maybe x {nothing} = x

    maybe 3 {just: 4} == 4
    maybe 3 {nothing} == 3

    type Tree a =

        {left_tree: Tree a, right_tree: Tree a}
      | {leaf: a}

    type List a = { head: a, tail: List a } | { nil }

    sum: List Num -> Num
    sum { head: x, tail: xs } = x + sum xs
    sum { nil } = 0
    
    sum { head: 2
          tail: { head: 3
                  tail: { nil } } }
        == 5

Lists have a syntax sugar as well.

    sum [: 3, 4, 5 ] == 12

In case this sort of things floats your boat, you can also declare polymorphic types in "java" style, with < >.

    has_value: Maybe<a> -> Bool
    has_value {just: _} = true
    has_value _ = false

    has_value {just: 4}
    not (has_value {nothing})
    

Changes

0.2

  • Incremental compilation, increased compilation speed
  • Made inlines inline their arguments as well when fully applied, like a macro. Code generator works better with Closure, resulting in smaller, more optimized JS.
  • One-sided if expressions.
  • where expressions.
  • Silent mode
  • Fixed namespace rendering error.
  • Fixed definition ordering in let bindings.
  • Fixed indentation error

0.1.3

  • Fixed many parsing bugs, including negative numbers, indentation on anonymous functions & do blocks, string-escaping, isnt, accessor ordering.
  • Operator partials ala Haskell, sum = reduce (+)
  • Fixed type checking of mutually recursive definitions.
  • Fixed Node.js test suite to work nearly-identically to Phantom.js
  • Fixed output to use sensible filenames, and respect the -o option.
  • Tetris!

0.1.2

  • Added accessor function syntax for treating record fields as functions (eg x.map f == x |> .map f)
  • Added _ as valid type variable for throwaway unique types.
  • Module aliases allow binding a module to a structurally typed symbol (eg open prelude.list as list).
  • Prelude expanded, with special attention to the array module.
  • Embedded prelude tests are skipped.
  • Bug fixes.

0.1.1

  • Documentation generation has been greatly improved. Better styling, generates individual pages for each file.
  • The prelude is now embedded in the compiler. Simply import it via open prelude - the compiler will include the code automatically. Currently weighs in at ~11k, if you care about that sort of thing.
  • Command line interface is more pleasant to work with

Contributors

A special thanks to everyone who has contributed to forml!