Skip to content

eobrain/funcgo

Repository files navigation

Tour - FAQ - Reference

funcgo

Funcgo is a compiler that converts Functional Go into Clojure, to run on the JVM or as JavaScript.

Try It Out

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).

Quick Start

1. Set up Clojure development environment.

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

2. Write Funcgo

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!

Next Steps

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]))

Not Using Leiningen?

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.

Introduction to the Funcgo Language

Why a new language?

The goal of Funcgo is to combine the readability of the Go language with the semantics of Clojure.

  1. 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.

  2. 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.

Examples for Clojure Programmers

In this section are Funcgo versions of some of the Clojure examples from the Clojure Cookbook.

Defining and using a function

		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).

Adding a file header

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.

Using symbols from other packages

        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!"

Specifying string escapes and regular expressions

        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.

Concatenating strings

        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.

Specifying local (immutable) variables

		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.

Specifying global (mutable) variables

		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.

Using vectors

        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.

Getting cleaner syntax using infix notation

        []  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.

Specifying keyword and dictionary literals

		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.

Combining infix and functional programming

        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.

Calling function variadically

        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).

Inter-operating with Java or JavaScript

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.

Examples for Go Programmers

In this section are Funcgo versions of some of the Go examples from the A Tour of Go.

Placement of constant definitions

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.

Go primitive types

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.

Optional return

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.

Data structures

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.

Building and Development

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

Thanks

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.

License

The Funcgo code is distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

Creative Commons License
Funcgo Documentation by Eamonn O'Brien-Strain is licensed under a Creative Commons Attribution 4.0 International License.