Funcgo is a compiler that converts Functional Go into Clojure, to run on the JVM or as JavaScript.
Without installing anything you can try the online tour where you can type Funcgo and see how it converts to Clojure and evaluates.
(By the way the online tour is itself an example web application that uses Funcgo for both it server side (JVM) and its client side (JS).
Follow the install instructions for Leiningen
On the command line, type ...
lein new appfgo hellofuncgo
cd hellofuncgo
lein do fgoc, run
This should print out Hello, World from Funcgo
.
You can also execute the tests ...
lein do fgoc, test
Edit src/hellofuncgo/core.go
and modify it.
Then again do ...
lein do fgoc, run
Congratulations, you have just written and executed your first Funcgo program!
You can get a better feel for the language by reading the Introduction to the Funcgo Language section below.
To dive deeper, see Funcgo Reference doc.
To browse some actual working code, the biggest and most complex
program so far written in Funcgo is its own compiler. (Turtles
all the way down!) You might start at the main.go
file in
the source directory.
A smaller set of working code is fgolib. In addition to
looking at the Funcgo code there, you can also examine the
project.clj
file which is a working example of using the Leiningen
plugin.
If you want to see a complete web app, that generates both Clojurescript and Clojure, see the source for www.funcgo.org.
There is also do lein fgoc --repl
to bring up the beginnings of a
REPL that you can use to explore...
$ lein fgoc --repl
test
src
fgo=> 2+3
Clojure: (+ 2 3)
Result: 5
fgo=> func{10 * $1} map [1,2,3,4,5,6]
Clojure: (map #(* 10 %) [1 2 3 4 5 6])
Result: (10 20 30 40 50 60)
fgo=>
(In the above example, note you must have double-spaces around the
map
in the infix expression. This expression is equivalent to map(func{10 * $1}, [1,2,3,4,5,6])
)
The preferred way to use this compiler is via the Leiningen Plugin as described in the Quick Start section.
If you are not using Leiningen you can use java -jar bin/funcgo-compiler-*-standalone.jar directory ...
to compile.
The goal of Funcgo is to combine the readability of the Go language with the semantics of Clojure.
-
Go is a language that has been well designed to be very readable. However it is best as a low-level system programming language (replacing C) and it is missing many of the higher-level features that programmers expect for working further up the stack, in for example in web applications.
-
Clojure is a variety of Lisp that inter-operates with Java or JavaScript. It encourages a functional programming style with efficient immutable containers, combined with a thread-safe model for mutable state called software transactional memory. However, Clojure is difficult to read for programmers unfamiliar with Lisp syntax.
In this section are Funcgo versions of some of the Clojure examples from the Clojure Cookbook.
func add(x, y) {
x + y
}
add(1, 2)
=> 3
Here we define a function add
and then call it. If you are a Go
programmer this should look familiar. However you might notice that
the types are missing and that there is no return
statement.
Funcgo does not require types, though as we will see later, in certain cases when performance is important you can specify types at a few strategic locations.
Funcgo does not need a return
statement, rather a function simply
returns the value of its last expression (often its only expression).
package example
import(
"clojure/string"
)
Here we see what the top of a Funcgo source file called example.go
might look like. Here we import in a Clojure
string utility package to be used in this file.
string.isBlank("")
=> true
Because of the import
statement at the top we can now access
functions in the string
package provide by Clojure. One little
wrinkle is that the Clojure function is actually blank?
,
with a ?
character that is illegal in Funcgo. Similarly many Clojure
functions have -
characters in their name that Funcgo does not
allow. So we automatically mangle identifiers so that isSomething
becomes something?
and thisIsAnIdentifier
becomes
this-is-an-identifier
. This is important, because you will often
have to refer to the Clojure documentation of its library.
string.capitalize("this is a proper sentence.")
=> "This is a proper sentence."
string.upperCase("Dépêchez-vous, l'ordinateur!")
=> "DÉPÊCHEZ-VOUS, L'ORDINATEUR!"
string.replace("Who\t\nput all this\fwhitespace here?", /\s+/, " ")
=> "Who put all this whitespace here?"
The example above shows that string escapes are familiar-looking to
most programmers. It also introduces the syntax for regular
expression literals, which are written between a pair of /
characters.
str("John", " ", "Doe")
=> "John Doe"
Funcgo does not concatenate strings using a +
operator like other
languages you may be familiar with. Instead you use the str
function. This is one of the many functions defined in clojure.core
that can be used without needing an import
statement.
firstName, lastName, age := "John", "Doe", 42
str(lastName, ", ", firstName, " - age: ", age)
=> "Doe, John - age: 42"
In keeping with its orientation as a functional programming language, Funcgo does not have mutable local variables. Instead, inside functions and other scopes you should create constants (whose values can not be changed.
var firstName = "John"
var lastName = "Doe"
var age = 42
str(lastName, ", ", firstName, " - age: ", age)
=> "Doe, John - age: 42"
You can create mutable variables using var
, but these are global and
changes are not propagated between threads, so you should avoid using
them if possible.
into([], range(1, 20))
=> [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]
Here we see an example of using the range
function to create
a lazy sequence of integers and then using the into
function
to create a vector with the same values.
This example also introduces vector literals, with the empty vector
being passed as the first parameter of into
.
[] into range(1, 20)
=>, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]
This example has the exact same effect as the previous example, but we
are taking advantage of another feature of Funcgo any function that
takes two parameters foo(param1, param2)
can alternatively be
written in infix notation as param1 foo param2
(with double spaces
around the foo
). This can sometimes lead to cleaner and more
readable code.
me := {FIRST_NAME: "Eamonn", FAVORITE_LANGUAGE: "Funcgo"}
str("My name is ", me(FIRST_NAME),
", and I really like to program in ", me(FAVORITE_LANGUAGE))
=> "My name is Eamonn, and I really like to program in Funcgo"
The above example introduces a number of new language features.
First note the dictionary literal which creates a dictionary with two entries.
Here the keys are keywords which in Funcgo are distinguished by being all-uppercase. Unlike symbols that evaluate to something else, keywords just evaluate to themselves and are most commonly used like this as dictionary keys.
Note that to extract values from the dictionary you treat it as if it were a function, using the key as the parameter to the function.
str apply (" " interpose [1, 2.000, 3/1, 4/9])
=> "1 2.0 3 4/9"
This example shows two nested infix expressions.
The inner ones uses the interpose
function to take the
vector [1, 2.000, 3/1, 4/9]
and create a new vector with blanks
inserted between [1, " ", 2.000, " ", 3/1, " ", 4/9]
.
The outer infix expression shows an example of Funcgo being used as a
functional programming language. The apply
function is an
example of a function that takes a function as a parameter. Here
str
is passed as the first argument.
str(...(" " interpose [1, 2.000, 3/1, 4/9]))
=> "1 2.0 3 4/9"
This example is equivalent to the previous one, but it shows some
syntactic sugar for the apply
function in a way that echoes how
variadic functions are declared. Essentially if you have const args = [a, b, c]
then calling foo(...args)
is the same as calling
foo(a, b, c)
.
func isYelling(utterance String) {
isEvery(
func(ch Character) { !Character::isLetter(ch) || Character::isUpperCase(ch) },
utterance
)
}
This example shows an example of Java interoperability. The ::
specifies access to a static function (with symbol names not being
mangled, but passed to Java as-is).
This is also the first time we have specified a type for a value,
specifying the String
type on the outer function's parameter. This
is optional, but doing so in this case avoids Java reflection, making
for a more efficient implementation.
We also see here an example of an anonymous function, here a predicate (function returning Boolean) that tests if a character is a non-letter or an upper-case letter.
The isEvery
function tests whether this predicate is true
for every character in the string.
In this section are Funcgo versions of some of the Go examples from the A Tour of Go.
package main
import "fmt"
Pi := 3.14
func main() {
World := "世界"
fmt.Println("Hello", World)
fmt.Println("Happy", Pi, "Day")
{
Truth := true
fmt.Println("Go rules?", Truth)
}
}
=> Hello 世界
Happy 3.14 Day
Go rules? true
One constraint on :=
definitions is that, except for at the top
level, they have to be at the beginning of a curly-brace block. So
above we had to add an extra level of curlies to allow Truth
to be
defined at the bottom of the function.
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
v
} else {
lim
}
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
=> 9 20
For compatibility with Go, you can use Go-style primitive types, but they are mapped to JVM primitive types that may have different bit sizes.
package main
import (
"fmt"
)
func newton(n int, x, z float64) float64 {
if n == 0 {
z
} else {
newton(n-1, x, z-(z*z-x)/(2*x))
}
}
func Sqrt(x float64) float64 {
return newton(500, x, x/2)
}
func main() {
fmt.Println(Sqrt(100))
}
=> 10.000000000000007
For compatibility with Go, you can add a cosmetic return
to a
function, but only in the special case of returning the top level
expression of a function.
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
fmt.Println(Vertex{1, 2})
}
=> {1 2}
You can go a long way in Funcgo just using the built in dictionary and vector types, but you can also create data structures that are implemented as Java classes.
You need Leiningen (the Clojure build tool) to build the compiler.
(Note that if you are on Ubuntu, as of March 2014 the version in the
standard Ubuntu package manager is too old to work with this project.
Instead download the lein
script from the
Leiningen web site and put in your
PATH.
First clone this repo, and cd into the funcgo
directory.
To create a new compiler JAR execute ...
lein with-profile bootstrap fgoc
lein uberjar
... which will compile the compiler and generate a JAR file
target/funcgo-x.y.z-standalone.jar
You can run the unit tests by doing
lein do run test, midje
Funcgo is built on the folder of giants.
Thanks to Rich Hickey and the Clojure contributors, to Thompson, Pike, and Griesemer and the Go contributors, and to Mark Engelberg for the instaparse parsing library.
The Funcgo code is distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.
Funcgo Documentation by Eamonn O'Brien-Strain is licensed under a Creative Commons Attribution 4.0 International License.