#plottingscheme — Public Fediverse posts
Live and recent posts from across the Fediverse tagged #plottingscheme, aggregated by home.social.
-
CW: Spring update on GRASP
It's been a while since I wrote a report on the progress of #GRASP
In large part, it's because things haven't been going too well lately. The fact that I no longer commute to work by train has a negative impact on my productivity.
I will likely start renting a co-working office starting next month. I'm also in the process of switching jobs.
So, my last few weeks were a kind of a mental diarrhea, when I was figuring out what to do next.
So I had a lot of ideas and very little accomplishments.
My current attitude is that in order to finish GRASP, I will have to start developing it from scratch. Maybe not entirely from scratch, because I think some parts of its design are completely fine, but I will probably start a new project, and just move some bits of the old one as I'll be needing them.
But this also leads me to the position of figuring out certain current shortcommings of GRASP. One of the problems is that I'm unable to run it in the browser. So last week I made a very simple Scheme to JS translator. (I call it jsssssssss, which stands for "JavaScript Scrounged from a Sophisticated Superset of a Simple Subset of Scheme or Something Similar")
Another thing is that I'd like to decouple GRASP from Kawa, and possibly be able to run it on top of other Scheme implementations. But I don't want to give up on the affordances that I get from Kawa, and in particular its type system, and so I'm looking for a way to transplant that system to some other Schemes, refining it along the way. It consists of a few components, including
- a subsystem for interface-based object-orientation
- a record system
- type signaturesThe system is going to be very similar to what I developed on top of Kawa, and described a while ago around here (under the #PlottingScheme tag). I originally thought of making a #lang for Racket, because I was hoping that I could use RacketScript to target the browser, but it turned out that RacketScript doesn't really suit my needs.
I still think of targetting Racket though, because it has its own object-orientation system that I think aligns fairly well with that of Kawa.
I would of course also want to have that system available in jsssssssss, and perhaps I will also try to make a version that works with Chez.
The system's name is POORMANS, which stands for Portable Object-Orientation/Reflection Module Across Numerous Schemes.
The development of jsssssssss made me revisit the old compiler that I wrote for my...
-
The first thing is that the state of Guile's community is much better than that of Kawa (because the latter seems largely non-existent), which also entails a better documentation.
Because of the JVM overhead, Guile also has better performance and less quirks (e.g. tail calls aren't always optimized in Kawa unless you force the compiler, which comes with a performance pentaly; also, Kawa only supports a subset of call/cc equivalent to Guile's call/ec, and the treatment of multiple values in Kawa is somewhat inconsistent; also I think that Kawa's module system doesn't allow for arbitrary renaming of bindings)
There is a suite of Scheme benchmarks available here: https://ecraven.github.io/r7rs-benchmarks/
Some 15 years ago Per Bothner also made a benchmark of JVM languages, and Kawa seems to be as good as it gets on the JVM:
https://per.bothner.com/blog/2010/Kawa-in-shootout/
(I don't know how it would cooperate, say, with GraalVM)
Guile has a good C interoperability, and Kawa has a great JVM interoperability.
Actually, the development of Kawa was somewhat related to that of Guile - some history can be found here: https://www.gnu.org/software/kawa/internals/history.html
Guile comes with an object system called GOOPS, which is heavily inspired by Common Lisp's CLOS (with support for generics/multimethods), whereas Kawa provides a way of defining Java classes.
I have used Guile a lot for some time, but to me the type annotations provided by Kawa are huge asset, and I think that every Scheme should have something like that (I think Bigloo does, but I never actually tried it).
I wrote some more specific things about Kawa under the #PlottingScheme hashtag on this instance (functional.cafe), so you may want to check it out.
-
CW: A criticism of Scheme
When I was first learning Lisp, I was reading the book "Successful Lisp" by Daniel Lamkins.
One thing I remembered was the "defstruct" form, which in that book was introduced as
(defstruct check
number date amount payee memo)This definition creates a number of accessor functions, such as
check-number
check-date
check-amount
etc.At that time, I was already familiar with C and C++ (I wasn't a dedicated hater of C++ yet), but having a distinct symbol "check-number" instead of a compound expression "check.number" like in C seemed to be a hoax to me.
When I became fascinated with Scheme, learning about its record type was a similar disappointment.
Also, in the fifth chapter of SICP there was this weird implementation of object-orientation, where every method call is just a function call whose first argument is supposed to be a method name, and the whole thing is dispatched in a "cond" form.
And then there's this myth of "the lisp curse", which states, among other things, that "[...] making Scheme object-oriented is [...] easy [and] many Scheme hackers have done so. More to the point, many individual Scheme hackers have done so".
Which in my opinion isn't true. I mean, sure, there's plenty of SRFIs that add some lousy object-orientation to Scheme. And then there are a few Scheme implementation that add OO to the core language, in a way that couldn't be added by a language user, or at least not at the level of "a sophomore homework assignment". (Think Guile's GOOPS or Rackets OO system - those are the two that I personally am familiar with)
Gee, even adding records to Scheme seems to be a task that proficient experts are failing at, and no one has done it well till this day.
I find that to be problematic, but I also find it bad that there seems to be no real discussion about it.
Of course, those who follow me, know that I have a certain preference - namely, that in my view, the Scheme community should look at the way Kawa approaches the idea of extending Scheme, including type annotations, object subsystem and syntactic extensions for working with objects. Most importantly, it allows to express the notion of interfaces, which I consider fundamental to software design.
-
CW: Properties, attributes and mappings
When I started porting #GRASP to #Kawa #Scheme, I originally wanted to use good ol' cons-cells to represent documents
This turned out to be problematic, and I'm still suffering from that idea, but I think that it was, in some ways, fruitful
Usually we understand OO's classes/objects as things that bundle together properties and methods, where properties are object-specific variables
If we represent a cons-cell as an OOP object, it will have at least two properties - the "head" (or "car") and the "tail" (or the "cdr")
If we wish to add new properties, we usually need to resort to inheritance
(An alternative view is that a cons-cell is something that satisfies an interface consisting of four methods: getCar, getCdr, setCar and setCdr and that we abstract the storage cells away)
But there is another technique of bundling data with objects. Assuming that an object is a bearer of unique identity, we can store additional information in hash tables. In particular, in Lisps there is the notion of "weak key hash tables", i.e. hash tables that do not prevent their stored keys to be garbage-collected
A good name for this kind of information is "attribute": things *have* or *possess* their properties, but we can *attribute* some characteristics to those things
Properties, therefore, are unique, objective and essential to objects, while attributes are subjective, and are present "in our looking" rather than "in the world"
For example, a screen position of some object is not its property, but it is something that can be attributed to that object by different views (because you can render the same object many times on one screen)
I originally thought that I should use such attributes to represent whitespace and comments around the contents of cons cells, and so I created some nice wrappers around hash tables, using Per Bothner's "generalized set!" (aka SRFI-17), so that I can write things such as:
(define-attribute (attribue key)
default-expression)(set! (attribute 'x) 42)
I like this so much more than all the hash-table-set! and hash-table-ref and other functions for dealing with hash-tables, and I wonder why this syntax isn't available in the core Scheme
I can also use it with non-weak keys, and in that case, I call such entities "mappings". (And I like those names, because they focus on the function of named things, rather than some irrelevant implementation details, like "being a hash table")
-
CW: Keyword arguments in Scheme
Some implementations of #Scheme provide a syntax for defining optional and keyword arguments that is akin to the one that can be found in #CommonLisp, or - relatedly - in DSSSL. For example, in Kawa they can be used like this:
(define (f arg1 ... argN
#!optional
(opt1 default1) ... (optM defaultM)
#!key
(key1 val1) ... (keyL valL))
<body>)The default values are optional, and if they are not given, instead of "(key val)" or "(opt default)" one can simply write "key" or "opt", respectively.
I really don't like this syntax, for the same reason I prefer Scheme's
(define (f args ...) body)
to Common Lisp's
(defun f (args ...) body)
namely, because Scheme exposes symmetry between definitions and their usages.
However, there is no symmetry between using #!optional and #key and using them.
For this reason I have defined - in Kawa - a define/kw macro, which lets me write
(define/kw (cut-selection!
at: cursor ::Cursor := (the-cursor)
to: range ::integer := (the-selection-range)
in: document := (the-document))
::void
...)instead of
(define (cut-selection! #!key
(at ::Cursor (the-cursor))
(to ::integer (the-selection-range)
(in (the-document)))
::void
...)I like it for a few reasons.
First, like regular Scheme definitions, it exposes symmetry between definitions and their usages.
Second, the Common #Lisp style requires to remember two special keywords: #!optional and #!key, whereas the thing I came up with requires knowing only one special keyword, namely := (which is short and therefore I think fairly easy to remember)
Third, it lets me name arguments differently than keywords used to invoke those arguments.One day - perhaps after I release #GRASP - I plan to fork Kawa, and to provide this syntax in the core "define"/"lambda" forms, instead of the Common Lisp style.
(I recall that @dpk did a review of different ways of handling optional/keyword arguments in various Scheme implementations. My syntax wasn't included, because I haven't announced it anywhere, although I have experimented with its implementation for Guile many years ago in the (grand scheme) glossary)
-
CW: static and dynamic types in Scheme
Since Twitter has long been deceased, I thought that maybe bringing up the static vs. dynamic debate could cheer some people up :D
For along time, I have been using #Scheme with no type annotations, and I had no problem with that. I was using variable names that would hint what the underlying type would be, and it became my good habit to provide usage examples alongside definitions.
But I was looking at other languages, such as #Haskell, #ML or #Shen, and thinking how to incorporate static type annotations into Scheme.
One candidate was "A Type Notation For Scheme" by Gary Leavens et al. I also took a look at Typed #Racket, and I didn't like either of them. #Guile 's object system - GOOPS - contains CLOS-like system of defining generic method, where I could write
(define-method (m (a <A>) (b <B>)) ...)
where <A> and <B> are names of classes. But I didn't like that, since it wasn't compatible with my "grand scheme" syntax with destructuring everywhere, and it felt somewhat arbitrary (e.g. in C the type name comes before the argument name, which is the exatly opposite order).
I also considered Racket's contract system, and its cheap rip-off, Clojure's spec, but I found both cumbersome.
The solution came from the place I didn't expect. In the early 2022 I started using #Kawa Scheme for developing a new iteration of #GRASP, and Kawa turned out to provide a system of type annotations.
Functions can be annotated like this
(define (f arg-1::type-1 arg-2::type-2)
::result-type
...)and the :: sequence is always read as a separate token.
The type system has a limited support for generic types, but expressions in type positions are macro-expanded, which means that there is no limit on the expressiveness of this type system.
In the worst case, I can just have my type signature macro-expand to java.lang.Object.
While the stuff that Kawa comes with is in my opinion a bit bare-bones, I leveraged macros to define three ways for defining new types:
- define-type, for defining records, e.g.
(define-type (Rect width: real height: real))
- define-object, for defining classes ("define-class" was already taken by Kawa)
(define-object (Name args)::Interface
(define (method args ...)::type ...)
...
(SuperClass))- define-interface for, well, defining interfaces
(define-interface Interface (Supers ...)
(method args ...)::type
...)I also have a pattern matcher that can destructure records
-
CW: Lisp vs. Scheme coding style
There is an old #CommonLisp style guide written by Peter Norvig and @kentpitman that can be found here:
https://www.norvig.com/luv-slides.pdf
It's generally well written, and it parallels Grice's maxims of communcation.
However, it provides some examples that I, coming from the #Scheme world, find hard to accept.
In particular, it explicitly recommends writing
(if (numberp x) (cos x) nil)
instead of
(and (numberp x) (cos x))
backing it up with the more general recommendation to use "and" and "or" for boolean values only.
In my understanding, this recommendation comes from the attempt to mimick the usages of natural language.
I already gave some consideration to this question (again, contrasting Scheme with Common #lisp in one of the Quora answers that I once wrote: https://www.quora.com/What-are-the-pros-and-cons-of-functional-programming-compared-to-imperative-programming/answer/Panicz-Godek
The bottom line is that while there are some valuable traits of natural languages that should definitely be considered with care and attention, programming languages have a potential of being *better* than natural languages in some regards - especially if we take their compositionality to extreme with all due seriousness.
I remember asking a question on comp.lang.scheme about a function like "find" which - instead of an element satisfying a "predicate", would return the value of that "predicate". Someone replied that I can use the SRFI-1's "any" "quantifier" for this, and then I thought: of course! because it's a generalization of the "or" connective!
In the same vein, SRFI-2, written by Oleg Kiselyov, provides the and-let*, which is pretty much like Haskell's "do" notation over the Maybe monad (for those of you who haskel). And while the name "and-let*" is indeed a terrible one (although admittedly it does indicate that it's a combination of the "and" operator with the "let*" operator), the operator itself is very useful
I devised its improved variant, described in the SRFI-202 document, which also has a capability of destructuring, and of receiving multiple values, and I use it a lot
I don't have too much hands-on experience with Common Lisp to say anything significant about its coding style, but I remember how awkward it felt when I was working through Norvig's PAIP that he had to use '((t . t)) to represent empty bindings, because the empty list in Common Lisp is indistinguishable from the false value - and how much more natural it felt when I could use '() to represent empty bindings and #f to represent the match failure
-
CW: The Grand Scheme/redefining "define"
Sometimes, when some people learn about the "define" keyword in #scheme they - usually more or less jokingly - ask the provocative question: can you redefine "define"?
One example of it was given by Will Byrd, in his talk about "The Most Beautiful Program Ever Written", where he evaluated the form (define define 5) - and then had to restart the interpreter.
However, behind this seemingly playful question there is a much more serious one, namely - can you meaningfuly redefine "define".
To me, the eye-opening moment was when I saw the source code of the (ice-9 curried-definitions) module in Guile:
https://github.com/cky/guile/blob/stable-2.0/module/ice-9/curried-definitions.scm
It generalizes the "define" operator to allow usages such as
(define ((f x) y) (list x y))
so that, for example, ((f 3) 4) would evaluate to the list (3 4). I think this syntax has been used throughout the "Structure and Interpretation of Classical Mechanics'.
When I learned about it, and about the (ice-9 match) module, I thought it would be cool to have a variant of "define" that would not only allow for such "curried" definitions, but also to destructure the arguments in place. Of course, for consistency, this sort of destructuring would also be implemented for other core forms in Scheme - such as "lambda", but also "let" and "let*".
This gradually gave rise to the (grand scheme) glossary
https://github.com/plande/grand-scheme
which I eventually codified as the SRFI-201 "spec".
Above everything else, it made me realize how exceptional a programming language Scheme is. Every other language forces me to write programs in the way envisioned by its designers. With Scheme, however, I could write my programs in the exact way I wanted them to be written, as long as they were encoded in s-expressions (although some implementations overcome even this limitation. For example, Bigloo's "read" function accepts an additional argument, which is a grammar specification to be used for parsing the input character stream; also Racket has an incredibly powerful module system that allows every single module to use a different syntax. But I like s-expressions).
Sometimes I hear the argument that such limitations are "actually good", because they don't let programmers "go wild and write completely incomprehensible code", which is "important on team projects".
(and I usually think about Go and Java when I hear them)
But in reality, lack of expressiveness hinders maintainability of non-trivial programs.
-
CW: SICP and Scheme
There is a lot of excellent books about #Scheme and #Lisp - my favorite ones are Structure and Interpretation of Computer Programs and Paradigms of AI Programming.
The latter is a book that uses Common Lisp, and the presented programs are written with great style and taste.
When I was working through that book, I was translating some of the examples to Scheme. I was only a beginner, so I'd dare to say that I wrote fairly awkward Scheme at that time. It took me many years before I developed my current style of programming.
I was starting out with Guile (because I embedded it in the 3d games engine that I was developing at that time), and one of the greatest revelations was the discovery of the (ice-9 match) module, which contained the "match" macro that I later called the Wright-Cartwright-Shinn matcher, when I wrote SRFI-200.
I rediscovered SICP after I was already using Guile for some time. When I first encountered that book - a few years earlier - I only skimmed through some initial chapters and I thought to myself that I know that already and that the book may be interesing to beginners, but not to me. (Boy I was wrong.)
And in order to absorb the content of this book, I had to unlearn a lot of things that I have had already learned up to that point (mainly from the books about C and C++). The fact that the book didn't just pile more and more language features (hey Bjarne!), but instead carefully provided each new feature with the means of program analysis (such as the substitution model, or the environment model) was such a breakthrough. (And I find it very sad that maybe, like. 0.001% of programmers seem to understand that).
It is often said that even though SICP uses Scheme, it is not a book that teaches or promotes Scheme, but that it instead focuses on some fundamental concepts.
Yet, all the adaptations of the book to various different programming languages seem to fail at capturing the hallmark of SICP, namely - the metacircular evaluator. So instead of, say, presenting JavaScript evaluator in JavaScript, they build a Scheme interpreter in JavaScript - which kind of misses the point of metacircularity.
However - while I would claim that SICP actually promotes Scheme, it does not teach Scheme programming - and definitely not the "modern Scheme programming".
One thing that I consider awful about it, is overusing function aliases to car/cdr functions. I also don't like SICP-style OOP.
(oops, character limit)