Isn't this very confusing in almost all instances?
It's really not useful for variables, but can be very useful for functions, and since they're pretty much the same thing, you get the behavior for free. Although I haven't touched Lisp, my understanding is that Forth and Lisp have some conceptual similarities, but Forth is written by an engineer and Lisp by a computer scientist. Limit you for your own safety? Not a thing in Forth.
In this Kamby language, it looks like the bindings survive and are then accessible by name. When you're executing these [ ... ] blocks, a tree-shaped environment is built up which is then navigated with the :: operator, enabling it to simulate objects with fields.
The idea of there being a stack-like environment of variables which can survive the blocks in which they are bound is very useful in pattern matching and unification.
Under pattern matching, the successful path will have the cumulative variables arising from everything that has successfully matched, including some nested constructs. However, the search strategy will implicitly backtrack in searching for matches, and in backtracking, it will implicitly erase any variables accumulated in the discarded paths.
In the TXR pattern language, I invented some mechanisms for controlling the proliferation of variables. When a pattern function is used, only those variables that correspond to its arguments can emerge.
$ txr -B -c '@(define fun (arg1 arg2))
@(bind arg1 "a1")
@(bind arg2 "a2")
@(bind arg3 "a3")
@(end)
@(fun x y)'
Output: y="a2"
x="a1"
How it works is that the unbound variables x and y are identified with the arg1 and arg2 variables. The pattern function executes, binding arg1, arg2 and arg3. Then a resolution step is done at return-time. Because arg1 was identified with unbound x, x now receives that binding. Similar for y. The variable arg3 disappears; that entire local environment of the function is discarded, and x and y are grafted onto the original environment that existed on entry into the function.If we bind an argument, the parameter will be bound on entry into the function, and so then has to unify in the subsequent bind directive. It does if we pass the value "a1":
txr -B -c '@(define fun (arg1 arg2))
@(bind arg1 "a1")
@(bind arg2 "a2")
@(bind arg3 "a3")
@(end)
@(fun "a1" y)'
y="a2"
fails if we pass some other value: txr -B -c '@(define fun (arg1 arg2))
@(bind arg1 "a1")
@(bind arg2 "a2")
@(bind arg3 "a3")
@(end)
@(fun "foo" y)'
false
The variable binding environment isn't reified as a value that can itself be bound as a variable and inspected; in any context, there is one environment which contains everything that was done in the chain of pattern matching leading up to that point; dead ends that were backtracked out of are gone.How is this less confusing? := vs = will either push a new variable on top of a stack of identically named variables, or change the content of the top element on that stack? Why would anyone want any of that? Shadow variables are usually associated with hard-to-find-bugs, (i.e. forgetting that there is already a variable named 'xyz' in the current scope) not some great feature we want more of..
Anyway again very inspiring :)
People who use lisp like the parentheses.
I don't, though they're no more objectionable than all the {}; languages. I just don't like unnecessary punctuation.
I coded in scheme for years using a preprocessor that understood
define-syntax opt
syntax-rules |
$ _ (x v) b ...
let || x | if (null? x) v | car x
b ...
which is line for line equivalent to (define-syntax opt
(syntax-rules ()
((_ (x v) b ...)
(let ((x (if (null? x) v (car x))))
b ...) )))
It's a matter of taste, but I'd rather read the former. The key ideas, other than using indentation to carry parenthesis level, are to use $ to hang double indents, and | to open a parenthesis that auto-closes.I gave this up learning Clojure so I could use other people's tools. Instead I prefer lighter parentheses, and I use this script to tailor fonts for coding lisp:
https://gist.github.com/Syzygies/226253bc38743ef474ee67cbf58...
I have the most trouble with comment characters, in any language. In various languages I used a preprocessor that implemented a practical version of "comments are flush, code is indented" (hey, it cost me one level) and relied on syntax coloring to mute the comments. Again, I gave this up to use other people's tools.
[0]: https://dwheeler.com/readable/readable-s-expressions.html
Another technical reason is that we work with text based version control tools, under which we use whitespace-insensitive diff as a hack to hide some differences that don't make a difference. That tool becomes unreliable over indentation-only languages; you can make a semantic change whose whitespace-insensitive diff is empty.
Another technical reason not to go indentation-only is that you don't know whether a partially obscured block of code is complete or just a prefix:
|defun foo():
| bar()
| xyzzy()
+---------- < window border
am I looking at all of foo, or do I have to scroll down to see more?Here you know it's the whole thing:
|(defun foo():
| (bar)
| (xyzzy))
+---------- < window border struct KaNode *ka_add(struct KaNode *node, struct KaNode **env) {
if (node->type == KA_STR && node->next->type == KA_STR) {
struct KaNode *output = ka_str(node->str);
strcat(output->str, node->next->str);
return output;
}
return ka_num(node->num + node->next->num);
}
Is this legal? If so, I'm going to use the heck out of it for my version of STOIC.[Edit] Ok.. thanks for the help.. I'm used to seeing function types declared AFTER the parameters because pascal habits die hard. It looked like a run of the mill structure declaration to me.
planet = [ name := 'World' nick := 'Earth' ]
'Hello, ' + (planet :: {WAT})
Output: 10585168
$ txr -i kamby.tl
Syntactic toffee recipe: melt butter over low heat, stir in Lisp macros.
1> (kamby-repl)
kmb> (let dz (/ 10 0))
** error: /: division by zero
kmb> (let a 1)
a
kmb> a
1
kmb> (let foo [(let bar1 42) (let bar2 73)])
foo
kmb> foo
#<kamby-env:(bar2 bar1)>
kmb> foo.bar1
42
kmb> foo.bar2
73
kmb> (set a 4)
4
kmb> a
4
kmb> (set a (* 2 a))
** warning: unbound variable a
** error: unbound variable a
Errors out at the end because unknown forms are evaled as Lisp, and Kamby variables are not Lisp variables.Mostly this was to play with the "construct environment tree as-you-go, with qualified pathname access" idea.
Code:
(defstruct kamby-env ()
bindings ;; assoc list
next-env ;; kamby-env object or nil
(:method extend (me sym value)
(upd me.bindings (acons sym value)))
(:method lookup (me sym)
(assoc sym me.bindings))
(:method delete (me sym)
(let ((binding (assoc sym me.bindings)))
(upd me.bindings (remq binding))))
(:method print (me stream pretty-p)
(format stream "#<~s:~s>" 'kamby-env [mapcar car me.bindings])))
(defvar *kamby-env* (new kamby-env))
(defun kamby-error (form fmt . args)
(error `~s: ~s: @fmt` 'kamby-eval (if (atom form) form (car form)) . args))
(defun kamby-eval (form)
(let ((e *kamby-env*))
(match-case form
((let @var @expr) e.(extend var (kamby-eval expr))
var)
((set @var @expr) (let ((binding e.(lookup var)))
(if binding
(rplacd binding (kamby-eval expr))
(kamby-error form "no such variable: ~s" var))
(cdr binding)))
((del @var) (unless e.(delete var)
(kamby-error form "no such variable")))
(@(symbolp @var) (let ((binding e.(lookup var)))
(if binding
(cdr binding)
(kamby-error form "no such variable: ~s" var))))
;; TXR Lisp qref syntax a.b.c.d <--> (qref a b c d)
((qref . @vars) (let (value)
(while vars
(let* ((var (pop vars))
(binding e.(lookup var)))
(unless binding
(kamby-error form "no such variable: ~s (when resolving ~s)" var form))
(set value (cdr binding))
(if (and vars (not (typep value 'kamby-env)))
(kamby-error form "~s isn't an environment object; cannot lookup ~s"
value (car vars)))
(set e value)))
value))
;; TXR Lisp dwim brackets syntax [a b c] <--> (dwim a b c)
((dwim . @forms) (let ((*kamby-env* (new kamby-env next-env e)))
[mapdo kamby-eval forms]
*kamby-env*))
(@else (eval else)))))
(defun kamby-repl ()
(whilet ((line (progn (put-string "kmb> ") (get-line))))
(catch
(prinl (kamby-eval (read line)))
(error (x)
(put-line `** error: @x`)))))https://github.com/henriquegogo/kamby
Still looks awesome; 400LOC is an incredible achievement for grok-ability c:
But there's always Forth for the the lighter taste in delimiters: https://github.com/TexTerry/forth-examples/blob/master/pasca...
But as Lisp was my first real language, I might be a bit biased. And besides Lisp I like languages like Forth or Postscript, and even Perl ;-)
There’s a point in using different signs that mean different things.