> Carp borrows its looks from Clojure but the runtime semantics are much closer to those of ML or Rust.
One of my current side-projects is a little Civ-style game I'm working on in Racket, which I decided to start coding in on a whim. Will see how it goes moving pieces of it across to Carp.
To me, the most interesting aspect of S-expression programming languages is that they are very well suited for other editors than the text editor. I know of a number of experiments in this regard but I have yet to see anything quite as ground breaking as what I can imagine is possible.
How come we don't have Google Maps-like zooming with LOD mapping to abstractions at different levels?
Many have tried. The upshot is, it is either not as cool as it is in our heads if you get it, or it isn't as easy as it is in our heads.
You don't even really need s-expressions to do what you're talking about. You can do it off of any grammar, which all current programming languages have[1]. S-expressions are slightly easier, but they aren't easier in the place where the real problem lies. The real problem lies in the fact that the code isn't shaped the way you think it is and it isn't as nice as you think it is and it turns out nearly every attempt to project code into something other than our current plain text representation flounders on the pathological nature of source code.
Personally I think this is a fundamental problem, but there is an avenue to where I would be wrong. If somebody who respected text and understood why it is currently so dominant, in a positive sense, also took on the task of trying to transcend it based on that understanding, perhaps they could find something, revealing the current lack of such editors to be accidental after all. But as long as the only rebels trying it also labor under the crippling error that text is a weakness that needs to be discarded as quickly as possible, rather than the only methodology currently known to be even remotely strong enough to support our programming endeavors [2], they're never going to get anywhere in trying to deploy a system that for anyone to be even remotely interested in it pretty much has to immediately be able to scale up to some reasonably fraction of what text editors already do.
I find it telling that the stickiest such effort I've seen to date is literally to simply provide the programmer with a completely zoomed out graphic of the text file in the scrollbar area.
[1] Since nobody has yet had the resources to try to figure out how to take a language model and turn it into a programming language. Patience.
[2] Or, to put it another way... make a list of all the weaknesses text putatively has for programming languages. Then sit and stare at it for a while until you grok that that is still the shortest list of weaknesses we know of, and all other efforts have even worse weaknesses at scale. It isn't that I deny the weaknesses; they are manifest and obvious to pretty much everybody. It is that it's still the shortest such known list at scale.
I’m looking at the task of making programming more visual and structured but am at a very early stage (still getting my head around OpenGL/WebGL). I do realize text us still used for its own reasons.
I’d say they are:
1) Lack of need for a proprietary tool to view/edit.
2) Density of information.
3) Nearly every existing attempt I’ve seen at visual programming is insultingly bad. So I get it why folks would be hesitant to waste time on some schmuck’s new invention.
Am I missing anything on the advantages of text? This is an honest attempt to see where I’m missing perspective, not an attempt to convince.
I don’t think the target audience is developers with an existing working workflow- the potential upside prob wouldn’t justify someone spending the time/mental effort to grok a new model. But I think the audience is those who have some aptitude and could be coding and aren’t yet. In my mind, primarily so that maybe individuals can have a little more influence over the code and algorithms in their own life more so than industry roles.
Given that the ways you can convey information graphically are a super set of what text provides, I can’t help but think there’s some room for improvement- especially with concepts like literate programming around.
Overall, I’d like to give your average personal a little more agency. Maybe that’s a graphical approach. Maybe its getting folks to do basic Rails/Django/Flask/PHP whatever.
But I am curious if I’m missing obvious rationales for text…
In programming, we always build something initially that we can understand intuitively, then once we've gained experience with that program, more things become intuitive and we expand the software in ways we couldn't have foreseen. We change the code in ways that seem a little crazy and unlikely to work well at first, but with experience the changes become completely sensible. For example, some arbitrary piece of the code that starts out as a constant may later become a variable, and then a database table, and then a user interaction, and then a machine learning algorithm, and then an API call, and then a variable again.
Source code is the best way to allow arbitrary evolution of code. Visual programming cripples the kind of evolution that is possible or reasonable.
Now, what might work would be a component designer where all the state of the designer is stored as the source code in human-editable form. It might be interesting to be able to right-click a class or function call in source code and "Edit as Component". If that improves my productivity, it might be worth paying for. OTOH, such a thing now has to compete with AI code generators.
I think you are, text is just both more easier and more flexible. Visual programming always gets supercomplex quick and in this situations make it harder to grok any detail. imo (have been there too btw. ;) )
But still, maybe someone didn't just find the right approach, so don't feel disillusioned!
I have friends that work in game dev and over the years they have shared some "amazing" screenshots of blueprints on our discord server.
I've seen a lot of people in business point to it as something to uphold - that if we were writing software correctly, it would be capable of that.
But it still requires fairly technical people, they can still create abominations, and oftentimes they're used as prototypes for designers until the programming team can come in and write something that is actually maintainable and performant.
By “blueprints”, do you mean the visual editors the big game engines tend to have? I think Unreal calls theirs blueprints. I’ve never done any game dev but find it an interesting compare/contrast with typical web dev approaches since they push some boundaries that typical business application doesn’t.
One common failure mode of previous visual code editing projects has been forcing the program to be grammatically valid at every moment, and maybe even that each identifier is defined at all times. This doesn't always match how people like to initially write or edit programs.
It would be fascinating to instrument a programming text editor to capture the entire chain of user input. Then go through that log and determine:
1. How many edits don't preserve the AST structure of a program. Changing a leaf expression to a different expression does, as does, say, changing a `for` loop to a `while` loop. But cutting the `(cond)` part of `if (cond)` out (including the parentheses) and hoisting it to a separate expression does not because now those parentheses mean something different.
2. How often the program is in a grammatically invalid state across multiple edits.
I suspect fans of always-valid visual editors massively underestimate the size of both of those.
"Am I missing anything on the advantages of text? This is an honest attempt to see where I’m missing perspective, not an attempt to convince."
Acknowledged.
My answer is, programming is n-dimensional and text is one of the few mechanisms we have for dealing with that. I believe this is actually the characteristic of it that has screwed so many other efforts, especially visual ones.
I don't know if n-dimensional has a formal, accepted definition, but the characteristic of an n-dimensional space that is of interest to me is that at any point, suddenly a new dimension can be introduced, and this is indefinitely recursive.
Let me try to demonstrate this with a web site first. Imagine there's a web site that has a home page with ten internal links. Each of these ten links then has a set of ten pages that they link to, all unique, and those pages link nowhere. You could represent this particular website in a clean 2D spreadsheet. In the A column, you have the ten top-level links, and then in B through K you can have the subsidiary pages. In this simplified model, a web page can be imagined as something like a linear collection of links, so I can describe a page as being "Link 4, Link 7", identifying the subpage from the 4th top-level link, then the 7th link of that page.
Of course, this is a highly contrived example. Perhaps only and exactly one of those links goes to another top-level page with another set of 10 links that each have ten pages. So now, in the middle of this nice two-dimensional layout, in exactly one cell, suddenly you have another two dimensional layout appearing, and neither of those two dimensions is the same as the previous two dimensions, it's a fresh set. Nothing stops these pages from having another fresh set of dimensions appearing. In n-dimensional space, you never know when you're going to get a fresh set of dimensions when you cross over some unassuming-looking edge in the graph.
In reality, web pages are so complexly interlinked that there isn't any visual way of representing them in the general case. My example is obviously super unrealistic. We normally wouldn't even dream of trying to represent a website in a spreadsheet like that.
That doesn't mean the situation is hopeless necessarily, but it does mean we will make compromises. A sufficiently static site might have a canonical "site map" that completely organizes the site into a tree. But that is a simplified projection of a human conceptualization of the space; it isn't the real linking map of the web site. It is valuable, but it's only a particularly humanly-compelling slice.
In general, trying to collapse an n-dimensional space into a finite number of dimensions is not something you can do. Especially when the number you're trying to collapse it into is as small as "two" or "three"! This, again, doesn't mean the situation is hopeless. There's a lot of angles you can take on that "in general". "In general" is, as the saying goes, doing some heavy lifting in that statement. But it is an important aspect of understanding the problem.
How does text handle the n-dimensionality of code? Consider it from the point ofview of the language grammar. If I have
x = 1 + 1
each of those "1"s is actually an "expression" in the grammar (or some local equivalent), which means I can do:
x = 1 + (readint() + readint())
and now I've expanded that expression into a new tree. For the purposes of a visual editor, not only have I expanded that 1 into two function calls, I've also got to have a link to the functions in question.
And then this nests infinitely recursively. And programmers keep coming up not only with new dimensions through the basic n-dimensionality of text conforming to tree-based grammar structures, but then new ways of adding dimensions; "x.y" as a field reference of a particular type is one thing, but then x can be polymorphic. Or decorated. Or monkey-patched. Or perhaps the language has multiple dispatch. Or overloading operators, meaning that operators now need to be be something you can follow to an implementation. Or... the list goes on.
You end up with a statement like "x.y.z = a.b.c(q, q2)" having potentially dozens of graph edges departing from it, each to a declaration that may itself be a rats nest of graph edges, each to a further declaration that may be a rats nest of edges... eventually, everything has to terminate in some sort of literal or atomic value of some sort, but we want those complex things. It's why we're not programming in raw assembler. (Though that would raise its own problems.)
Generally, we don't even think of these spaces as being "dimensional", which is to say, able to identify members by a specification of numbers in some number of "dimensions". But it is a thing we do. You can see this in programming in a very simple form pretty much anywhere you see dot-separated values of arbitrary length. A number of standards have some sort of 2.8.183.2.18.1377273 embedded in them. DNS names function this way too; behind every DNS dot could be another arbitrarily large and complicated subdomain scheme. You could proceed down a dozen simple elements and then only suddenly see an explosion of complexity. If you imagined a perfectly static web, you could theoretically (non-uniquely) identify web page locations by starting with a top-level catalog like Yahoo, and identifying web pages via the path you need to take to get there, like the number above. In reality, the space itself is shifting too fast for this to work, isn't constant, etc. But that's what n-dimensional looks like; you can identify the location of things, but it's not well-defined within that space how many "dimension" components you'll need.
Now, there actually is a natural visual mapping to all this. You have your "x.y.z = a.b.c(q, q2)" in a function, and you make it so you can click on anything, and it "zooms in" on that portion, in a way that the original expression is now left behind and gone. The problem with this natural visual mapping is you end up with a lot less context shown rather than more in the obvious, naive implementation. (Another problem is that this is basically nothing more or less than "Jump to definition", a thing I generally already have in my textual IDE.) The next obvious elaboration is to try to "expand" instead of zooming in, but this rapidly becomes impossible for all kinds of visual reasons. Maybe if we had 5 or 10 or 20 spatial dimensions to work in this wouldn't blow up so immediately, but holy crap is a mere 2 or 2.5 dimensions a kick in the pants to work with here.
There's actually two possible solutions. One is to come up with some clever visualization, the usual approach. The other approach is to attack the root of the problem and try to come up with a programming language with a much lower n-dimensionality. The latter is arguably what having s-expressions has to do with any of this, but it's still not really low enough. The problem with the latter is the resulting language is pretty unpleasant still. We kinda had them in the past, and nobody's rushing back to them. Even the crazed whackos writing Turing-incomplete languages aren't doing what you'd need for this. The need for n-dimensionality, and the value of it, just come out too readily in programming. I don't think this is a result of us having text and leaning into it, though there may be an element of that; there is intrinsic benefit in being able to take what was a constant int in the previous version of the code and inserting an arbitrarily complicated function to obtain that int some other way instead. I might just be too blinded by my own experience to be able to conceive of programming any other way, but I am even so inclined to think that's a fundamental thing everyone, everywhere will always want. And if you have that, you've introduced n-dimensionality. Kinda like accidental Turing completeness; sealing this away and still having something useful seems a non-trivial challenge to me.
Text has that characteristic where it's a well-defined operation to wedge new dimensions in anywhere, e.g. replacing a 1 with a new more complicated expression, with reasonable, if not perfect, cost/benefit tradeoffs. I haven't seen any other solutions to this. Given the number of whacks taken at it, it is at least a rational conclusion to think there may not be one. But even "rational" conclusions can still be wrong (the great rationalist conundrum, but that's a separate post). If someone does crack it, from either angle, I won't complain.
We can tell that in constrained problems it can still do OK. Obviously visual programming is used in some places completely routinely in some places today. But I've never seen an approach that would handle, say, the Linux kernel any better than text.
I would personally say that super-high-dimensional spaces are challenging. There are ways it will trip you up for sure, like the well-known "spiky spheres" which are themselves not "real" in the way we would traditionally think of them. But n-dimensional spaces can be actively perverse; they can trip you up very effectively even with relatively small instances, your every attempt to apply some simplifying assumption (even for the purposes of visual display) being rapidly and frequently destroyed by some falsification of that assumption, e.g, "function calls usually don't have more than 3 or 4 parameters" or "functions generally only have 50-100 lines tops" or... the list goes on. Which is another way of viewing why visual approaches to this problem have fared so poorly historically.
OK, cool. Thx for the all the inputs. If I get any where with it, it’ll hopefully pop up in HN. I know it may be a fools’ errand, but I’m always willing to step up to the plate… :)
Another interface for programming is a graph that shows the flow of data (like a circuit), like Blender nodes. Problem is, it only really works for functional programs, and don't work for higher order functions either
Fascinating insights, thank you. Is it not perhaps that text as is is perfect for programming languages but we could do a lot better in terms of displaying and presenting it?
I'm sure there's improvements around the edges to be made. I'm not sure if there's a massive paradigm shift that can be done.
At least not in terms of current technology; we can all imagine the Star Trek future where you can just tell the computer that you want an analysis of all the currently-interesting factors related to this diplomatic situation and a collection of AIs turn that into something effective. At that point "programmers" aren't staring at large blocks of text. Though don't be fooled; someone is going in deeper than the initial instruction. "Programming" may instead be a matter of working with an AI almost like you'd work with a coworker, but someone's going to be dedicated to the task of maximizing output. You've seen we've recently taken a big step in this direction, but we've still got a number of steps to go, and even this big step is still being processed by the world at large.
But in terms of current tech and the world of programming as we know it (which I actually believe will always be with us; while it has its manifest limitations, the ability to take concrete instructions and chew through them at light speed will always be useful; even if you've got an AI that can use teraflops and petaflops to have a real-time conversation with you, even that AI will have uses for conventional programming techniques to use those teraflops & petaflops just as we'd use them today, rather than for high-level AI functionality), I don't know if there's a huge paradigm shift that can scale to, say, the size of the Linux kernel.
But if anyone wants to create it, I think they need to start from an understanding of the positives of our current paradigm, rather than the false assumption that it's all just some sort of big mistake and it's so obvious what the better answer would have been.
You might be interested in Dion[1], which is the most impressive demonstration of such a feature that I have seen so far. However, it does not appear to be in active development any longer.
"Our ultimate goal is to build a better way to develop software. Last year we developed a prototype of some of our ideas and presented it at Handmade Seattle 2020. Some of the results were exciting, but now we think we shouldn't start by building a single integrated tool. Our new priority is the design of a new file format for source code. It's still a challenging task, but it's well scoped and achievable. Here we explain our roadmap for the development of this new format.
...
"Our goal is to build a format that compares to text files in the way that text files compare to bitmaps."
As Hal Abelson said back in 1986 in the original SICP lectures [1] while drawing `(+ 3 (* 5 6) 8 2)` and the corresponding syntax tree on the board:
>"We're writing these trees ... and parentheses are just a way to write this two dimensional structure as a linear character string. Of course, when LISP first started and people had teletypes or punchcards or whatever and (the parentheses character structure) was more convinient. But maybe if LISP started today the syntax would look more like (a 2D syntax tree)."
> How come we don't have Google Maps-like zooming with LOD mapping to abstractions at different levels?
With macroexpand-* family of emacs+slime, which I use all the time, I do have access to this. I haven't done any Aspect-Oriented Programming, but suppose it wouldn't be impossible to get similar functionality with it too.
A lesser form I use often with non-Lisp languages is narrow-to-defun and narrow-to-region, especially when I need to look at more than one area of code simultaneously.
Some IDEs can auto-inline something temporarily if that's an approximation of what you want, it's the same sort of trick as intellisense popping up documentation when you hover or whatever, it's extra info that's there when you want it and not there when you don't.
But for the broader idea I think you can also just as well ask why Bret Victor's vision has gone nowhere (and probably never will). I'm satisfied myself with an answer like this:
"Linux supports the notion of a command line or a shell for the same reason
that only children read books with only pictures in them. Language, be it
English or something else, is the only tool flexible enough to accomplish
a sufficiently broad range of tasks." -- Bill Garrett
But I also think that for many abstractions, the unabstracted form is often just fine too, and if it's common enough it becomes a 'pattern' that is read quickly in the same way that adults read words or chunks of words and not letter by letter. rpg's free book "Patterns of Software" gets into this near the beginning. For a simple example, in Common Lisp you might at some point write something like
(let ((result (mapcar f list))
(length (length list)))
...)
This is clean and abstract, you want the result of applying some function to some input list, and also get the list's length. It's too bad that this involves traversing the list twice, it'd be nice if mapcar could return the length as a second value or something. You probably don't want to modify f itself to mutate a counter, that goes beyond f's scope. But the unabstracted form of mutating a local counter and just traversing the list once with mapcar is fine, and a common enough pattern (again copying from the book):
You might call the pattern "accumulating a result" but naming things is overrated. You see it in other languages too. It's just:
Initialize some local variable result
Loop
Within the loop, update the result (in the above, it's the "length" variable)
Outside the loop, use the result
Anyway, perhaps a clever compiler could turn the first into the second, or a clever IDE could turn the second into the first, and a suitable environment lets you toggle between them at will. It'd make a cool demo maybe. Perhaps s-expressions make one or both a lot easier compared to not having them? But I doubt it because the most advanced IDEs today focus on non-s-expression languages and do fancy things just fine, some fancy things not done by any environment currently available for Common Lisp. But for such a common pattern, with straightforward code in both cases (even if the second is a bit more wordy), it'd just be unnecessary to "zoom in and out", an experienced programmer reads the second as easily as the first because they can read the "pattern". How many non-pattern abstractions do we really have in a typical software project that would benefit, and situations that aren't already good enough with simple tools like inlining, jump-to-def, and intellisense?
That example is sort of a thing in compilers. It's possible to recognise the two traversals, synthesise a new function which does both jobs, traverse once, disassemble the result. Gets called things like "map fusion" or "kernel fusion". I haven't seen it done robustly.
I tried it out months ago. It has a really limiting REPL if you’re coming from other lisps like Common Lisp or even Clojure. If your bag is interactive development, then that might be a bummer. https://github.com/carp-lang/Carp/blob/master/docs/Manual.md...
I'm excited about Carp's comprehensive and well documented[1] interoperability with C, which unlocks lots of potential for interfacing with existing libraries.
Tim Dévé has even created a game for the Nintendo Game Boy Advance by using Carp's C interoperability; you can play an emulated version online[2].
The project compiles LISP to C (with Haskell code-base) and relies on a C compiler to generate executables. This means you will have to get your C dependencies in-place to work with carp.
I was trying this from `nix-shell -p carp` and immediately ran into issue with this when building the hello world example from the README. (Package sdl2 was not found).
Not sure how to fix this just adding `- p SDL` to the nix-shell prompt does not help, as the invoked compiler does not know where to look.
Any recommendations here? I want to avoid using a global clang/gcc installation for various reasons. Is running this under `nix` a good idea? Should I move this into a container?
Sounds like a bug with the Nix package, it should be bundling SDL2 as a buildInput if Carp depends on it. I'd open a issue at nixos/nixpkgs.
As the other comment mentions, installing pkg-config and sdl2 alongside carp might work, but I suspect Nix is smart enough to properly isolate derivatives such that it wont.
> I suspect Nix is smart enough to properly isolate derivatives
That's not really relevant here, as carp itself seems to basically do code-gen and then compile stuff on its own (outside of any Nix build), so the environment in which you run carp must be set up such that the compiler and linker running in it can discover the needed libraries. Very often the mechanism used for that is pkg-config.
If you wanted a more "Nixy" way to do this, you could write a `buildCarp` suite of functions in Nix that correctly sets all this up inside of a derivation build and takes the Carp code you're compiling as an input, yielding the compiled one as a Nix store path.
For just playing around, it seems more reasonable to just add the needed dependencies to the runtime environment of carp.
Cakelisp is an S-expression representation of C++, kind of like how Assembly language is a nicer representation of machine code.
Carp is its own language which transpiles to C, like a bunch of other languages that get transpiled to C (e.g. Nim, Chicken Scheme,...).
I suppose the main trade-off (besides Clojure syntax) is RAII and MMM versus a borrow checker, consult your nearest Rust article for much back and forth about that stuff.
I only read the front page, but I can't figure out what "statically typed" even means in the context of lisp. If it isn't doing manifest value typing it's hard for me to call whatever it is "lisp"; it's something else with a lot of parentheses. Does it just mean once a symbol has taken a value of one type it can't ever take a value of another? What would the point of that limitation be?
As its name implies, Statically typing does mean that the typing is resolved in the static environment (before execution). Dynamic typing is resolved in the dynamic environment (during execution). Both enabling all kind of techniques. You need dynamic typing to have eval which is the core of the lisp language therefor you can't have a "true" lisp without dynamic typing.
At the top of that page, it says it is used in the REPL and during compilation and not available in compiled code. The guide elaborates they are also available in macros. But I don't see a mechanism for runtime eval.
We did typed racket in school[1]. It helps me wrap my brain around the concept. In if then statements, both branches must return the same type, and the whole statement is typed to that type, for example. It is pretty limiting, but hey, lisp is the programming language you can use to make other programming languages. I do agree though; common lisp and its `declare` statements hit the sweet spot for me.
It's through "gradual" typing. You check what you can as early as you wish and emit dynamic checks for the remainder.
There was a claim a while ago that gradual typing is inherently slower than static or dynamic, but that sounds like an implementation bug to me, not anything fundamental.
Extremely weird take! While there are a variety of static type systems (enough that it might even form a kind of cluster concept) they all share a pretty recognizable character: a statically typed language has a phase _before_ execution which enforces certain invariants (beyond syntax) on the behavior of the code related to what operations can be performed on what objects. Unquestionably this involves some trade offs, but to suggest that its purely a marketing term is truly bizarre.
Moreover, static typing means that every expression has a well defined type (checked or inferred statically, as the name suggests). Dynamic typing means that only values, ie the result of evaluating expressions, have types in any meaningful way. It's as far as a marketing gimmick as it comes, and there's literally a whole academic field in CS dedicated to it.
Static typing need not involve a phase distinction. It does not in dependently-typed languages, though you can always reintroduce one via some kind of program extraction.
You still get homoiconicity from the syntax which is nice since you can easily transform data into code into data into code into data... At build time.
Metaprogramming in C or Java is not fun. It's either code generation integrated with the build system, preprocessor (yuck), annotations processors or runtime weaving. Which all suck at different levels.
Don't get too hung up on the "lisp" in that title.
I think the language would better be described as statically typed, with s-expr syntax, inspired by Clojure|Rust|ML and with a lispy language accessible for use in macros. But that's less catchy.
Is it suitable for real-time embedded programming? How about audio libraries? Does it have microcontroller support?
I've heard about Carp before, but haven't ever tried it. I've longed for an embedded ML language, and I like Lisp and Scheme, so this seems right up my alley. Apologies for all the questions, but I'm just curious about potentially applicability to some things I have in mind.
The goal is certainly to have a language suitable for "real-time applications" (read games/audio), that's not my area of expertise however so your definition of real-time might not be different than mine.
As it compiles to C, any microcontroller with a compiler that supports something that looks like C can work. I've run Carp code on a GBA (as mentionned somewhere else in the comments), esp32, esp8266, as well as Arduboy & Pygamer via Arduino.
There is some more information about running Carp on embedded platforms in the docs[0].
One last thing I wanted to mention is that Carp is still very much in flux so it might not be the best choice for longterm projects, but if you're interested in playing with the language there is usually always someone to answer questions on the Gitter[1].
Wasn't this at some point written in Rust? What prompted the Haskell rewrite? I tried looking through the issue tracker but didn't find anything, just curious.
> The first version of the compiler was made 2015 - 2016, attracting a fair bit of attention in the programming community. In the spring of 2017 I rebooted the project, rewriting it in Haskell (it used to be C).
A looked at it a couple of years ago. It didn't have a Lisp like repl in the sense that it was not possible to recompile running code. I don't know if that has changed in the meantime.
https://github.com/carp-lang/Carp/blob/master/docs/LanguageG...
This part hits a real sweet spot for me:
> Carp borrows its looks from Clojure but the runtime semantics are much closer to those of ML or Rust.
One of my current side-projects is a little Civ-style game I'm working on in Racket, which I decided to start coding in on a whim. Will see how it goes moving pieces of it across to Carp.