Klisp – An implementation of the Kernel programming language
klisp.org
klisp.org
Short circuiting in Nim, seems simple enough:
template myAnd(a, b: bool): bool =
if a: b
else: falseI.e. this and operator is an anti-deluvial beast once known in the Lisp word as a FEXPR.
If a compiler is developed, every such operator will have to be reimplemented as a macro.
Special Forms in Lisp [0] by Kent Pitman (1980) is about FEXRs vs. MACRO:
> It is widely held among members of the MIT Lisp community that FEXPR, NLAMBDA, and related concepts could be omitted from the Lisp language with no loss of generality and little loss of expressive power, and that doing so would make a general improvement in the quality and reliability of program-manipulating programs.
> There are those who advocate the use of FEXPR's, in the interpreter for implementing control structure because they interface better with certain kinds of debugging packages such as TRACE and single-stepping packages. Many of these people, however, will admit that calls to FEXPR's used as control structure are bound to confuse compilers and macro packages, and that it is probably a good idea, given that FEXPR's do exist, to require compatible MACRO definitions be provided by the user in any environment in which FEXPR's will be used. This would mean that a person could create a FEXPR named IF, provided he also created an IF MACRO which described its behavior; the FEXPR definition could shadow [12] the MACRO definition in the interpreter, but programs other than the interpreter could appeal to the MACRO definition for a description of the FEXPR's functionality.
Kent is writing very theoretically there and being very generous to the idea.
Single stepping through macro-expanded code is perfectly possible. There is no debugging disadvantage between stepping through a macro-expanded control flow operator, versus one which is interpreted. In both cases, the single-stepping interpreter can know the source code location where the argument expressions came from and jump the cursor there, providing visual stepping.
Not to mention that compiled code can be stepped through a source code view; countless programmers have been doing this in C for decades, and similar languages. Given that we can write an if statement in C, compile it and step through it in gdb, the position that we benefit from an FEXPR to do the same thing in a Lisp interpreter is rather untenable.
Speaking of which, eval is also a first-class function; that doesn't mean it's a good idea to use all the time.
And to the fact that fexprs operate on second class data. It's still a win that they are first class objects. It means you can dynamically pick which fexpr (or applicative operator) to call on a set of arguments, which like you said, can be selectively eval'd.
The enabler of interesting semantics is not the FEXPR but the env: that the environment is available to the program itself, reified as an object. We can write code which somehow receives this env as an argument and then use it in eval. (Then it's basically an afterthought that we can put such code into functions, hook them to operator names, and have the interpreter dispatch them for us, and automatically pass them the environment.)
Given access to the environment, we can explore questions like, "what if we dynamically build a piece of code, say, based on some external inputs, and then evaluate it in the environment where it can see the local variables of the current function?"
Ultimately, this sort of thing is entertaining bunk, which could be why it disappeared: the evaluation-semantic equivalent of Escherian impossible waterfalls and such puns and ironies. (I just coined a term: trompe d' eval).
Or, maybe the ancient Lispers were wrong; was there a tiny baby hiding in the bath water? Was it really just chauvinism (our main program is research into better compilers, and whatever gets in our path is to be pushed aside).
Possibly, the Algol people and lexical scoping had an influence: lexical scopes encapsulate and protect. You don't want to reveal run-time access to the environment, which breaks the doctrines of lexical scoping, allowing a function to peek into or mutate another's environment, if it only it receives that environment as an object. That would have been repugnant to the Wirths and Dijkstras of that heyday.
We have a less powerful version of this in the lexical closure, which binds a specific piece of code to a specific environment, without revealing that environment as an object. The closure is reified; the environment isn't, being considered something lower-level that remains hidden under the hood (and subject to a myriad implementation strategies which make it hard to model as a cohesive object).
As far as the search for compilers is concerned, I think what is considered powerful notation should be kept around, even if it's tough to compile at the moment.
Suppose the Lisp is bootstrapped in some other language, like C or assembler. The special operators in the interpreter are written in C. If you write the IF operator in C, and that operator itself needs an if operator, it uses the C if statement or ternary operator. (Obvious, right? No level confusion.)
If you add FEXPRS, they are interpreted code themselves: interpreted code controlling the interpretation of code. If you write an IF FEXPR and it needs an if operator, and you use IF, then you get infinite regress/recursion: while trying to interpret IF, the IF FEXPR calls itself, and then runs into the same situation, calling itself again, ...
If the Lisp has a compiler and macros, then you can write an IF macro, and compile that FEXPR. Then, when the interpreter evaluates an IF form, it now dispatches a compiled function. When that function needs IF, it's just running the compiled code, and not recursing any more; the IF FEXPR is only for interpreted code.
FEXPR's can do some "impossible things", and if you want to do those things fast, compiled FEXPR's could be useful.
So will this (Racket):
(define-syntax my-and.v2
(syntax-rules ()
[(_) #t]
[(_ a) a]
[(_ a b ...) (if a
(my-and.v2 b ...)
#f)]))The Klisp authors picked a pretty bad example for demonstrating the power of fexprs. You can think of them as being first-class macros in a way [0].
Joe Marshall demonstrated that fexprs can be divided into two distinct classes: safe and unsafe [1]. He showed that all safe fexprs could be implemented as macros with no loss of expressiveness. (An unsafe fexpr is one that relies on metacircular fixpoints (whatever that means)).
[0]: That's not exactly true. Macros are syntactic transformers whereas fexprs are procedures that can syntactically modify and selectively evaluate its arguments in a given environment. Despite this semantic difference, there's a very large overlap in their use-cases.
[1]: https://www.brinckerhoff.org/scraps/joe-marshall-on-FEXPRS-a...
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity and_or_top is
Port ( INA1 : in STD_LOGIC; -- AND gate input
INA2 : in STD_LOGIC; -- AND gate input
OA : out STD_LOGIC; -- AND gate output
end and_or_top;
architecture Behavioral of and_or_top is
begin
OA <= INA1 and INA2; -- 2 input AND gate
end Behavioral; myand := method(
if(call evalArgAt(0), call evalArgAt(1), false)
)
myand(true, 3 println) # prints 3 to stdout
myand(false, 3 println) # nothing happens
This should also be possible in TCL. Scala has some kind of lazily eval'ed arguments, too. And, of course, Haskell has this built-in.EDIT: better example code.
Vau interpreters can actually be simpler than lambda interpreters, and make a great playground for language design. I wrote a blog post back in 2012 about converting a simple Python Lisp interpreter into a vau interpreter (http://gliese1337.blogspot.com/2012/04/schrodingers-equation...), and that eventually grew into an undergraduate capstone paper on designing the denotational semantics of and implementing another vau-based language called Vernal (https://github.com/gliese1337/CS598R).
I don't expect any of these will ever become mainstream industry languages, but they have a theoretical elegance and simplicity that makes them fun to play with for the theoretically-minded.
They're vastly fun for prototyping complicated logic in.
So is the problem with fexprs the fact that subexpr elimination can't happen because you don't know what will be a fexpr at optimization time? Or is it something else? Because if it's just that, then why can't a fexpr just be an ordinary function whose canonical name is actually a macro that `quote`s its arguments? I'm assuming I'm missing something, because somebody must have tried that by now.
Or you could just `quote` the call yourself, like TCLers do.
That's part of it. But the bigger issue is, I think, that you can't even generally compile[0] the arguments before runtime, because you don't necessarily know what expressions they'll be by the time they get evaluated.
> Because if it's just that, then why can't a fexpr just be an ordinary function whose canonical name is actually a macro that `quote`s its arguments?
That's exactly what they are, along with the environment at the call site. Then, when (and if) you need the arguments to be evaluated, you do it explicitly with a call to `eval`. But keep in mind that the fexpr body is free to modify those arguments as data before evaluating them.
> Or you could just `quote` the call yourself, like TCLers do.
Tcl is similarly a very difficult language to compile and/or optimize.
[0]: I mean ahead-of-time compilation here. I don't see any reason why a JIT compiler couldn't be effective.
According the summary of the Wand paper, though, another problem is that optimizations can't happen without full-program analysis. And now the question becomes, if fexprs can be trivially implemented in lisp using macros, or just hand-quoting args, then why don't non-fexpr lisps have this issue? And if they do, why don't we just add fexprs, and first class environments, and eliminate macros entirely?
Only if they're not enforcing some (actually, the worst possible) evaluation strategy.
/usr/include/features.h:374:25: fatal error: sys/cdefs.h: No such file or directory
# include <sys/cdefs.h>
(I installed uuidcdef, for what it's worth).Also I don't understand the code for the operator and, isn't it gonna return #t whenever x is null?
Maybe this article might help you: http://askubuntu.com/questions/470796/fatal-error-sys-cdefs-...
This operator is defining a function which interprets and operator calls.
func myAnd(lhs: Bool,
rhs: @autoclosure () -> Bool) -> Bool
{
return lhs ? rhs() : false
}
(Heavily inspired by https://medium.com/swift-programming/facets-of-swift-part-5-.... Not tested; any bugs likely are mine)[0] https://stackoverflow.com/questions/29750244/variadic-autocl...
Ok, but what problems are this programming language trying to address?
P.D. Msimoni has written about fexprs (and a Lisp dialect with fexprs on JS, wat.js) if you want to read about them some more.
Does this count?
function and(a,b) { return (a ? b ? b : false : false) } and(false, console.log("This shouldn't run"))
A critical part of the semantics of the kernel function, and the javascript built in &&, is that they are _short circuit_, that is, they only evaluate the second value if the first value evaluates to false. This is very important if you're programming with side effects. (defmacro and (&rest forms)
(cond ((endp forms) t)
((endp (rest forms))
;; Preserve non-toplevelness of the form!
`(the t ,(first forms)))
(t
`(if ,(first forms)
(and ,@(rest forms))
nil))))I sort of regard operative lisps as "lisp, but even more so", as it were (and for sheer brain melting power when prototyping weird logic they're even more fun than normal lisps, at least to me)
false ? console.log("This shouldn't run") ? console.log("This shouldn't run") : false : false
The ? operator checks the left hand side, branching on whether it's true or false. So it'll only execute b if a is true. Otherwise, it jumps to the false section, which returns false, short circuiting. However, there is a problem with that code in that it'll run the b command twice if they're both true. So
and(a,b)(a?(b?(true):false):false)
will both short circuit, and will only evaluate each variable once. It gets to the first test, a?, and checks a's value. a is not true, so it jumps to the false section, which returns false, never executing b.
Another way of rewriting it would be
if (a) { if (b) { return true } return false } return false
Not in JavaScript. This is prevented by 11.2.3(3) which specifies that a function call evaluates it's arguments, and which occurs before function evaluation. Note that the actual call to the function to have it's code run doesn't occur until 11.2.3(8), so by then the full arguments list has already been evaluated. Since this is part of the specification, it is not possible for a JIT to completely elide the argument evaluation unless it can prove that there are no side effects (which may or may not be something that individual engines do).
"Let argList be the result of evaluating Arguments, producing an internal list of argument values (see 11.2.4)."
"Return the result of calling the [[Call]] internal method on func, providing thisValue as the this value and providing the list argList as the argument values."
This is also super easy to test:
> and(false, console.log("This shouldn't run"))
This shouldn't run
falsehttp://csharppad.com/gist/0408e33984025e970013
This behavior is defined (in the C# 5.0 specification document) at section 5.1.4 "Value Parameters"[0].
"A value parameter comes into existence upon invocation of the function member (method, instance constructor, accessor, or operator) or anonymous function to which the parameter belongs, and is initialized with the value of the argument given in the invocation."
[0] - https://www.microsoft.com/en-us/download/details.aspx?id=702...
PS: Also, I'm not sure because I don't know Kernel, but it could be that the function of the page can take any number equal or greater than 2 arguments (and a b c).
It can. That is what line 5 is for. If eval returns true, $and? is applied to the rest of the arguments, aka cdr.
function and(a,b) { return (a ? b : false) } def and(a: Boolean, b: =>Boolean) = if (a) b else false
And with Scala it can be easily made to work as an infix operator: implicit class AndOp(a: Boolean) {
def and(b: =>Boolean) = if (a) b else false
}
Verification: def foo = { println("foo"); false }
scala> false and foo
res2: Boolean = false
scala> true and foo
foo
res3: Boolean = falseheh