Conal Elliott writes on functional programming, functional reactive animation, and compiling Haskell to categories and parallel hardware.
HN commenters recommend his work on functional programming and list his site among favorite personal blogs.
Computers are not a kitchen. They are clay, they are Lego, they are jungles of imagination. It's true that our current trajectory devolves their universality to a set of discrete isolated applications == multiplexing a set of appliances on one device.
That's an economic win sure, but it's tragic if we fail to unlock more of their flexibility for "end users"! IMHO that's the biggest unsolved problems of computer science: that it takes so much professional learning to unlock the real potential (and even that fails us programmers much of the time! How frequently do you say "I'll solve it myself" and the time invested is actually worth it? In the words of Steve Krouse we've not quite solved "End-programmer Programming" yet.)
I do want FOSS to follow through and offer live inspectors for everything, but no, I no longer believe users should "learn to code" as the salvation. We're nowhere near it being worth their time, we actually went downhill on that :-( Conversational AI and "vibe using" will play a role, but that's more like "adversarial interoperability", doesn't cover what I mean either.
I want cross-app interoperability to be designed in, as something everyone'd understand users want. I want agency over app state — snapshot, fork, rewind, diff etc. I want files back (https://jenson.org/files/), and more¹. I want things like versioning and collaboration to work cross-app. I want ideas and metaphors we haven't found yet — I mean it when I call it unsolved problem…
> As far as I know, one can create a denotational semantics for any well-defined language. In practical terms, you could pick, say, JavaScript and do a similar representation as explained in the article: Every “non-pure function” is evaluated to a description of the state change that is caused by that function.
Yes this argument has been done before [1], so it's important not to lose sight of the real value proposition:
I want the ability to write a function which returns the same output for the same input, regardless of when/where it's called, number of threads, whether it's in a test or in prod, and so on. But I can already do that in an any programming language, so the real value here is being able to mark the function as such, and have the compiler reject me if I'm wrong.
> However, the more I look into this the less I buy into the whole “functional semantics” for side-effects argument. Immutable data is great, explicit dependencies are great, but those are about minimizing the use of side-effects, not about dressing them up.
A function with minimal side-effects is a function with side-effects [2].
> effectful computations are marked with an ! so they are easier to spot
There's two important steps that you've rolled into one. The first step is the 'functional semantics (with explicit effects)'. E.g. you take your straight-line imperative program:
html = Http.getUtf8(url); path = Path.fromStr(filename); File.writeUtf8(path, html); Stdout.line ("Wrote…
> You can replace bash with JS
Noo-ot really except in a Turing-tarpit sort of way. Like it or not, once learned (!) Bourne shell together with the traditional tools is a well-designed user interface and works even better for that than, say, Tcl[1]. Not an automation or scripting language, a user interface for all daily interaction. I would absolutely hate to manually sort through my files in JS, while in shell I can often do it faster than in a GUI file manager.
And, of course, it’s also a fairly strong contender as a programming language in the paradigm of many pipelined imperative processes—probably because that paradigm remains largely unexplored. I can only maybe name Icon as a viable competitor, and Icon’s also very nice. (Python, no matter how “inspired” it is by Icon, has traded its command of streams for more mainstream ease of use.) By comparison, the old complement of parallellized JS build tools (Gulp? Grunt? I forgot, it’s been a while) always surprised me with how awkwardly it accomplished shell-script-equivalent tasks.
To be clear, it’s not that Bourne shell is good and JS is bad. JS is a passable Fortran[2], while shell is at best a marginally functional one—there’s a reason Awk exists. But shell competes in categories that most other languages don’t even try to qualify for.
[Yes, I know about rc. I happen to think rc’s focus on one-level lists of strings (incidentally shared by Jam, an attempt at a better make) is a mistake, and more consistent…
Any imperative algorithm admits functional mathematical interpretations. While humorous, the observation that programming in C is purely functional[1] isn't wrong. Also consider, for example, that the formalism for the Turing Machine, the archetypical imperative automata, is purely functional.
More seriously, the semantics of an imperative algorithm are perhaps always best understood as mathematical functions. Predicate transformer semantics reduce verifying an imperative program to proving a first-order formula. Leslie Lamport, who most people doing so-called "formal verification"[2] know for TLA+, is also a shining light in this area of research. They are observably complementary cognitive tools for the task of writing correct programs, even if one is attracting more grants. The trick is learning to write programs that reduce to formula that can practicably be solved, automatically or not.
Granted, for many languages that first-order formula might be difficult to solve for many programs, but that doesn't change the fact that there exists a functional description of the procedure's semantics, even if the process the hardware actually executes isn't so analyzed[3]. But then that's true for Haskell too. It's just Haskell and its cousins do a comprehensive job of beating you over the head that you're reasoning about program semantics and not processes.
[1] http://conal.net/blog/posts/the-c-language-is-purely-functional
[2] Correctly it should be called automatic…
I think saying that FP is bad for "manipulating state" is misleading.
FP is great at manipulating state. A pure Haskell function (State -> State) manipulates state.
What's being discussed is whether or not the state is manipulated destructively. And personally I don't see any real benefits to the destructive method, except performance issues in some cases, and arguably stylistic ones.
Returning new values instead of in-place updates may seem cumbersome before you get to know the abstractions that make it easy to use.
As an example, in Haskell, if you want to update a value deep inside multiple nested records, you do something like:
movePlayer :: (Pos -> Pos) -> GameState -> GameState
movePlayer f gs = gs { player = oldPlayer { position = f oldPos } }
where
oldPlayer = player gs
oldPos = position oldPlayer
That looks horrible, compared to something like:
gs.player.position = f(gs.player.position);
But then, if you discover the Semantic Editor Combinators abstraction (http://conal.net/blog/posts/semantic-editor-combinators/), then you can write that as:
movePlayer :: (Pos -> Pos) -> GameState -> GameState
movePlayer f = (atPlayer . atPosition) f
or just:
movePlayer = atPlayer . atPosition
Then you can use:
movePlayer (+delta)
to add delta to the position.
Now one nice thing about the return-new-value approach is that it is more composable than the destructive update approach. When you compose state…
Language innovation is tightly tied into computer science innovation. There are plenty of paradigms that are fundamentally different and quite potentially better than UNIX and LISP out there- you just need to get out of that rut.
Mozilla's language [Rust](https://www.github.com/graydon/rust/wiki) is working on static verification (using typestate) and concurrency in a C/C++ level language.
The Haskell community has several people ([Conal Elliot](http://conal.net), [Luke Palmer](http://lukepalmer.wordpress.com)) who are working on new models of functional I/O such as functional reactive programming.
There is a lot of programming language innovation in parallel/concurrent programming that's just hitting mainstream- actor model, asynchronous programming, CUDA/OpenCL, etc.
The way you talk to the machine is an extremely important part of "how to get problems solved." If you can't express something without absurd amounts of overhead, you're not going to solve the problem that way.