APL Interpreter – An implementation of APL, written in Haskell (2024) (scharenbroch.dev)

127 points by ofalkaed 19 hours ago

xelxebar 14 hours ago

> Array programming is similar to functional programming – the primary way to control execution involves composition of functions – but APL tends to encourage the reliance on global properties and sweeping operations rather than low-level recursion4.

This AoC solution is, indeed, quite the functionista! Better yet, it leans heavily into point-free expressions. The style is pretty popular amongst APL language enthusiasts and puzzlers.

That said, you actually see quite different APL styles in the wild:

- Pointed declarative style, also a popular with functional programmers (e.g. anything like this[0] from the dfns workspace)

- Imperative, structured programming, very common in legacy production systems (e.g. this[1] OpenAI API interface)

- Object-oriented, also common in somewhat newer production environments (e.g. the HTTP interface[2])

- Data-parallel style (e.g. Co-dfns[3])

Heck, APL even has lexical and dynamic scope coexisting together. IMHO, it's truly underrated as a language innovator.

[0]:https://dfns.dyalog.com/c_match.htm

[1]:https://github.com/Dyalog/OpenAI/blob/main/source/OpenAI.apl...

[2]:https://github.com/Dyalog/HttpCommand/blob/master/source/Htt...

[3]:https://github.com/Co-dfns/Co-dfns/blob/master/cmp/PS.apl

valcron1000 18 hours ago

My favorite language used to interpret my most hated language (used both professionally).

There are several things I disagree with regarding Haskell but it's understandable given that this is OP's first time using the language (like a "monad's internal state"), but I want to highlight one particular observation:

> This uncertainty of time of evaluation also makes catching errors difficult, because calling catch on the function that throws the error will not necessarily catch that error

It's important to distinguish between imprecise exceptions (ex. calls to `error `, `undefined`, and the like) and synchronous exceptions (async exceptions are not important for the article).

> Catch must be called on the function that forces evaluation on that error. This is something that is hard to trace, and something that types don’t help much with.

The types are actually the most important part here! Synchronous exceptions cannot be thrown by pure code (as long as we're not dealing with escape hatches like `unsafePerformIO`), while IO code can throw and catch all kind of exceptions .

tome 3 hours ago

Regarding catch, yes, I agree types help, but they can help even more! I suggest an IO-wrapper effect system (mine is called Bluefin; effectful is also a good choice). Then there is absolutely no ambiguity about where an exception can be handled. There is exactly one place -- no more, no less. It makes dealing with exceptions very easy.

https://hackage.haskell.org/package/bluefin-0.0.16.0/docs/Bl...

0xTJ 15 hours ago

Seeing "all built-in functions and operators are single unicode symbols" stood out to me, given that APL existed well before Unicode did. It's not that it's wrong today, but that wasn't always the case. My father used APL back in high school, and that was before the earliest year mentioned in the "History" section of the Unicode Wikipedia article.

gitonthescene 6 hours ago

It wasn’t Unicode but it wasn’t ASCII either. I think here unicode is probably shorthand for not ASCII.

howerj 17 hours ago

Is there an implementation of an APL language (or other any other array language) written in *readable* C that is around 1000 LoC? There are for LISP, FORTH, Prolog, TCL and the like.

itishappy 16 hours ago

What, you don't like Arthur's style?

https://www.jsoftware.com/ioj/iojATW.htm

https://github.com/kparc/ksimple/blob/main/ref/a.c

Slightly less factiously, the ksimple repository has a version with comments.

https://github.com/kparc/ksimple

https://github.com/kparc/ksimple/blob/main/a.c

Note, these aren't APL, but they are in the same family of array languages.

xelxebar 14 hours ago

Unlikely, at least for what I think you mean by "readable" here.

APL isn't really one of these exhibitions of computational simplicity in the way of the languages you mention. It's inventor, Kenneth Iverson, was more focused on the human side of thinking in and using the language.

Forth, Lisp, et al are quite easy to implement, but they require considerable library layers on top to make them useful for expressing application-level logic, even if we just focus on the pure functions. APL, on the other hand, has a larger core set of primitives, but you're then immediately able to concisely express high-level application logic.

Are you looking for a kind of reference implementation for learning purposes? If so, I'd say the best route is just go with the docs. Arguably the Co-dfns compiler is a precise spec, but it's notably alien to non-practitioners.

ofalkaed 13 hours ago

Any pointers on how to get better at expressing high-level application logic in APL? Any good resources on programming in APL? So far I have only found tutorials and basic stuff for learning APL but not much on applying APL. I am slowly improving and think I sort of get it but probably don't.

xelxebar 12 hours ago

geocar 9 hours ago

ofalkaed 17 hours ago

Not that I have found, closest is an incomplete one done in python.

https://mathspp.com/blog/tag:lsbasi-apl#body-wrapper

gitonthescene 6 hours ago

Readable is probably in the eye of the beholder but here’s a partially expanded version of ngn/k https://codeberg.org/growler/k/src/branch/expand/a.c

The expansion is mechanical and thus not really at attempt at readability.

dzaima 4 hours ago

Beyond what others have mentioned, I think another big differentiating factor between APL and the rest of those languages is that APL isn't focused on allowing the user to expand the language meaningfully, but rather on being a well-rounded language by itself (which is how it can be reasonably useful without objects with named fields, mutation, explicit loops (or only gotos in APLs infancy!), no first-class functions, no macros, and only one level of higher-order function (though of course most APL implementations have some of those anyway)).

As such there's really no pretty "core" that pulls its weight to implement in 1000LoC and is useful for much.

Here's a simple minimal APL parser in JS that I wrote once to display one way of parsing APL: https://gist.github.com/dzaima/5130955a1c2065aa1a94a4707b309...

Couple that with an implementation of whatever primitives you want, and a simple AST walker, and you've got a simple small APL interpreter. But those primitive implementations already take a good chunk of code, and adding variables/functions/nested functions/scoping/array formatting/etc adds more and more bits of independent code.

Perhaps if you accept defining bits in the language in itself via a bootstrap step, BQN is a good candicate for existing small implementations - a BQN vm + minimal primitive set is ~500LoC of JS[0] (second half of the file is what you could call the native components of a stdlib), 2KLoC for first public commit of a C impl[1], both of those having the rest of the primitives being self-hosted[2], and the compiler (source text → bytecode) being self-hosted too[3]. (the C impl also used r0.bqn for even less required native primitives, but modern CBQN uses very little of even r1.bqn, having most important things native and heavily optimized)

[0]: https://github.com/mlochbaum/BQN/blob/master/docs/bqn.js though earlier revisions might be more readable

[1]: https://github.com/dzaima/CBQN/tree/bad822447f703a584fe7338d...

[2]: https://github.com/mlochbaum/BQN/blob/master/src/r1.bqn (note that while this has syntax that looks like assigning to primitives, that's not actual BQN syntax and is transpiled away)

[3]: https://github.com/mlochbaum/BQN/blob/master/src/c.bqn

geocar 9 hours ago

ngn's k is publicly available, around 1000 lines and readable, in that I can read it.

librasteve 9 hours ago

this is pretty cool

i think the first raku (perl6) parser (pugs) was written in Haskell, certainly all the team learned Haskell before they started

_glass 2 hours ago

Done by Audrey Tang who later on became Minister of Digital Affairs of Taiwan.

behnamoh 18 hours ago

Depending on your personality type (perfectionist or pragmatic), this is either the best of both worlds or the worst.

instig007 18 hours ago

> Apparently Haskell’s performance isn’t that bad, but I don’t plan on using Haskell for anything that is remotely performance-sensitive. Trying to optimize Haskell code does sound like an interesting problem, but it might be a lost cause.

When the dude uses `foldl` over lists and `foldr` with `(*)` (numeric product) it is not the language that's the lost cause.

kccqzy 17 hours ago

In case anyone who doesn't know Haskell: both of these are beginner level mistakes. Using `foldl` causes space leaks and turns your O(1) space algorithm to O(N) space for no good reason. And using `foldr` over lists is good when you are dealing with unevaluated lists and want list fusion, but not when they already exist in memory and will continue to do so. And that doesn't even include the obviously wrong choice of data structure, built-in Haskell lists are singly-linked lists not arrays. There are Array types and Vector types (both come with GHC so no extra dependency needed) that are more appropriate for implementing APL in the first place.

itishappy 16 hours ago

> And using `foldr` over lists is good when you are dealing with unevaluated lists and want list fusion, but not when they already exist in memory and will continue to do so.

What's the preffered approach?

tome 3 hours ago

farrellm23 16 hours ago

pierrebeaucamp 6 hours ago

> When the dude uses `foldl` over lists and `foldr` with `(*)` (numeric product) it is not the language that's the lost cause.

This is a great example of Haskell's community being toxic. The author clearly mentioned they're new to the language, so calling them a "lost cause" for making a beginner mistake is elitist snobbery.

I usually don't point these things out and just move on with my life, but I went to a Haskell conference last year and was surprised that many Haskell proponents are not aware of the effects of this attitude towards newcomers.

tome 3 hours ago

Can I ask which conference? Did people behave towards you in that way at that conference, or are you referring to behaviour online? I will try to use whatever authority I have in the Haskell community to improve the situation.

(Still, hopefully in this case it's clear from instig007's reply that it's not a member of the Haskell community behaving in that way.)

instig007 4 hours ago

> calling them a "lost cause" for making a beginner mistake is elitist snobbery.

I wonder how do you call the practice of complete beginners spreading FUD and suggesting to their readers that something in the language is "a lost cause", all whilst having neither enough knoweldge nor sufficient practice to make assumptions of this kind.

> This is a great example of Haskell's community being toxic

To be clear: I don't represent haskell community, I'm not part of it, and I couldn't care less about it. It just so happened that I saw the author inflating their credentials at the expense of the language via spreading FUD, that the beginners you seem to care about are susceptible to, and I didn't like it.

If you get triggered by the expressed dissatisfaction with the author's unsubstantiated presumptuousness, reflected back at them in a style and manner they allowed themselves to talk about the thing they don't know about, then it's purely on you and your infantilism.

nickzelei 16 hours ago

Interesting read!

On a semi-related topic: I tried learning Haskell this past weekend out of curiosity that I last tried it some 10+ years ago while still in college.

I found resources for it scant. Coming from more modern languages/tooling like Go/Rust, I also struggled quite a bit with installation and the build/package system.

I tried the stack template generator for yesod/sqlite and after some 15 minutes of it installing yet another GHC version and building, I eventually ctrl+C'd and closed out of the window.

Maybe this was a unique experience, but I'd love some guidance on how to be successful with Haskell. I've primarily spent most of my professional years building web services, so that was the first place I went to. However, I was taken aback by how seemingly awful the setup and devex was for me. I've always been interested in functional programming, and was looking to sink my teeth in to a language where there is no other option.

shae 16 hours ago

The easiest and fastest way to get everything installed is ghcup https://www.haskell.org/ghcup/

As for being successful, there are several nice books, and several active forums. I've gotten good answers on the Libera IRC network #haskell channel, and on the Haskell matrix channel #haskell:matrix.org

If you want to get started without installing anything, there's the exercism track: https://exercism.org/tracks/haskell

I've heard good things about Brent Yorgey's Haskell course ( https://www.cis.upenn.edu/~cis1940/spring13/lectures.html ) but haven't tried it myself.

itishappy 16 hours ago

My understanding is that Cabal has more or less supplanted Stack. Use GHCup to install everything, then use `cabal init`, `cabal run`, or `cabal repl` like you would in Go/Rust.

Stack builds on top of Cabal, and used to solve a bunch of problems, but the reasons for it's existence are no longer super relevant. It still works totally fine if that's your thing though.

cosmic_quanta 12 hours ago

I learned using the Haskell Programming from First Principles book (haskellbook.com). I don't think it goes into web development, but it certainly goes through the basic project setup.

Do you think you would have benefitted from a resource like the Rust book? I've been toying with the idea of writing something similar and donating it to the Haskell Foundation

internet_points 9 hours ago