[incomplete]
Source files names must end with either .go
(if to be run on the
JVM) or .gos
(if to be run in JavaScript).
A file called name.go
must start with package name
followed by
optional import
statements, an optional const
declaration, and a
list of expressions.
package hello
println("Hello World")
As a minimal example, the code above is in a file hello.go
.
package larger
import (
test "midje/sweet"
)
test.fact("can concatenate strings",
{
greeting := "Hello "
name := "Eamonn"
str(greeting, name)
}, =>, "Hello Eamonn"
)
test.fact("can use infix when calling two-parameter-function",
{
greeting := "Hello "
name := "Eamonn"
greeting str name
}, =>, "Hello Eamonn"
)
The above slightly longer example is in a file called larger.go
.
In Funcgo most things are expression, including constructs like if
statements that are statements in Go.
smaller := if a < b {
a
} else {
b
}
smaller
=> 55
The above treats an if
-else
as an expression, setting smaller
to either a
or b
.
digits := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
squares := for d := lazy digits {
d * d
}
squares
=> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
And here the value returned from a for
loop is actually the vector
of the values generated on each iteration. (Called a list
comprehension in some language.)
Unlike some languages, newlines can be significant in Funcgo. This happens when you have multiple expressions inside curly braces or at the top level of the source file.
if a < b {
println("Conclusion:")
println(a, "is smaller than", b)
}
=>
Conclusion:
55 is smaller than 66
For example in the if
statement above the two println
expressions
must be separated by a newline. (In this case we are ignoring the
values returned by the two expressions, the latter of which is
returned by the if
. Instead we are using these expressions for
their side-effects:
if a < b { println("Conclusion:"); println(a, "is smaller than", b) }
=>
Conclusion:
55 is smaller than 66
If you really want to, you can use semicolons instead of newlines as shown above, but for readability I recommend you avoid semicolons.
You can directly use anything provided by the clojure.core API without further specification. However if you want to use anything from any other library you have to explicitly import it at the top of your file. Depending on what you are you importing you use one of these forms.
-
import
(the most common case) for Clojure or Funcgo librariesimport ( test "midje/sweet" fgo "funcgo/core" fgoc "funcgo/main" "clojure/string" ) ... test.fact(... ... fgo.Parse(... ... string.trim(...
As shown above an
import
statement can import multiple Clojure or Funcgo libraries. It specifies the library as a string of slash-separated identifiers. Each library can be preceded by a short name by which the library is referred to in the body of the code. If no short name is specified, then the last identifier in the library name is used (for examplestring
in the last example above).In the body of the code any function or variable referenced from an imported library must be qualified by short name.
import( _ "hiccups/runtime" "fgosite/code" )
Sometimes you want to import a library only for the side-effect of importing it. To avoid getting a compile error complaining about an unused import, you can use
_
as shown above.import "clojure/string"
If you are only importing a single library you can use a short form without parentheses as shown above.
-
import type
for JVM classes and interfacesimport type ( java.io.{BufferedWriter, File, StringWriter} jline.console.ConsoleReader ) ... ... = new StringWriter() ... func compileTree(root File, opts) { ...
JVM types, such as defined in Java (and sometimes in Clojure), have a different syntax for importing as show above. Each type must be explicitly listed, though types in the same package can expressed using the compressed syntax shown above for
java.io
.Once imported, such types can be simply referenced by name, without qualification.
Types from the base
java.lang
API do not need to be imported. -
import macros
(when targeting JavaScript only) for importing ClojureScript macrosimport macros ( hiccups "hiccups/core" ) ... func<hiccups.defhtml> pageTemplate(index) { ...
When targeting the JavaScript runtime you sometimes need to import macro definitions in a special way as shown above.
-
import extern
(advanced use only) needed when creating macrosimport extern( produce bakery ) ... ... quote(produce.onions) ... ...
Occasionally you will need to refer to symbols in libraries that you cannot import. As shown above you can declare them as
extern
libraries.
In Funcgo you should use constants for any value that is set once and never changed.
...
{
cljText := core.Parse(inPath, fgoText)
strWriter := new StringWriter()
writer := new BufferedWriter(strWriter)
cljText writePrettyTo writer
strWriter->toString()
}
As shown above, constants are defined using the :=
operator.
There can only be a single contiguous group of constant declarations in
each block of expressions, and they must appear at the top of the
block. A block is either to top-level code if a file after the
import
statements, or some newline-separated expressions surrounded
in curly braces. The constants you define in a block can only be used
inside that block.
...
{
const (
cljText = core.Parse(inPath, fgoText, EXPR)
strWriter = new StringWriter()
writer = new BufferedWriter(strWriter)
)
cljText writePrettyTo writer
strWriter->toString()
}
As shown above, there is also an alternative syntax using the const
keyword. It too must be at the beginning of a block.
...
{
const consoleReader = new ConsoleReader()
consoleReader->setPrompt("fgo=> ")
consoleReader
}
If there is a just a single constant, you can drop the parentheses.
First, lets look at an ordinary (non-tail) recursion
func sumSquares(vec) {
if isEmpty(vec) {
0
} else {
x := first(vec)
x * x + sumSquares(rest(vec))
}
}
sumSquares([3, 4, 5, 10])
=> 150
The above example shows the sumSquares
function that returns the sum
of squares of a vector of numbers. It is implemented as the square of
the first element plus the recursive sum of squares of the rest of the
vector. This works fine for small vectors but for large vectors it
could cause one of the infamous stack overflow exception.
func sumSquares(vec) {
func sumSq(accum, v) {
if isEmpty(v) {
accum
} else {
x := first(v)
recur(accum + x * x, rest(v))
}
}
sumSq(0, vec)
}
sumSquares([3, 4, 5, 10])
=> 150
The above example avoids this stack overflow by using the special
recur
syntax to recursively call the containing function. However
recur
must be in tail position, which means that the function
needs to be re-arranged to add an inner recursive function that passes
down an accumulator variable. This version can be called on
arbitrarily long vectors without blowing your stack.
There is also an equivalent way of getting the same result without
using an inner function by using instead the loop
construct.
func sumSquares(vec) {
loop(accum=0, v=vec) {
if isEmpty(v) {
accum
} else {
x int := first(v)
recur(accum + x * x, rest(v))
}
}
}
sumSquares([3, 4, 5, 10])
=> 150
The loop
construct declares a set of iteration variables and sets
their initial values. The recur
calls the nearest enclosing loop
passing in updated iteration variables (which are actually constants
in each iteration). The number of parameters in the recur
must match the
number of parameters in the loop
.
loop(vec=[], count = 0) {
if count < 10 {
v := vec conj count
recur(v, count + 1)
} else {
vec
}
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
And above is another simpler example of using loop
, starting with an
empty vector and using the conj
operator to add numbers to it.
Everywhere you can put an expression you can put a newline-separated sequence of expressions in a curly braces block. The result of the last expression is returned as the result of the block.
product := {
logging.info("doing the multiplication")
100 * 100
}
product
=> 10000
Above is an example of the product
constant being assigned the value
of the block, with the multiplication expression being preceded by a
logging statement that is executed only for its side-effects.
There are three forms of switch statement.
switch count(remaining) {
case 1: {
[expr] := remaining
str(acc, " :else ", expr, ")")
}
case 2:
typeCase() str ")"
default:
recur(typeCase(), 2 drop remaining)
}
In the first form, shown above, the switch takes an expression and
matches execute whichever of its case
sections match the result of
the expression. This is the more efficient form of switch because the
dispatch to a case happens in constant time, but it has the restriction
that the case
sections must have compile-time constants values.
switch {
case isNil(t):
new TreeNode(v, nil, nil)
case v < VAL(t):
new TreeNode(VAL(t), L(t) xconj v, R(t))
default:
new TreeNode(VAL(t), L(t), R(t) xconj v)
}
The second form, shown above, is more general. There is no expression
beside the switch
but instead each case
has an arbitrary Boolean
expression. In general this form is slower because the dispatch
happens in linear time, each case expression being evaluated in turn
until one returns true.
The third form is the type switch using the .(type)
suffix to indicate that we are switching on the type, and using
type names in the case statements.
func plus(a, b) {
switch a.(type) {
case Number: a + b
case String: a str b
case Iterable: vec(a concat b)
default: str("Unknown types for ", a, " and ", b)
}
}
[
2 plus 3,
0.5 plus 0.75,
[P, Q] plus [R, S, T],
"foo" plus "bar",
FOO plus BAR
]
=> [
5,
1.25,
[P, Q, R, S, T],
"foobar",
"Unknown types for :foo and :bar"
]
In the above example we define a plus function that does different operations depending on the types of the first argument. (A more robust version would check both arguments.)
To use static methods or fields from a Java class you use the ::
operator after the Java class name (which you should import using the
import type
syntax unless it is in the java.lang package).
2 * Double::MAX_VALUE // => Double::POSITIVE_INFINITY
Integer::parseInt("-42") // => -42
Math::round(2.999) // => 3
13 Integer::toString 2 // => "1101"
The first example shows how a static field is uses. The remaining three are static method invocations, with the last one showing how you can use infix notation to invoke a static method that takes two parameters.
Clojure allows characters in identifiers that are not allowed in Funcgo identifiers, therefore to allow inter-operation Funcgo identifiers are mangled like so:
- camel-case is converted to dash-separated:
fooBarBaz
→foo-bar-baz
FooBarBaz
→Foo-bar-baz
is
prefix is converted to?
suffixisEqual
→equal?
mutate
prefix is converted to!
suffixmutateSort
→sort!
- underscore prefix is converted to dash prefix
_main
→-main
However, identifiers referring to Java entities are not mangled.
These are any identifiers in import type
statements, anything before
or after a ::
and anything after a ->
.
You can avoid mangling by surrounding arbitrary closure code in backslashes:
origDispatch := \pprint/*print-pprint-dispatch*\
The above example uses this escaped identifier syntax to refer to the
pprint/*print-pprint-dispatch*
Clojure identifier which has the
"earmuff" characters not allowed by Funcgo.
You can redefine any of the built-in operators.
package operator
import test "midje/sweet"
exclude ( ^, + )
func ^(x, y) {
Math::pow(x, y)
}
func +(x, y) {
x str y
}
test.fact("Can redefine existing operators",
2 ^ 3, =>, 8.0,
10 ^ 2, =>, 100.0,
"foo" + "bar", =>, "foobar"
)
The above example shows how you can simply use an operator as the name of a two-parameter function. The precedence of the built-in operator is preserved when you use it in the normal infix way.
Note above the use of the exclude
directive, which prevents
importing the given operators from the built-in package. Without the
exclude you will get warnings about functions being redefined.
func \**\(x, y) {
Math::pow(x, y)
}
test.fact("Can use new operators",
2 \**\ 3, =>, 8.0,
10 \**\ 2, =>, 100.0
)
If you want to define your own operators that are not existing built-in operators then you will have to use the Clojure escape syntax to define and use them, as shown above.
// Operations on matrices, stored as sequences of row vectors
package matrix
import "clojure/core"
exclude ( +, * )
// Begin private functions
func colCount(m) { count(first(m)) }
func dotProduct(v1, v2) {
core.+ reduce map(core.*, v1, v2)
}
func vecSum(a, b) { map(core.+, a, b) }
// Begin exported functions
func +(m1, m2) { map(vecSum, m1, m2) }
func Transpose(m) {
firstColumnT := first map m
if colCount(m) == 1 {
[firstColumnT]
} else {
firstColumnT cons Transpose(rest map m)
}
}
func *(m1, m2) {
for m1row := lazy m1 {
for m2col := lazy Transpose(m2) {
m1row dotProduct m2col
}
}
}
Above is an example of a simple matrix package supporting matrix addition, transpose, and multiplication. The operators are exported in the same way as capitalized functions for use in other packages. However, note that in other packages they must be qualified by the package as shown below.
import (
...
"funcgo/reference/matrix"
)
...
a := [[3, 4]]
b := [
[5],
[6]
]
a matrix.* b
=> [[39]],
{
m := [
[1, 2, 3],
[4, 5, 6]
]
mT := [
[1, 4],
[2, 5],
[3, 6]
]
m matrix.* mT
=> [
[14, 32],
[32, 77]
]
If possible you should use consts because they are immutable, but sometimes you need vars. These are thread-local mutable storage.
The case of the name is significant. If it begins with an upper-case letter then it is exported and visible globally, otherwise it is private to the file it is declared in.
var initialBoard = [
[EE, KW, EE],
[EE, EE, EE],
[EE, KB, EE]
]
They use a syntax similar to the const
construct. They can be
without parentheses as shown above.
var (
pp = 111
qq = 222
)
pp + qq
=> 333
Or it can use the grouped version of the syntax as shown above.
var rr = 111
var ss = 222
rr + ss
=> 333
Alternatively each var can be separately declared as shown above.
Unlike consts, var declarations do not have to be at the beginning of a curly-bracket block.
var tt int = 111
var uu string = "foo"
uu str tt
=> "foo111"
If you want you can add type hints as shown above.
start := if suffixExtra == "" { SOURCEFILE } else { NONPKGFILE }
The above example shows the if-else expression.
if cmdLine(ERRORS) {
println(cmdLine(ERRORS))
}
If the else
part is omitted the if
expression returns nil when
the condition is false, though in the example above this is ignored.
if met := meta(o); met {
print("^")
if count(met) == 1 {
if met(TAG) {
origDispatch(met(TAG))
} else {
if met(PRIVATE) == true {
origDispatch(PRIVATE)
} else {
origDispatch(met)
}
}
} else {
origDispatch(met)
}
print(" ")
pprint.pprintNewline(FILL)
}
Finally, the first line of the example above shows another format,
where a constant met
is set and tested as part of the if
line.
This constant can be used in the body of the if expression. The above
example also emphasizes the fact that there is no "else-if" construct
-- you must use nested if-else expressions (or alternatively use the
switch expression).
There are three types of for
expression.
fib := [1, 1, 2, 3, 5, 8]
fibSquared := for x := lazy fib {
x * x
}
fibSquared
=> [1, 1, 4, 9, 25, 64]
The "lazy" version returns a sequence that is the same length as the
input sequence (given after lazy
), with the body of the loop being
executed for each member of the input sequence.
fib := [1, 1, 2, 3, 5, 8]
fibSquared := func(x){ x * x } map fib
fibSquared
=> [1, 1, 4, 9, 25, 64]
As an aside, you can get the same result as the lazy for
using the
map
function as shown above.
fib := [1, 1, 2, 3, 5, 8]
for x := lazy fib {
print(" ", x)
}
=> ""
The reason that this construct is called "lazy", is shown in the example
above where the body of the for
does not return a value, but instead
has a side-effect (writing on the console). In this case the print
is not executed.
fib := [1, 1, 2, 3, 5, 8]
for x := range fib {
print(" ", x)
}
=> " 1 1 2 3 5 8"
To cause such a side-effect body to be executed you can use the "range" form of the for loop as shown above. It is not lazy, but will execute the body of the loop for each member of the input sequence.
for x := times 10 {
print(" ", x)
}
=> " 0 1 2 3 4 5 6 7 8 9"
The final form of the for loop is the "times" version, which executes
its body the number of times specified after times
as shown above.
Funcgo supports exceptions in a way similar to Java.
eval := try {
main := loadString(clj(id))
withOutStr(main())
} catch Throwable e {
str(e)
}
The above example shows an example of catching an exception. A
difference from Java is that try-catch is an expression, thus in the
above case if the exception is caught the eval
constant will be set
to the value of str(e)
.
try {
throw(new AssertionError("foo"))
} catch OutOfMemoryError e {
"out of memory"
} catch AssertionError e {
"assertion failed: " str e->getMessage()
} finally {
"useless"
}
=> "assertion failed: foo"
The above example shows a more complete example, including how you can
throw your own exceptions. Note, that although the finally
expression is evaluated, its result is ignored so it is not useful in
this case.
<-mutex // grab mutex
try {
i := dangerous->get(0)
dangerous->set(0, i + 1)
} finally {
mutex <- true // release mutex
}
Above is an example of a more useful application of finally
where we
are depending on the side-effect of evaluating its expression.
c1 := make(chan, 1)
c2 := make(chan, 1)
thread {
Thread::sleep(10)
c1 <- 111
}
c2 <- 222
select {
case x = <-c1:
x * 100
case x = <-c2:
x * 100
}
=> 22200
The example above uses the same syntax as Go, where for a channel c
the operation <-c
is taking from a channel (blocking if necessary
until input arrives) and c <- x
is sending the value x
to the
channel.
The thread
construct executes its contents in a separate thread,
running in parallel with the code following the thread construct.
The select
construct allows you to block on multiple asynchronous
channel operations, such that the first one that unblocks will
activate.
When targeting JavaScript however we are restricted because
JavaScript is single-threaded, instead you have to use a different
syntax (using <:
instead of <-') for channel operations, and
goinstead of
thread`:
c1 := make(chan, 1)
c2 := make(chan, 1)
go {
for i := times(10000) { var x = i }
c1 <: 111
}
go {
c2 <: 222
}
<-go {
select {
case x = <:c1:
x * 100
case x = <:c2:
x * 100
}
}
=> 22200
All the operations that use <:
must be lexicallyinside a go { ... }
block as shown above.
The go
block has an effect similar to thread
except that on the
JVM it shares a pool of threads, and in JavaScript it is implemented
with some clever code reorganization rather than with threads. Even
on JVM where thread
is supported, go
is often a better choice
because it is more lightweigth and you can feel free to invoke a large
number of go
blocks.
c1 := make(chan, 1)
c2 := make(chan)
thread {
Thread::sleep(20)
c1 <- 111
}
thread {
Thread::sleep(10)
<-c2
}
select {
case x = <-c1:
x * 100
case c2 <- 222:
"wrote to c2"
default:
"nothing ready"
}
=> "nothing ready"
Finally the example above shows some more features.
If there is a default
clause in the select
and all the case
clauses are blocked, then it will execute instead.
Finally note that this example has both types of case
clauses, those
writing to channels and those reading from channels, both of which can
block.
str("foo", "bar")
=> "foobar"
You can call a function of two arguments in the normal prefix format
of f(a,b)
.
"foo" str "bar"
=> "foobar"
Alternatively you can call such a function in an infix format
of a f b
. The infix function name must be separated by double-spaces.
This table shows all the built-in operators and how they group. The ones at the top bind most tightly.
- unary expression
*
/
%
<<
>>
&
&^
+
-
|
^
==
!=
<
>
<=
>=
&&
||
- infix function call
^a * b // => (^a) * b,
a * b - c // => (a * b) - c
a + b < c // => (a + b) < c
a < b && b < c // => (a < b) && (b < c)
p && q || r // => (p && q) || r
p || q str r // => (p || q) str r
You can declare multiple constants on the left-hand-side of the =
and put a vector on the right-hand-side. Thus "unpacks" the vector
assigning each element to the corresponding constant.
vec := [111, 222, 333, 444]
[a, b, c, d] := vec
b
=> 222
For example, above we unpack the vector vec
, so that constant b
ends up with the value 222
.
func theSecond([a, b, c, d]) {
b
}
theSecond(vec)
=> 222
This also works for function arguments, where above we have used a function to extract the second element from the vector.
vec := [111, 222, 333, 444]
[first, rest...] := vec
rest
=> [222, 333, 444]
For variable-length vectors you can use ellipses ...
after the
constant to match it to the remaining part of the vector. So for
example, above first
gets the the first element in the vector and
rest
gets the remaining elements.
dict := {AAA: 11, BBB: 22, CCC: 33, DDD: 44}
{c: CCC, a: AAA} := dict
c
=> 33
You can also destructure dicts using the syntax shown above, where on
the left-hand-side each match is specified as constant:
key.
func extractCCC({c: CCC}) {
c
}
extractCCC(dict)
=> 33
Dict destructuring also works in function parameters as shown above.
planets := [
{NAME: "Mercury", RADIUS_KM: 2440},
{NAME: "Venus", RADIUS_KM: 6052},
{NAME: "Earth", RADIUS_KM: 6371},
{NAME: "Mars", RADIUS_KM: 3390}
]
[_, _, {earthRadiusKm: RADIUS_KM}, _] := planets
earthRadiusKm
=> 6371
You can nest these destructurings to any depth. For example the above
example plucks the earthRadiusKm
constant from two-levels down inside
a vector of dicts. We are using the convention of using the _
identifier for unused values.
Funcgo is a gradually typed language. Unlike Go, you do not need to specify any types, and in most cases the Clojure runtime can figure them out. However sometimes you may get a runtime warning from the Clojure runtime that it is using reflection because it cannot figure out your types. To allow your code to run more efficiently you can add types using the same syntax as the Go language.
In practice, you usually only need to add types in a very few places in your code.
consoleReader ConsoleReader := newConsoleReader()
Above is an example of a constant being declared of ConsoleReader
type so future uses of the constant are more efficient.
func compileFile(inFile File, root File, opts) {
And above is an example of the first two of the three function
parameters being declared to be of type File
.