Funky LISP

Funky LISP is a scripting language designed for interoperation between short-lived processes as through a Unix pipe or sockets. Features include loose typing, lazy evaluation, native support for JSON objects, implicit partialing, LISP-style macros, and a little non-LISPy syntactic sugar to help mitigate the paren hell that most LISP n00bs whine about.

Source code can be obtained via git:

git clone

Table of Contents

1 Quick Start

If you have clang or gcc, you should be able to compile everything fine. In order to run the tests, you will need valgrind.

$ cd src

$ make funky

$ echo '(+ 3 2 1)' | ./funky

2 Intro

For the most part, Funky is pretty much another LISP.

2.1 Procedure Definition

Defining procedures works basically like with Common LISP:

(def foo (x y) (+ x y))

The procedure's name immediately follows the call to the definition macro (so it's not inside the operand definition expression like in scheme-descended LISP's).

Naming a procedure is optional. If you omit the procedure name in a def call, it becomes an anonymous procedure (a lambda).

((def (x y) (+ x y)) 3 4)



2.2 Macro Definition

Macros work much like they do in other lisps.

(mac yig (x y) (append x y))

(yig (1 2 3) (4 5 6))


(1 2 3 4 5 6)

Macros can also be anonymous.

((mac (x y) (append x y)) (1 2 3) (4 5 6))


(1 2 3 4 5 6)

2.3 List Construction and Sugar

We've added some sugar to help make things a little more legible to the unenlightened. The following lines are interchangeable…

[1 2 3]

(list 1 2 3)

Commas are white-space characters so people coming from something like a Python background can construct lists the way they're used to. The following lines are interchangeable.


(list 1 2 3)

2.4 Grids

We have a native hash table type called a "grid"

(grid (pair "key" "val") (pair "one" 1))

There's syntactic sugar so it can be annotated the way you might do for JSON or Python dicts. The following lines are interchangeable:

(grid (pair "key" "val") (pair "one" 1))

{"key" : "val", "one" : 1}

The "." operator gets elements from a grid. The following lines are interchangeable:

(get mygrid "foo")

mygrid . "foo"

Output: FOO

In order to improve our ability to consume Javascript, the . and : operations break the symbol boundary. The following lines are interchangeable:

{"blah":429857, "narghe":92090}

{"blah" : 429857, "narghe" : 92090}

As are these:

gridThing . "key"


If a grid's key is an atom rather than a string, you needn't surround it in quotes when getting its value.

(set! yourmom {stinks:true})





Though this is an anticommentist ( project and the goal is to make the language unambiguous enough that comments are not needed for the vast majority of algorithms, we wanted to be able to make scripts in Funky usable from the shell with shebangs. So we lifted the shell script convention of # denoting comments. Everything from # to the end of the line will be ignored by the interpreter.

2.6 Partial, Arity and Strict Application

All procedures in Funky are variatic (they can take any number of arguments). The output of the procedure may change based on the number of arguments given.

Funky impicitly partials (pretend curries) incomplete procedure calls.

(def foo (x y z) (+ x y z))

(set! threeplus (foo 3))

(set! threeplustwoplus (threeplus 2))

(threeplustwoplus 2)



When extra arguments are given, they are stored in the &args list for the scope of that procedure. The following funk will print the first two arguments given on their own lines, all subsequent arguments on the same line, and will return the last argument given (per the rules of print).

(def bar (x y) (print x) (print y) (apply print &args))

(bar 3 4 5 6 7 8 9)

If it's more important that you have each operand bound to an argument, you can enforce that with strict-apply. When strictly applying a procedure, incomplete calls or calls with too many arguments evaluate down to an error.

(strict-apply foo (list 1 2))


(err "Argument count does not match the operand count")

2.7 Application Sugar

Having lots of nested procedure calls can get a bit hard for some folks to read. The first thing that most folks complain about when learning LISP is the abundance of parenthesis. We have sugar to help keep the nesting from getting out of control. Given the following procedure definitions…

(def addthree (x) (+ x 3))

(def addmore (x y z) (+ x y z))

… the following three lines are interchangeable with each other:

(addthree (+ 2 2))

(+ 2 2) -> addthree

addthree <- (+ 2 2)

… and these are interchangeable with each other:

(apply addmore [1 2 3])

[1 2 3] ~> addmore

addmore <~ [1 2 3]

… and these lines are also interchangeable with each other:

(strict-apply addmore [1 2 3])

[1 2 3] S> addmore

addmore <S [1 2 3]

This sugar, when combined with implicit partialing, can chain together to break deeply nested expressions into small, legible parts:

(seq 1 10) -> (map (def (x) (+ x 3))) ~> print


4 5 6 7 8 9 10 11 12 13

2.8 Applying Grids

When grids are applied to procedures, apply will try to correlate each operand with a key in the grid and bind the value as its argument.

{fooby:"FOOBY", barby:"BARBY"} ~> (def (fooby barby) (print fooby barby))



If the operand is not found as a key in the grid, nil is passed in for the unfound argument.

{barby:"BARBY"} ~> (def (fooby barby) (print fooby barby))



Strict-apply is not very useful when applying grids. Nothing from grids ends up in the &args local variable. In the future, it may become worth-while to have incomplete application of grids result in a curried procedure asking for the ungiven arguments instead. But that sounds like arity madness to me.

2.9 Laziness

Like other LISP languages, Funky is eagerly evaluated at its core. But since LISP is the True Way of Computing, laziness is easy to add to the language.

The "repeat" procedure will take any expression and repeat it infinitely. The "take" procedure will take the first N elements from the list given (where N is the first argument and the list is the second argument).

(+ 3 2) -> repeat -> (map print) -> (take 4)





(5 5 5 5)

2.10 Pseudoclosures

Funky is not lexically scoped and therefore can not have proper closures. Closure-like behavior is emulated by the way a procedure resolves its symbol names at definition time.

(def enclose-add- (x) (def (y) (+ x y)))

(set! add-four- (enclose-add- 4))

(add-four- 3)



Because the memory of each scope is freed after its expression is evaluated, pseudoclosures can not modify the state of the scope that they were enclosed into. The pseudoclosure's state is locked to whatever it was at the time the enclosed procedure was defined.

3 Documentation

The most unambiguous documentation for this language will always be in the tests. Look at src/test/*.in and the related *.out files to see how everything is generally expected to work. Plan is to (eventually) generate docs from the test data. :P

4 Gotchas

Some things you'll want to know:

  1. We don't (and probably won't ever) use the ' token to quote an expression. Instead, the single quote character behaves as it does in most shell scripting languages; signifying the beginning and end of a quoted string. (Just like double-quote does). You can use the quote macro to quote things instead: (quote foo)
  2. We use a "true" keyword instead of "t" to represent truthiness because single-character global constants are stupid.
  3. The funk machine currently reads until EOF before evaluating the text you gave it. This is dumb and should change soon.
  4. Not all of the necessary features are finished
  5. It's slow/inefficient. It'll be faster. Promise.
  6. The "car" operator is called "head" and "cdr" is called "rest" because that cryptic tradition isn't very helpful. You can try to (set! car head) if you absolutely can't let those terms go.
  7. If you're used to Haskell, you may notice that the : operator is similar to Haskell's. Be warned: Funky does not do the evaluation order shenanigans that Haskell does and so will always evaluate left to right. So where haskell will say "1:2:[3,4]" is equal to a list of "[1,2,3,4]" Funky will have that exact same input result in a lisp-style "dotted cons" being the very first element in a list: "((pair 1 2) 3 4)"

5 License

Copyright (c) 2013, Anthony "Ishpeck" Tedjamulia All rights reserved.

Redistribution and use in source and other forms, with or without modification, are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in other form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Date: 2015-06-28T21:04-0600


Org version 7.9.3f with Emacs version 24

Validate XHTML 1.0