GFun

  (fun: fib [n Int] Int
    (if (< n 2) n (+ (fib (dec n)) (fib (dec n 2)))))
  
  (fib 10)

55

intro

GFun aims to become a Lispy scripting language/vm implemented and embedded in Go.

Much along the same lines as g-fu, but a tighter implementation that takes everything learned since into account.

The different layers of the implementation are well separated and may be used separately or mixed and matched; for example by putting a different syntax on top or adapting the existing one, or adding additional compilation steps.

limitations

For performance reasons, the core loop specifies operations inline; which means that it’s impossible to extend with new operations without changing the code. Limits on number of operations, number of registers etc. are determined by the bytecode format.

status

It’s still early days, I’m currently profiling and optimizing the implementation guided by initial performance numbers. All functionality described in this document is expected to work.

setup

$ cd tests
$ go test
$ cd ..
$ go build gfun.go
$ ./gfun

types

GFun supports first class types, typeof may be used to get the type of any value.

  (typeof 42)

Int
  (typeof Int)

Meta
  (typeof Meta)

Meta

Any

The root type.

Fun < Any

The type of functions.

Bool < Any

The boolean type has two values, T and F.

Int < Any

The type of whole numbers.

Macro < Any

The type of macros.

Meta < Any

The type of types.

Nil < Any

The nil type has one value, _.

bindings

Values may be temprarily bound to identifiers using let.

  (let [foo 35 bar (+ foo 7)] bar)

42

Rebinding temporarily binds the outer value, ie. bindings act as scoped special vars.

  (let [foo 1]
    (fun: bar [] Int foo)
    (let [foo 3]
      (bar)))

3

Values may be bound until further using set.

  (set foo 35 bar 7 baz (+ 35 7))
  baz

42

functions

Functions may be anonymous.

  (let [foo (fun [bar Int] Int bar)]
    (dump foo)
    (foo 42))
  
(Fun 0x824634418592)
42

performance

GFun is currently around 1-4 times as slow as Python.

  (fun: fibrec [n Int] Int
    (if (< n 2) n (+ (fibrec (dec n)) (fibrec (dec n 2)))))

  (bench 100 (fibrec 20))

527

  (fun: fibtail [n Int a Int b Int] Int
    (if (= n 0) a (if (= n 1) b (fibtail (dec n) b (+ a b)))))

  (bench 10000 (fibtail 70 0 1))

218

fusing

The generated bytecode is analyzed before evaluation in an attempt to fuse as much of it as possible.
In the following example; GFun initially detects that the arguments are not used, which results in the function entry point being moved to 3; then the exit point is moved past the _ since that’s the default return value.

 (fun: foo [x Int y Int] Nil _)

2022/03/02 22:13:46 Fused unused load at 1: 32
2022/03/02 22:13:46 Fused unused load at 2: 33
2022/03/02 22:13:46 Fused entry to 2
2022/03/02 22:13:46 Fused entry to 3
2022/03/02 22:13:46 Fused exit to 3
0 GOTO 5
1 NOP
2 NOP
3 RET
4 RET

support

Should you wish to support this effort and allow me to spend more of my time and energy on evolving GFun, feel free to help make that economically feasible.

coder/mentor for hire

I’m currently available for hire.
Remote or relocation within Europe.
Send a message to codr7 at protonmail and I’ll get back to you asap.

GitHub

View Github