I am just writing down ideas. I have many pages of notes on things i may or may not want to put into Oot. Please note that my philosophy is, whenever i take notes for myself, if there is no reason to keep them private, to put them on my website; these notes are therefore here, not because they are intended to be readable by anyone other than me, but just as notes to myself. Over time i hope to evolve them into something readable and after that i'll remove this disclaimer.
At this point, the design is unfinished; there is no coherent concept of a language called 'Oot', just a bunch of ideas towards such a proposal. There is no timeframe to actually implement this, and it most likely will never get finished.
To be clear, i repeat myself: there is no such thing as Oot. Oot has neither been designed nor implemented. These notes about 'Oot' contain many conflicting proposals.
This document is very out of date and no longer serves as a good introduction to the evolving language proposal.
You may want to look at proj-oot-whyOot.
There are a bunch of other files in this folder with more details and notes on various parts of Oot: proj-oot.
Oot is a readable, massively concurrent, programmable programming language.
Oot is a general-purpose language that particularly targets the following application domains:
Why not Oot?
Oot aspires to combine the readability and ease of Python, the functional laziness of Haskell, the commandline suitability of Perl, the straighforwardness of C, the metaprogrammability of Lisp, and the simplicity of BASIC (well, not quite), with massive concurrency.
print "Hello world!"
In this section we briefly explain the basics of Oot. Our purpose is to give you a sense of Oot, and also the background you'll need for understanding the examples in the Tour.
If you are coming from another language, you may be surprised by the following aspects of Oot's syntax and builtin functions:
Unlike e.g. Python, the AMOUNT of whitespace does not matter in Oot; but the PRESENCE or ABSENCE of whitespace, and the TYPE of whitespace (whether it is a space, a newline, or a blank line) does matter.
Only punctuation affects the way things are parsed; there are no alphanumeric 'keywords'.
A person reading code does not have to look up any function or macro definitions to see how things will parse (except of course within metaprogramming constructs that essentially pass a string to something like a custom 'eval').
Identifiers can consist of alphanumeric characters and dashes. The functions of a string of alphanumeric characters and dashes varies depending on case:
Grouping is supplied by parenthesis (), blocks {}, and graph constructors [].
Blocks are constructor for first-class (lists of) expressions.
The precedence of infix operators can be determined by the characters in them (eg you don't have to look up the function definition of custom infix operators in order to determine how they parse).
Any Oot program can be expressed in a single line. Newlines usually represent implicit semicolons (except when the line ends with a '\', or when all parentheses have not yet been closed). A region of text bounded by square brackets or blank lines is called a 'paragraph'. Paragraphs implicitly close open parentheses. Eg:
print 1 + 1 print (1 + 1) print (1 + 1 print 2
is equivalent to:
print 1 + 1; print (1 + 1); print (1 + 1); print 2;
In Oot, it sometimes matters whether an operator is separated from its argument by a space (freestanding), or whether it is 'attached', and if it is attached, whether is appears on the left or the right side of its argument. Often prefix versions of operators are something like a 'constructor', and the postfix version is the corresponding 'destructor'. For example:
&x ;; like C's &x x& ;; like C's *x
Everything is an expression. The last line of a function is its (primary) return value. The last line of a branch within an 'if' statement is its return value.
To apply the function 'add' to 1 and 'x' and store the result in 'result':
result = add 1 x
(function application is by juxtaposition)
Partial function application is achieved just by not giving all required arguments:
increment = add result = increment x result == increment x == (add 1) x
(functions are curried and left-associative)
Functions can have keyword arguments:
result = bake-n-pies 3 variety="pumpkin"
Defining functions:
TODO
Atomic literals consist of ints (3), floats (3.0), strings ("hi"), booleans (TRUE), and NIL.
Some Oot implementations may support Unicode in source files. In this case, Unicode is permitted only within string literals.
TODO
Lisp is a homeoiconic language built upon lists. Oot is a near-homeoiconic language built upon labeled graphs.
In this section we briefly highlight some of the features of Oot.
Here we describe, in detail, the syntax of Oot.
Parsing occurs as a separate stage prior to the rest of Oot compilation/execution. There are scoped metaprogramming constructs that allow custom parsing of individual, clearly-marked strings, lines, or blocks within code, and there is a per-file 'source filter' preprocessing facility, but there are no metaprogramming constructs that can alter the behavior of Oot parsing in a non-local way. This guarantees that if you are reading Oot code outside the scope of the above-mentioned metaprogramming constructs, you can be assured that it parses in a standard way.
The precedence of operators is determined by which symbols they are composed of; although users can define custom binary operators, you never have to look up a function definition just to see how to source code will parse.
Echoing [1], one might say that Oot's parser has syntax defined in terms of characters, and the Oot language has syntax defined in terms of symbols, graphs, and literals. The parser is available to the user via the function ootparse, which reads the next form from a stream, and returns the objects represented by that form.
Any Oot program can be expressed as a single line, by using ';'s in place of newlines, by closing all parentheses explicitly instead of using paragraphs, and by using "\n"s in place of multiline strings.
Integer: 3
Floating point number: 3.0, INF, NAN
String: "Hello!"
String, with interpolation and newline substitution: "Hello {name}!\n"
String, raw: r"Hello, i can include the '{' and \n characters!"
Multiline string ('HERE documents'): """ This is a string named {name} that is 3 lines long. """
Multiline string with custom delimiter: """xyz This is a string named {name} containing """triple quotes""" that is 3 lines long. """xyz
Multiline raw string with custom delimiter: r"""xyz This is a string containing """triple quotes""" and a { and a \n and which is 3 lines long. """xyz
In addition to 'r', string delimiters may be prefixed by '#' and then a string, which indicates that the string is a literal that is to be passed to a macro.
keyword: THIS-IS-A-KEYWORD
A keyword literal is just like a string literal, except (a) you don't have to enclose it in double-quotes, (b) the implementation is encouraged to represent it internally as an integer to aid performance, (c) translation tables can't contain mappings for keywords. Although you can try to coerce keyword values to strings and print them out, this is really only for debugging and most implementations will just print out the internal integer representation unless this is a debug build.
nil: NIL
booleans: FALSE, TRUE (synonyms: F, T)
If your Oot implementation supports Unicode source files, non-ASCII characters are ONLY allowed within strings.
Oot has a facility to attach a separate file providing translations of strings. So, for instance, in the source code you could write (x = "Hello!"), and then in the translation file for French you could map "Hello!" to "Bonjour!", and then at runtime, if Oot is in a French locale, x will be set to "Bonjour!" when the line (x = "Hello!") is encountered in the source code.
The meaning of a string consisting of alphanumerics-with-dashs depends on its case:
Lowercase: an ordinary identifier
Uppercase: a keyword literal (see above)
Capitalized: an annotation
mixedCase: the upper-case parts are actually macro operators (we call these 'attached macros') operating on the surrounding lowercase words. eg "mixedCase" would be read as a macro 'C' applied to 'mixed' and 'ase'.
Any alphanumerics-with-dashs beginning with one dash is 'private' and will not be exported or directly accessible from the containing module (although the containing module can choose to return it from a function called from outside of the module). E.g. -this-is-a-private-identifier
Any alphanumerics-with-dashs beginning with two dashes are 'reserved' for the use of the Oot language. E.g. --ROOT. If you see one of these, be aware that although it will parse like any other keyword, semantically the language might treat it specially in some way.
When you see alphanumerics-with-dashs smashed together with other punctuation character(s) without intervening spaces, then the punctuation characters act as operators or modifiers acting upon the alphanumerics-with-dashs they are attached to. A given (string of) punctuation character(s) may have three DIFFERENT MEANING depending on whether:
Generally the prefix and the postfix meanings are related; they are usually rough inverses of some sort, where prefix can be imagined as going 'up' (constructing) and postfix goes back 'down' (deconstructing) in some very abstract space. Sometimes the composition of a postfix with the corresponding prefix is an identity. For example, y = &x takes a pointer or reference to x, and y& defererences the pointer in y; (&x)& === x.
Strings of commas and strings of puntuation containing =s operate the same whether they are attached or unattached to their neighbors.
Attached punctuation binds tighter than unattached operators.
Any from of whitespace separates non-whitespace. Note that the meaning of punctuation may change dependent on whether it is attached to something, or separated from it by a space.
Oot usually inserts semicolons at every newline. There are two exceptions:
In debug mode, it is possible to mark a certain dynamic scope of the program so that expressions in that scope which were delimited by newlines, as opposed to explicit semicolons, automatically print out the value of the expression on that line.
A region of text which does not contain blank lines and which is surrounded by either one or more blank lines and/or the boundaries of a block is called a 'paragraph'. A blank line is two newlines with no non-whitespace characters in between them (excluding comments). The only function of paragraphs is that, upon reaching the end of a paragraph, any levels of parenthesis within the current block scope which have not yet been closed are implicitly closed (just as if the blank line contained a string of closing parenthesis of length sufficent to balance the parenthesis in this block); and a semicolon is inserted at the end.
For example, each of the following is equivalent to "print (1+1);":
print (1+1) print (1 + 1) print (1 + 1
Two adjacent semicolons, followed by a space, has the effect of a newline and begins a comment that continutes until the end of the line. Two adjacent semicolons, followed by a non-whitespace string, begins a comment that continutes until the same delimiter is encountered, even over multiple lines; note that no newline is inserted. For example:
print 1+1 ;; nice day today
print 1+ ;;xyz one ;;xyz 1
print 1+ ;;xyz man this is
quite a long
comment
;;xyz 1
Comments are removed at an early stage of parsing and have no further effects besides those mentioned above. Comments within strings, or within raw text being fed to metaprogramming facilities, are not removed.
=== Grouping ===
Aside from whitespace, there are three grouping constructs:
* (): parentheses affect the order of evaluation of expressions (and also enter code context from within a data constructor, see 'Data context and code context' below)
* {}: curly braces construct first-class representations of expressions and lists of statements, and in addition serve as a scope for variables and certain metaprogramming facilities. Note that the boundaries of a block always constitute 'paragraph' boundaries and hence unbalanced parentheses are implicitly closed at the end of each block (see 'whitespace', above)
* []: square brackets are data constructors
=== Commas (convenience only; not in Oot Core) ===
A single comma has the lowest precedence aside from = and multiple commas, and has the effect of placing an implicit data constructor around its area of precedence, and surrounding each side of itself with implicit parens, unless this area is itself delimited on both sides by an explicit data constructor. In addition, within this implicit or explicit data constructor, it serves as a shorthand for binding each comma-delimited item to a node labeled by its ordinal position within the comma-delimited list, starting with 0. For example, "a + b, c = f x" is the same as "a + b,c = f x" is the same as "[(a + b), (c)] = f x"; and all are the same as "[0=(a+b); 1=(c);] = f x".
A string of multiple commas has different effects depending on whether it is found in code context or in data context. In code context, it does nothing except by virtue of its precedence (a double comma's precedence is one step lower than a single comma, and each additional comma is one step lower); eg "f x ,, f y == (f x) (f y)"; eg "f x ,, g z ,,, f y == ((fx) (gz)) (f y) == f x,,g z,,,f y"
In data context, a string of multiple commas creates a multidimensional array. The first dimension of the array is delimited by single commas, the next dimension by double commas, etc.
todo:
commas for arrays is shorthand for:
[a,b] is [0=a; 1=b]
multidim arrays are shorthand for:
[a,b,,c,d] is [0,0=a, 0,1=b, 1,0=c, 1,1=d] is [[0=0,1=0]=a, [0=0,1=1]=b, [0=1,1=0]=c, [0=1,1=1]=d]
todo:
note (and think about) the idea of using node literals as nodenames for multidims
=== Expressions ===
Every line and block is an expression in Oot. The value of a block is the value of the last expression in it. The value of a conditional control construct such as 'if' is the value of the last line on whichever part of it executes.
Assignment statements return the value being assigned (ie the 'rhs', the right hand side).
=== Operators and macros ===
Unary operators start with the character '+'.
The following characters can be used in names for binary operators: todo
todo: what is the operator to negate a number or boolean or to invert a function?
=== Data constructors (also called graph constructors or node constructors) ===
todo: in many (but maybe not all) ways, the earlier G-constructor syntax is better than this, merge these 2 proposals
Oot has one primitive structure/composite data constructor, []. It is used to construct Oot Graphs. Oot Graphs can be used as lists, as associative arrays/dicts/hashes, as trees (acyclic graphs), or as (potentially cyclic) graphs. Examples:
a list: l = [1 2 3]; l.1 == 2;
an associative array with string keys: d = ["apple"="red" "pumpkin"="orange"]; d."apple" == "red";
an associative array with keyword keys: [APPLE="red" PUMPKIN="orange"]; d.APPLE == "red";
an associative array with variable keys: key1 = APPLE; val2 = 'orange' [key1="red" PUMPKIN=val2]; d.APPLE == "red";
a tree (edges by node syntax): tree1 = [SALLY=[$BOB, $ALICE]; BOB=[] ALICE=[$SALLYS_GRANDDAUGHTER] SALLYS_GRANDDAUGHTER;]; tree1.SALLY.ALICE.0 == tree.SALLYS_GRANDDAUGHTER
In the previous example, we used prefix dollarsign (eg '$BOB') to indicate that, eg instead of SALLY's first child being the keyword BOB, rather SALLY's first child is the node whose LABEL is the keyword BOB. Prefix dollarsigns are resolved within the context of the currently-in-scope data context.
alternate syntax for declaring edges (edgelist syntax): t2 = [SALLY BOB ALICE SALLYS_GRANDDAUGHTER SALLY/BOB BOB/ALICE ALICE/SALLYS_GRANDDAUGHTER]; tree2 == tree1;
using newlines:
tree3 = [
SALLY BOB ALICE SALLYS_GRANDDAUGHTER
SALLY/BOB
BOB/ALICE
ALICE/SALLYS_GRANDDAUGHTER
]; tree3 == tree2;
a cyclic graph: cg = [A B C A/B B/C C/A] cg.A.B.C == cg.A
a graph with a self-loop: sl = [NODE1 = [--SELF]]; sl.NODE1.0 == sl.NODE1;
a graph with an edge to the graph itself: gwaettgi = [NODE1= [--ROOT]]; gwaettgi.NODE1 == gwaettgi;
a graph with an edge whose target is another edge, rather than a node: gwet = [NODE1 NODE2 NODE1/NODE2 NODE2/NODE1.NODE2. ); gwet.NODE2.0.--SRC == gwet.NODE1
the same graph, with the addition of a NODE3 which is a node representing a reified edge: gwet = [NODE1 NODE2 NODE1/NODE2 NODE3=NODE1.NODE2. NODE2/NODE3 ); gwet.NODE2.0.--SRC == gwet.NODE1; gwet.NODE2.0 == NODE3;
Here the a postfix '.' means 'the last edge along the path just given'. For example, 'x.y.Z.' would refer to the edge whose source is the result of evaluating x.y, and whose label is Z.
=== Function application ===
Functions associate to the left. 'Multiargument' functions are curried. Function application is by juxtaposition. Eg:
{{{
sqrt 4 == 2
add 2 3 == (add 2) 3 == 5
Graphs can be accessed as functions:
d = ["apple"="red" "pumpkin"="orange"]; d "apple" == "red";
The 'dot' operator is a synonym for this that allows tight binding through attachment:
d."apple" == red d2 = [A=[0=$B] B=[1=$C] B=[2=$D] D ]] d2.A.0.1.2 == d2.D == d2 A 0 1 2 == (((d2 A) 0) 1) 2
todo: Perhaps the dot semantics will differ from ordinary function application when used on the lhs of an assignment? Perhaps something about testing for existence at each step, although i think we've got that covered with '(d2 A 0 1 2). Or is this useful at all?
When on the lhs of an assignment, two dots can be used to create desired edge if one doesn't yet exist:
d3 = [D] d3..A..0..1..2 = d3.D
Note that in this case, the nodes newly created are all labeled NIL.
Three dots can be used to create a first-class path object by defining the starting and ending nodes (todo really?):
d2 = [A=[0=$B] B=[1=$C] B=[2=$D] D ]] path = d2.A...0.1...2 len(path) == 1
todo
Expression evaluation is non-strict but not necessarily lazy. I'm not quite sure exactly how this will work, but right i'm thinking that the requirement will be that the program should not diverge or give an error unless a lazy evaluation strategy would also diverge or give an error; EXCEPT if the entire expression being evaluated was (a) created at the behest of the current module AND (b) not marked as lazy.
In terms of implementation, each thunk will be marked as to the modules in the call chain at the time of its origin, and marked if it is 'lazy' or not. If it originated in the current module and it is not marked lazy, it is evaluated eagerly. Otherwise, it is evaluated lazily yet with some heuristically bounded speculative eager evaluation, perhaps involving the maximal lexical nesting depth of the current module (static, i suppose, lest some macro make a really deep depth?); but if an error is generated, this error is not delivered until the value would have been demanded lazily.
Within the scope of a data constructor (that is, after an unquoted [ but before the closing ]), we are said to in 'data context'. Otherwise, we are said to be in 'code context'. Some syntax, and the operation of macros, differs depending on whether we are in data context or code context.
Parenthesis within data context enters code context. A nested data context can be entered from this code context, etc. Eg:
[(1 + 1), ([1 2] + [3 4])] == [2 [1 2 3 4]]
Base context is the program that is being executed. Metacontext is annotations and other data attached to parts of the program being executed. For example, static type annotations are in metacontext. Annotations (metacontext) may be attached to parts of the program within code context and also to parts within data context.
From base context, metacontext is entered via the prefix ^. From metacontext, base context is entered via postfix ^. As a shorthand, any capitalized word is automatically placed in metacontext. Eg:
Int i = 3 is shorthand for ^Int i = 3
To place multiple space-separated words into metacontext, use ^ with parenthesis, eg: ^(List int) l = [1,2,3]
To place multiple lines into metacontext, use ^ with blocks, eg: ^{ Wrapper1 List int }
Both base and metacontext can have both data context and code context within them.
' involves passage between a mode of error-handling in which exceptions are handled by immediately raising the exception, but variables are guaranteed not to contain nils, and a mode in which exceptions are captured by Fail values using Option types, but exceptions are always caught.
'x evaluates x, and, if this evaluation raises an error, it catches that error and puts it into the errorneous case of an Option type. If x does not raise an error, it puts the result into the successful case of an Option type.
x' takes an Option type and tests if it is an erroneous case or a successful case. If it is erroneous, it raises the contained error. If it is successful, it returns the contained result value.
x'e is like x' except that if x is an erroneous case of an option type, it returns not the contained error, but rather the result of applying the function e to the contained error.
For example:
(todo is the syntax right to terminate the optional arguments of the DivideByZeroError? constructor here?) (todo is the lambda function syntax right here?)
'is' is a boolean operator. 'subject is predicate' returns True (T) if any of:
and False (F) otherwise.
Oot values are auto-coerced to booleans when booleans are expected and when the value is in the domain of either the 'len' function, or the 'nonzero' function.
The coercion uses the builtin function 'truthy'. 'truthy x' works as follows:
todo
i'm not sure; maybe some coercion between ints and floats, but not much else? Autocoercion hurts readability. Otoh shouldn't users be able to implement their own numerics? Maybe limit to acyclic (on the type graph level) coercions, and to embeddings, eg int coerces to float?
i suppose it's also nice for things to coerce to strings for debugging. But maybe pp, and Exception, and print, should just explicitly coerce.
todo
i generally have the idea that instead of just having one 'nil' value, like Python's None, we'll have many, to disambiguate.
" The problem is that the null reference has been overloaded to mean at least seven different things:
a semantic non-value, such as a property that is applicable to some objects but not others;
a missing value, such as the result of looking up a key that is not present in a key-value store, or the input used to request deletion of a key;
a sentinel representing the end of a sequence of values;
a placeholder for an object that has yet to be initialized or has already been destroyed, and therefore cannot be used at this time;
a shortcut for requesting some default or previous value;
an invalid result indicating some exceptional or error condition;
or, most commonly, just a special case to be tested for and rejected, or to be ignored and passed on where it will cause unpredictable problems somewhere else.Since the null reference is a legitimate value of every object type as far as the language is concerned, the compiler has no way to distinguish between these different uses. The programmer is left with the responsibility to keep them straight, and it’s all too easy to occasionally slip up or forget. " -- Anders Kaseorg, http://qr.ae/CS2A6
i would also consider breaking the following into two: "a semantic non-value, such as a property that is applicable to some objects but not others;"; in true/false/nonsense, 'nonsense' is sort of a 'non-value', but this could sometimes be different from a property that is sometimes inapplicable?
also, true/false/maybe might be distinguished from true/false/don't know (eg dont know means i refuse to take any position, maybe means both are reasonable guesses), and true/false/nonsense, and true/false/neither (implying there is some other truth value besides true or false which is applicable here, eg in logics that admit other qualitatively different ones)
to deal with distributed systems:
maybe have two basic error conditions, for three result conditions in total:
(1) success (2) clean failure (3) success or failure unknown; also, partial success possible (4) corruption detected (eg ecc fail error)
so, if a side-effectful RPC was sent but there was a network failure that prevents us from knowing if the RPC request was received or not, (3) would be returned.
todo; also, this isnt syntax
--GET and --SET
todo; also, this isnt syntax
todo
todo (and commas for destructuring bind on the lhs)
M for map: eg [4,16]Msqrt == [2,4]
F for fold: todo
T for filter: eg [-5,5,-10,100]F(>0) == [5,100]
Z for zip: todo (and what about zipWith?)
P for pluck: todo (see underscore)
C for cross-product
U for unit: eg 3Um means '3 with units of meters'. xUm == (unit (--unitabbrevs 'm') x)
todo: check out Perl's 'metaoperators' again and see what else we're missing
todo: and what about that Ruby thing that is like a partially-applied __get?
$ involves the passage from variables to their values. In ordinary code, "x" would be replaced by the value of variable x when it is evaluated; but "$x" means the VARIABLE x, not the value of that variable. Similarly, in a context (such as in the lhs (left-hand-side) of an =s in a data constructor) in which "x" would be assumed to just be a name, "x$" means the interpolation of that name, that is, the substitution of x's value for x$.
"" involves the passage from literal strings in the source code to identifiers. ""xyz is shorthand for 'xyz'. xyz"" means to lookup the value of variable xyz at the time of evaluation, and then to substitute this string into the source code in place of xyz"". For example: a=3; xyz = 'a'; xyz"" == 3;
^ involves the passage from ordinary code to metadata and annotations about that code (see 'ordinary context and metacontext').
Attached macros can only 'see' the things they are attached to. They see the raw string text of those things, however, and can use postfix "" to get the variables if they want, or they can just use them as strings (eg the way that the unit abbreviation is just passed to a lookup table for U). Users can define custom attached macros, but their names must be more than one character long. Note that attached macros can only be used with an identifier or data constructor (not an operator) on their left, to allow them to be distinguished from capitalized word (annotations) and from keyword arguments to operators (todo is that exactly correct?).
todo describe different kinds of macros depending on what compilation stage they interferee with?
todo source filters (file scope)
line macros: In general, identifiers that consist of one lowercase letter repeated exactly twice are macros that operate on the raw source code string thru the earlier of either the end of the line, or the first ';' character not escaped by a preceding backslash.
block-scoped macros (given parsed representation, return new parsed representation)
racket-like syntax metaprogramming in block scope?
todo is some sort of a metaprogrammy thing
Variables and labels and functions are all in the same namespace.
'Oot' is built around a simple subset of itself called Oot Core. So,
todo
note: oot core and oot have thee same lexer and parser, but oot has a bunch of macros and metaprogrammed syntax that oot core does not
teach oot core first, then oot, then metaprogramming
todo
todo
"pp" is a macro that surrounds the rest of the line in double quotes, and sends result to the default console output. A synonym is 'print'. Eg
pp Hello World!
Lisp is a homeoiconic language built upon lists. Oot is a homeoiconic language build upon labeled (hyper multi reified) graphs.
Why Oot? -- built around a powerful data structure: labeled graphs. -- interface-based (or "attribute-based") type discipline -- constraint satisfaction -- readable, concise syntax -- memory managed. static typing, type inference. lazy evaluation. higher order functions. -- goal: the referential transparency, higher order functions, and type system of Haskell, the readability and convenience of Python
see [2] for more detail.
audience: general purpose. programmers.
statically typed. memory managed. type inference. lazy evaluation. higher order functions.
support for the following paradigms: imperative, oop, functional (referential transparency), logic, macros
priorities: power, readability, conciseness
syntax that compiles to cleaner core
primary data structure(s): labeled (reifiable) graph. this also provides a syntax for lists, association tables, arrays, trees, graphs, relations, structs, objects with managed attributes.
some goals:
tasks that could drive development
some features:
anti features:
some other design decisions:
AnA?
In some languages punctuation characters are also separators, so you aren't required to put a space before or after them, but in Oot, many punctuation characters mean something different if they are attached to another token as opposed to being surrounded by whitespace. For example, in Oot,
x = 3
is not interchangable with
x=3
The '=' in "x = 3" is called "freestanding". The '=' in "x=3" is called "attached".
Put the function arguments to the LEFT of the function, separated by spaces.
Example: if f is a function that takes arguments x and y: y x f
You can pass keyword arguments using keyword=value. The order of keyword arguments doesn't matter. All keyword arguments must go to the right of all positional arguments.
Example:
x = 3 [] lb="apple" ins x == ["apple"=3]
G-constructors are literals used to construct directed graphs (for technical notes on what we mean by directed graph, see [3]