Mill Computing, Inc. Forums The Mill Tools Simulators Simulation Reply To: Simulation

Post count: 9

# Flom – a Functional Language on the Mill

Ralph Becket at outlook dot com, 2-Feb-2015

I considered calling this “Mill Functional”, but that had an unfortunate contraction of its name.

## Introduction

The [Mill]( is a new CPU architecture. The designers have invited curious parties to propose a simple language to be compiled or interpreted under simulation. Real Programmers use functional languages and this is my contribution.

Flom is a statically typed, strict functional programming language in the vein of [Core Haskell]( as used in GHC (although GHC’s Core is, of course, lazy). Some of the implementation ideas I have taken from my experience developing the [Mercury]( compiler.

The goal is to produce something useful, which can be implemented reasonably efficiently with modest effort, and which can be later extended largely via syntactic sugar.

## Some examples

If you’re familiar with Haskell or ML or F#, these examples are standard. (The syntax is somewhat conjectural and the reader is invited to use their imagination to fill in any gaps.)

### Fibonacci

The slow way:

    let fib n = if n < 2 then 1 else (fib (n - 1)) + (fib (n - 2))

The quick way:

    let fib n =
        let fib' n i j = if n <= 1 then i else fib (n - 1) j (i + j)
        in fib' n 1 1

### Lists

    // All introduced types are either type synonyms or 
    // discriminated unions (aka algebraic data types).
    // Types can take type parameters, as here.
    type list T = nil
                | cons T (list T)

    let len xs =
        case xs of nil        = 0
                |  cons _ xs' = 1 + (len xs')
    let map f xs = 
        case xs of nil       = nil
                | cons x xs' = cons (f x) (map f xs')

    let foldr f xs z = 
        case xs of nil        = z
                |  cons x xs' = f x (foldr f xs' z)

    // Note that type constructors are also functions:
    let append xs ys = foldlr cons xs ys
    let filter p xs =
        let f x ys = if p x then cons x ys else ys
        in foldlr f xs nil

### Quicksort

    let qsort xs =
        case xs of nil        = nil
                |  cons x xs' =
                        let as = qsort (filter (> x) xs')
                        let zs = qsort (filter (< x) xs')
                        in append as (cons x zs)

## Core Flom and desugaring

    Expr ::= k             // Literal constant; e.g., an integer.
         |   ID            // A named quantity (variable, function, etc.)
         |   Expr Expr ... // Function application   
         |   LetsExpr
         |   CaseExpr
         |   ( Expr )

    LetsExpr ::= LetExpr* in Expr
    LetExpr  ::= let ID ID* = Expr      // Parameters indicate a function.
    CaseExpr ::= case Expr of Pattern | Pattern ...
    Pattern  ::= ID (ID | _) * = Expr   // First ID is the constructor tag;
             |   _ = Expr               // _ is the "don't care" pattern.
    Type     ::= type ID ID* = ID TypeExpr* | ID TypeExpr* ...
    TypeExpr ::= ID ID*                 // Type constructor or type synonym.

### Desugaring if expressions

    if x then y else z

desugars to

    case x of True = y | _ = z

### Desugaring nested expressions

    f (g x)

desugars to

    let tmp = g x
    in f tmp

(and likewise for all the other cases until we’re left with nothing else to simplify).

### Desugaring currying

If, say, f is a function of arity three occuring in an application of arity one, then

    f x

desugars to

    let f' y z = f x y z in f'

### Desugaring free variables and local functions

A free variable is defined here as a name whose value cannot be statically determined. The free variables of a function are desugared away by explicitly constructing and passing an environment argument and lifting the local function definition to the top level. For example,

    let f x y =
        let g a b = a * x + b * y
        in g 11 22

desugars to

    let f x y =
        let env = tuple2 x y
        in g' env 11 22
    let g' env a b = case env of tuple2 x y = a * x + b * y

where tuple2 stands for some built-in arity-2 tuple data type.

### Other desugaring opportunities

The usual opportunities exist for adding nicer syntax for, say, lambdas, lists, etc.

## Typechecking and type inference

The type system is plain old Hindley-Milner and [Algorithm W]( will do the trick nicely.

## Implementation detail

[This section is essentially a set of suggestions for writing a reasonably efficient Flom compiler without great effort. The first instance would be a cross-compiler, which ought to be sufficient to craft a native compiler in Flom itself.]

All atomic values (built-in types such as ints, chars, floats, etc.) occupy space equivalent to a pointer.

All values constructed via data constructor live on the heap and are referenced by pointer. Each such construction is a vector of pointer-sized words, comprising the data constructor tag followed by any argument values.

The usual discriminated-union optimisation tricks can all be applied at some point.

Equality and comparison on atomic values is by identity. Equality and comparison for algebraic data types is lexicographic.

There is no destructive assignment! Any such facility, including IO, must be provided via functions written in something other than Flom and somehow imported.

Garbage collection ought to be straightforward, but it isn’t clear to me exactly what is involved with the Mill.

### Translation to genAsm

This is just a sketch… and my grasp of genAsm is wobbly at best. [[e]]v denotes the translation of Flom expression e, bound to genAsm symbol v.

    k   ---> k -: v_k              where k is a constant
    x   ---> v_x                   where x is a function parameter or local

    let x = e                      local or top-level variable
        ---> [[e]]v_x
    case e of patExpr1 | patExpr2 | ...
        ---> [[e]]v_e
             if [[patExpr1]]v_c elif [[patExpr2]]v_c ... else <disaster!> 
    [[tag x1 x2 ... = e]]v 
        ---> load(v_e, 0) <tag> eql then
             load(v_e, 1) -: v_x1
             load(v_e, 2) -: v_x2
    f x1 x2 ...                     function application
        ---> [[x1]]v_x1 [[x2]]v_x2 ...
             call f v_x1 v_x2 ... -: v_f
                                    N.B.: the simple direct tail recursion
                                    case should be compiled into an ordinary
    ctor x1 x2 ...                  data constructor function
        ---> func ctor(x1, x2, ...):
             <alloc space>v_c
             store(v_c, 0, <tag>)
             store(v_c, 1, x1)
             store(v_c, 2, x2)
             retn v_c                        

    let f x1 x2 ... = e             top-level function definition
        ---> func f(x1, x2, ...):
             retn v
  • This reply was modified 7 years, 3 months ago by  ralphbecket.
  • This reply was modified 7 years, 3 months ago by  ralphbecket.
  • This reply was modified 7 years, 3 months ago by  ralphbecket.
  • This reply was modified 7 years, 3 months ago by  ralphbecket. Reason: Trying to preserve code formatting