In Dyalog, a function can be assigned like so:
foo ← { ⍵+1 }
Now, compare this with a very similar form:
foo ← { ⍵+1 } 2
As can be seen, the final digit 2
at the end of the line completely changes
a function definition into a variable assignment. KAP's parser is flexible
enough to handle this situation (with some work), but the code would be ugly
and explaining the parsing rules would be difficult at best.
In fact, some of these tricky parsing tricks have already been implemented in order to deal with things like this:
a +.(×/) b
vs.
a (+.×)/ b
The reason this is so complicated is because when seeing an opening parenthesis
the parser has to defer the decision as to whather to parse the containing
expression as a function or a value until after the entire expression has been
parsed. KAP solved this by having a class called ParseResultHolder
which can
be returned from parsing functions before the decision on how to interpret it
can be made.
The above trick works, but is complicated enough that I would rather not have to do something similar to deal with Dyalog-style function definitions.
Requirements:
-
Two forms of definitions:
-
Extended form, takes N arguments on each side of the function name. Current syntax:
∇ (arg0;arg1) functionName (arg2;arg3) { ... }
-
Short syntax. Arguments are always
⍺
and⍵
. In Dyalog, this looks like:functionName ← { ... }
KAP currently does not have this.
-
-
Need to have a natural syntax for defining operators. Currently, KAP does not have any syntax for defining operators.
-
Two different ways of declaring functions:
- Global (default in KAP)
- Local (no current syntax for this)
The difference between local and global function definitions can be shown in this simple example:
∇ foo (x) {
x+1
}
∇ bar (x) {
∇ foo (x) {
x+2
}
foo x
}
⍝ With local function definitions, the below will return 3.
⍝ If function definitions are global, it will return 2.
foo 1
It may be easy to simply say that function definitions are global if they are executed in the toplevel context. This is however not ideal, since when including another source file, the second file is evaluated in its own context. Thus, no code is ever really evaluated in global scope (except for commands typed on the REPL).
Additional thoughts: Perhaps evaluation of source files should be performed in a special context, which may not be toplevel but for the purpose of function definitions be considered as such. That's possible, but becomes somewhat difficult to document.
It's also important to note that we can't simply parse source files in the toplevel context, since local variables (not functions) declared in files needs to be local to said file.
Possible solution: One idea would be to have a special symbol that indicates global scope. This could be used for both functions as well as the case where a file needs to expose a global variable. The question is: What should such syntax look like? I have no idea right now.
Defined operators are rare enough that using some verbose syntax such as
defoperator
isn't out of the question. With the short function syntax
one can always use ⍶
and ⍹
to refer to the left and right function
names in a way similar to how GNU APL does it.
We could keep the Dyalog style for short function definitions, but instead
of using ←
, we can use ⇐
Thus, a function definition would look like this:
foo ⇐ { ⍵+1 }
This is neat, but doesn't answer the question as to how to deal with local vs. global functions. It also leaves the long-form function definitions unchanged.
This is an obvious solution. To take ideas from Lisp, let's call it defun
. A
function definition would them look like this:
defun (arg0;arg1) foo (arg2;arg3) {
arg1 + arg2 + arg3 + arg4
}
The short form would be:
defun foo {
⍺ + ⍵
}
The short form is not too bad compared to Dyalog (4 characters longer). It would also
be constent with a defoperator
symbol. However, in order to distinguish between the
function and variable names, the syntax would look pretty weird. Maybe something like
this:
defoperator (leftarg) [leftop] foo [rightop] (rightarg) {
(1 leftop rightarg) rightop leftarg
}
I'm not a huge fan of this, but I really don't have any better ideas.