Reading Goal
Goal code is processed line by line (but you can put more than one "line" on a line), and once tokenized, Goal code is parsed based on the parts of speech of the code forms.
Lines of Code
In Goal, there are two ways to denote a "line" of code:
- New lines
- Semicolons
;
Thus the following are equivalent:
a:2;b:3;a+b
/ ...is equivalent to these three lines of code...
a:2
b:3
a+b
/ ...and evalutes to...
5
⭐️ Within a single line, forms will be parsed and evaluated right-to-left, but whole "lines" (separated by semicolons) are processed left-to-right. To read a line of Goal code, first scan for top-level semicolons to determine these boundaries.
Not only can semicolons stand in for new lines, but when it comes to passing arguments to functions, new lines can stand in for semicolons.
+[1;2;3;4;5]
/ ...is equivalent to...
+[1
2;3
4
5
/ ...and evaluates to...
15
⚠️ Caution: Don't add extra semicolons when separating arguments with new lines:
/ Probably not what you meant:
+[1;
2;
3;
4;
5]
/ ...evaluates to...
+[1;;2;;3;;4;;5]
I hit this many times as a beginner, refactoring one-liners into multi-liners.
Parts of Speech
Goal, at parse time, differentiates syntactic parts of speech, namely verbs, nouns, and adverbs.
You must internalize these parts of speech and how they interact at parse time, so that you can read Goal programs and understand how the parts fit together. It's also important to remember that these are concerns at reading/parsing time, orthogonal to how these forms are evaluated at runtime.
Nouns
As stated in Goal's documentation, a noun is "any expression used as a value in an operation or statement." Examples:
1 / Number
3 4 5 / Array
"a string" / String
..[a:5;b:10] / Dictionary
{x+y} / User-defined function (lambda)
That last example might come as a surprise. You might be thinking that a function would be a verb, but don't conflate parse-time part of speech (verb) with runtime type (function). All verbs act as functions at runtime, but not all functions are syntactically verbs: user-defined functions (lambdas) are syntactically nouns. We'll dig into this distinction more in the Verbs section below.
Although built-in primitives like + are verbs, we can nominalize them (turn them into nouns) by surrounding them with parentheses:
/ * as Verb
1,*,2
1 2
/ Nominalized
1,(*),2
(1
*
2)
Verbs
Goal uses ASCII symbols for most of its built-in primitive functions and these are syntactically verbs:
!#$%&*+,-.:<=>?@_|~
Goal also has verbs that are spelled with non-symbol characters:
abs atan cos csv error eval exp firsts
in json log nan ocount panic path
rotate round rx sign sin sqrt sub uc utf8
When Goal parses a program and finds a verb, it expects that it will have one of:
- A single argument to its right (e.g.,
!5) - Two arguments, its first to its left and its second to its right (e.g.,
2-3) - More than two arguments, using m-expression syntax (e.g.,
+[1;2;3]
A verb that only accepts one argument is called monadic; verbs that accept two are called dyadic.
User-defined functions (lambdas) are nouns
Goal allows the end-user to define her own functions and assign them to variables spelled with non-symbol characters, but those are syntactically nouns.
It's so important and a common source of confusion for new learners that I'll repeat this:
⭐️ Built-in primitive functions are syntactically verbs, but user-defined functions are syntactically nouns. Built-in primitive functions are syntactically verbs, whether they're spelled with symbols or non-symbol characters; user-defined functions, which can only be assigned to variables spelled with non-symbol characters, are always syntactically nouns.
When a user-defined function takes two arguments, it cannot be written using the infix syntax that dyadic verbs support. If a user-defined function is monadic, it can be called with a single argument to its right (without brackets), but only if it's syntactically unambiguous. In an expression like a!5 where a is a user-defined monadic function, Goal will treat a (a noun) as the left argument to ! (a verb), resulting in an error.
💡 Tip: You can always use m-expression syntax with verbs and nouns to invoke them with arguments, you don't have to use prefix or infix syntax. This can be especially helpful if the above peculiarities of invoking nouns are a bit too much to keep in your head at the start.
Adverbs
Adverbs modify verbs to make new verbs. In a Goal program it's the only way to make new verbs.
Here are Goal's adverbs:
'`´/\
Adverbs are left-associative: they bind to the form on their left. That left argument, at parse time, might be a verb or a noun, but at runtime it will always be something invokable and it produces a verb.
See how / (which takes the verb on its left and creates a functional left fold) is used in the following examples:
/ Sum
+/2 3 4
9
/ Dyadic sum
1+/2 3 4
10
/ Product
*/2 3 4
24
/ Difference
-/2 3 4
-5
Goal language extensions can define new verbs
As a final point of complexity regarding parts of speech (but also part of the power of the Goal language as a platform), Goal is designed not only to be embedded in Go programs, but extended by users via Go. You can see what extensions are active in your Goal interpreter by evaluating rt.get"v", which on my machine currently returns "v1.3.0 +os +math +archive/zip +encoding/base64 +ari v0.1.4" (because I've written an extension called ari that I use as my daily driver). Goal extensions can define new verbs written with non-symbol characters, so you'll also need to familiarize yourself with the verbs your extensions define.
Exercises
Rewrite the following Goal programs using m-expression syntax. New constructs will not be explained; you're expected to have worked through Goal's documentation by this point.
2*3+4
Answer
*[2;+[3;4]]+/!101
Answer
(/)[+;![101]]sq:{x*x}; sq 4
Answer
sq:{x*x}; sq[4]