5 ms·
Picolisp - what a weird thing. I have to admire the temerity of a guy who in 2023 is still team "dynamic scope is better."
by nathan_compton 2y ago
Picolisp - what a weird thing. I have to admire the temerity of a guy who in 2023 is still team "dynamic scope is better."
- actionfromafar 2y agoIt's like monkey patching in Ruby. It can be used for good and for evil.
- DemocracyFTW2 2y agoIn a less balanced both-sides view Ruby as a social construct is an outlier, monkey patching being (rightly so IMHO) regarded as a more-than-questionable practice in most other mainstream PL communities. I mean, yeah, GOTO can be used for good and for evil, and so can GOSUB 200.
- nerdponx 2y agoWait, is it global by default (Lua, Bash) or truly dynamic? The latter would be kind of mind-bending to program with as the sole or default style. Was that ever a thing? Maybe I'm just too young to have experienced that.
- markasoftware 2y agoEmacs Lisp had dynamic binding as the default (without any true support for lexical binding) until 2012.
- tmalsburg2 2y agoPeople talk as if dynamic scoping was objectively a mistake, but the fact that it works well and is really useful in a complex piece of software like Emacs seems to suggest otherwise.
- p_l 2y agooriginal opposition to lexical binding in Lisp circles was that lexical would be slower. That turned out to be false. Emacs Lisp explicitly kept to dynamic binding for everything because it made for simpler overriding of functions deep in, but resulted in lower performance and various other issues, and ultimately most benefit from such shadowing seems to be focus of defadvice and the like.
- kazinator 2y agoI can understand why that objection would be raised, because lexical binding is slower in code that is interpreted rather than compiled, compared to (shallow) dynamic binding. Under shallow dynamic binding, there isn't a chained dynamic environment structure. Variables are simply global: every variable is just the value cell of the symbol that names it. The value cell can be integrated directly into the representation of a symbol, and so accessing a variable under interpretation very fast, compared to accessing a lexical variable, which must be looked up in an environment structure.
- DemocracyFTW2 2y agoA rather weak argument when you consider what kind of mechanisms (like a digital clock with working seven-segment display) people have been programming / put together in Conway's Game of Life; to me this does not suggest in any way or manner that GoL could ever be my favored platform to simulate a digital clock (or anything more complex than a glider for that matter). Likewise vacuum cleaners and toothbrushes have likely been made hosts for playing doom, and people accomplish all kinds of stuff like quines and working software in brainf*ck. None of these feats are indicative of the respective platform being suitable or the right tool for a sizable number of programmers.
- pjmlp 2y agoAs someone that used it in languages like Clipper, or Emacs Lisp, and ADL rule in C++ templates, cool to make programming tricks of wonder, a pain to debug when something goes wrong several months later.
- nathan_compton 2y ago
- actionfromafar 2y agoI used to program in PicoLisp a long time ago but I have forgotten most of it. Hope you can make sense of this: https://picolisp.com/wiki/?firstclassenvironments https://picolisp.com/wiki/?firstclassenvironments
- nathan_compton 2y ago>PicoLisp uses dynamic binding for symbolic variables. This means that the value of a >symbol is determined by the current runtime context, not by the lexical context in the source file. >This has advantages in practical programming. It allows you to write independent code >fragments as data which can be passed to other parts of the program to be later executed >as code in that context. This amuses me because while its technically true this amazing feat is accomplished only by denuding of the code of substantial expressive power - namely the relation between the lexical denotation of the code and its meaning. I will say this - aesthetically, I prefer picolisp's approach to Common Lisp's, which is to just paper over this problem with gensyms, packages, etc. Give me hygienic macros or give me death.
- More-nitors 2y agoidk doesn't this mean I can't get any help from IDEs? code-completion? find-all-references?
- cess11 2y agoYou'd probably be the only person using an IDE for development in Picolisp. The main author does (or at least did) a lot of development on a tablet, with his own software keyboard (https://play.google.com/store/apps/details?id=de.software_lab.pentikeyboard&hl=en_US https://play.google.com/store/apps/details?id=de.software_la... , which I've enjoyed for years on my handhelds, in part due to the tmux-arpeggio), and his own editor (https://picolisp.com/wiki/?vip https://picolisp.com/wiki/?vip ). I think most of us do something similar, using vim or vip, maybe on larger computers, but generally a pretty minimal setup. The REPL has string based completion, besides completing symbols it will also complete file paths. Development is heavily REPL based, you'd spend a lot more time inspecting the runtime than searching for string occurrences in files. From the REPL you'd also read the language reference, most likely in w3m, the preferred text web browser in this community. (doc 'macro) will open the reference on this entry if you started the REPL with 'pil +', where the + is a flag denoting debug mode. You can expect the web GUI framework to work rather well in w3m.
- bsder 2y agoIt's a shame that John N. Shutt is no longer with us. He created a very Scheme-like language called Kernel that seemed to walk the line between lexical and dynamic in a much more controlled way. https://web.cs.wpi.edu/~jshutt/kernel.html https://web.cs.wpi.edu/~jshutt/kernel.html
- nathan_compton 2y agoI think Kernel is the other side of the extreme from Picolisp, since it wants all objects to be first class but wishes to maintain lexical scope information for all of them. I think this is hard because in a certain sense the names of things in a program have no natural correspondence to the meaning of the program from the point of view of a compiler writer in particular. Code calculates a value using values or changes the state of memory or however you want to conceive of it. The names one used to tell the compiler how to do that don't have any obvious relation to the transformation and keeping them around so that the programmer can meta-program is complex and makes the generated code slower. In a way, Common Lisp and Scheme seem like two distinct local maxima, out of which I prefer the latter. Kernel is neat though.
- bsder 2y agoKernel is "mostly" just lexical unless you explicitly opt-out with FEXPRs. FEXPRs are what draw most people into Kernel. However, what is probably more important but doesn't immediately stick out until you poke at Kernel a lot harder are the fully reified "environments". "Environments are copy on write that don't destroy older references" has very subtle consequences that seem to make dynamic scope a lot better behaved. This also has the consequence that I can force things to explicitly evaluate in reference to the "ground environment" which is an explicit signal that it can always be compiled. I suspect there is a lot of fertile research ground here. In addition, there is a lot of implementation subtlety that I'm not sure he really grasped. Environments need a special data structure otherwise they cons up an enormous amount of garbage (I suspect it really needs a Bitmapped Vector Trie like Clojure). I wish Shutt were still around to talk to. :(
- cess11 2y agoOf all the quirks and weirdness in Picolisp, this is what gets to you? Either a Picolisp system is shortlived enough that your execution environment actually maps to the file your loading, or you're going to be interactively inspecting it anyway.
- zem 2y agohonestly, I feel the same way. for some reasons dynamic scope just feels "wrong" rather than quirky or weird, in that it doesn't fit my mental model of how a programming language should behave and what sort of bookkeeping needs to be the compiler's problem rather than my problem. never used picolisp, but I really wanted to like lush back in the day and the dynamic scope was the stumbling block.
- cess11 2y agoThere is no compiler in Picolisp, only a very simple interpreter. The only bookkeeping problem I've encountered in practice is littering the runtime with symbols. As far as I know there's no way to make it forget about symbols it has encountered, and they are interned as soon as they are encountered. I think the namespacing is supposed to counter this, but I've never learnt it properly. I'm not sure in what situation the scoping would be a problem. The way I usually go about picolisping is using maybe a few globals (could be options, credentials, global stacks, something like that), conventionally marked with an asterisk, like *Global, and then everything else is functions that typically take one parameter, or perhaps a data parameter and a numeric limit for an iterator. Besides let-assignment inside functions variables rarely seem to be the right tool for me. Lush, if it's the Lisp-like shell thingie, seems like a rather different programming environment, what with the inline C and whatnot. Might try it out, seems it hasn't been updated in fifteen years or so, could be an adventure.
- zem 2y agolush was basically an early attempt at what julia does today - a high level lisp like language and a high performance c-like language with good ffi support wrapped in one. I do see your point about picolisp being simple enough that the dynamic scope fits into the overall model; I might give it a try sometime just to see what it's like in practice.
- mppm 2y agoIt's not just temerity I think. Picolisp is a very different animal compared to all modern lisp and scheme flavors. It is the last "true lisp" that I am aware of -- it has an ultra-minimalist interpreter (hand-written in assembly, by the way) that actually represents programs as linked lists. A function is really just a list with the first element contains the argument and the second one the body, and the arguments are bare symbols. Picolisp has no compiler (not even a bytecode compiler), no lexical analysis and no other preprocessing. There is only the reader and the output goes directly to the interpreter. On the upside, this makes Picolisp the only language with truly "first class" functions in the sense that you can really create and manipulate them at runtime just like you would strings or integers, unlike pretty much every other language out there, where "lambdas" are just syntax sugar over function pointers. On the downside, this is all of course completely unchecked and pretty unsafe, and, to come back to the original point, you do not have such conveniences as lexical scoping. That would be literally impossible to implement without changing the nature of Picolisp into a proto-compiled language.
- lispm 2y ago> It is the last "true lisp" that I am aware of -- it has an ultra-minimalist interpreter (hand-written in assembly, by the way) that actually represents programs as linked lists. Strange, I thought many Lisp systems still have source level list-based interpreters. For Common Lisp I would think: SBCL (optional), CLISP, Allegro CL, LispWorks, ECL, ... They can also compile code. Compiling Lisp code was also already available in the first Lisp implementations and having a compiler was an explicit goal of the original implementors. Let's use the LispWorks Listener (the REPL tool): CL-USER 25 > (defun foo (bar) (break) (print (list :hello bar))) FOO CL-USER 26 > (foo 10) Break. 1 (continue) Return from break. 2 (abort) Return to top loop level 0. Type :b for backtrace or :c <option number> to proceed. Type :bug-form "<subject>" for a bug report template or :? for other options. CL-USER 27 : 1 > :bq INVOKE-DEBUGGER <- BREAK <- FOO <- EVAL <- CAPI::CAPI-TOP-LEVEL-FUNCTION <- CAPI::INTERACTIVE-PANE-TOP-LOOP <- MP::PROCESS-SG-FUNCTION CL-USER 28 : 1 > :n Call to INVOKE-DEBUGGER CL-USER 29 : 1 > :n Call to BREAK CL-USER 30 : 1 > :n Interpreted call to FOO CL-USER 31 : 1 > :lambda (LAMBDA (BAR) (DECLARE (SYSTEM::SOURCE-LEVEL #<EQ Hash Table{0} 81D03EFF03>)) (DECLARE (LAMBDA-NAME FOO)) (BREAK) (PRINT (LIST :HELLO BAR))) The debugger output of the currently running function looks like a linked list to me. I could modify it destructively, if I wanted. LispWorks has a list-level interpreter. One can also compile code. Typically Lisp interpreters tend to be written in C, since that usually is more portable than Assembler. > On the downside, this is all of course completely unchecked and pretty unsafe, and, to come back to the original point, you do not have such conveniences as lexical scoping. I would expect from a typical Lisp interpreter (a source level list-based interpreter) that it does all kinds of runtime checks and also provides lexical scoping. If there is a clojure, then this closure would be a combination of some function and an environment. In standard Common Lisp there is no access to that environment, but I could look from an inspector into it: CL-USER 54 > (defun example (a) (lambda () (break) (print a))) EXAMPLE CL-USER 55 > (example 10) #<anonymous interpreted function 8020001A59> CL-USER 56 > (describe *) #<anonymous interpreted function 8020001A59> is a TYPE::INTERPRETED-FUNCTION Code (LAMBDA NIL (BREAK) (PRINT A)) Environment ((A . 10) (#:SOURCE-LEVEL-ENVIRONMENT-MARKER FUNCTION NIL . #<EQ Hash Table{0} 81D03EFF03>) (#:FUNCTOR-MARKER LAMBDA (A) (DECLARE (SYSTEM::SOURCE-LEVEL #<EQ Hash Table{0} 81D03EFF03>)) (DECLARE (LAMBDA-NAME EXAMPLE)) (LAMBDA NIL (BREAK) (PRINT A)))) As we can see, the interpreted closure has code a list and an environment, where A = 10.