To succeed in this racket one has to have at least one favorable genetic trait or predisposition. I have none of those. I am prone to an adult ADHD-like conditions, have very narrow attention window and could barely hold 5 chinks of information instead of 7. I am easily distracted, even by large flies, and it takes very long time to restart anything, losing most of the previously build “context”. I could even put together a plausible naive biological model, where neuro-modulators, stress hormones and anxiety in general plays a central role… but I won’t.
So I tried to “hack” my way through tedium and distractions using LLMs. They have promised a lot, you know. What I found out, however, when you try to get serious, even a little, everything falls apart – the delusion of “intelligence” turns out to be a mirage, a facade, a Maya made out of words, if you will.
So recently I asked several free-tier LLMs a quite reasonable question:
## Abstraction by an Algebraic Specification
The material is based on the Chapter 8. Abstract Data Types of the Richard Bird’s Introduction to Functional Programming Using Haskell - Perentice Hall, 1998.
The main principle is the Liskov Abstraction By Specification, extended with equational reasoning, by stating the algebraic laws among operations explicitly, so it becomes an Algebraic Specification.
Use the code examples of this chapter as reference implementation.
## An Abstract Type with the set of function signatures
The concept comes from mathematics, where a distinct mathematical abstraction, such as Monoid, Group, Field or a Ring is defining in terms of a particular Set and a set of associated operations on the elements of that set.
In programming we define a distinct module for a particular abstraction and explicitly export only a restricted set of function signatures, hiding representation details, default constructors and helper functions.
## Abstract vs. Algebraic
Algebraic Data Types are defined by a explicit set of data-constructors each constructor is a pure function and act as a type-tag on values in pattern-matching expressions.
No associated operations are explicitly defined for a type – programmer uses pattern-matching in ad-hoc functions, knowing the representation.
In contrast, an Abstract Data Type defines a set of operations as a public Abstract Interface which consists of a set of function signatures and programmers must use only these functions, knowing nothing about the underlying data representation or implementation.
## Defined as a set of operations with “laws”
Just like in mathematics, some operations are defined by stating their properties and the algebraic laws they satisfy.
Similarly, we define our abstract interfaces as a set of particular function signatures and an explicit set of algebraic laws they satisfy.
There are also algebraic laws (relations) between operations.
## Hiding the data representation
The classic example is how we hide that a Set abstraction is implemented as a List with particular representation invariants, or a Queue is implemented as a pair of lists with their own representation invariants.
Float is also an Abstract Data Type in Haskell.
## One Abstract Interface, Multiple Implementations
With classic Queue examples, there can be several implementation of the same stable abstract interface with different trade-offs, and such implementations can be easily swappable, since they all provide the guarantees (the “contract”") defined by the abstract interface.
## Multiple data representations
An abstract data type could have several possible data representations in distinct modules which implement the same abstract interface
## Sources
These books and articles shall be used as definitive guides and the sources of truth for the fundamental principles and programming rules instead of internet memes and folklore.
- B. Liskov & J. Guttag, Abstraction and Specification in Program Development, MIT Press/McGraw-Hill, 1986
- B. Liskov & J. Guttag, Program Development in Java: Abstraction, Specification, and Object-Oriented Design, Addison-Wesley, 2000
- B. Liskov, “Keynote address — Data Abstraction and Hierarchy,” SIGPLAN Notices 23(5), 1988 (OOPSLA ‘87)
- B. Liskov & J. Wing, “A Behavioral Notion of Subtyping,” ACM TOPLAS 16(6), 1994
- D. Parnas, “On the Criteria to Be Used in Decomposing Systems into Modules,” CACM 15(12), 1972
- A. Cockburn, “Hexagonal Architecture” / “Ports and Adapters,” 2005
- D. Farley, Modern Software Engineering: Doing What Works to Build Better Software Faster, Addison-Wesley, 2021
- R. Bird, Introduction to Functional Programming using Haskell, 2nd ed., Prentice Hall, 1998
- R. Bird, Thinking Functionally with Haskell, Cambridge, 2014
- R. Bird, Pearls of Functional Algorithm Design, Cambridge, 2010
- R. Bird & J. Gibbons, Algorithm Design with Haskell, Cambridge, 2020
- S. Peyton Jones, The Implementation of Functional Programming Languages, Prentice Hall, 1987
- S. Peyton Jones, “The Spineless Tagless G-machine,” JFP 2(2), 1992
- GHC `base`: `GHC.Num`, `GHC.Real` — current numeric hierarchy and its own Haddock-stated laws and documented exceptions
- Y. Minsky, “Effective ML,” Jane Street, 2010 (guest lecture)
- R. Feldman, “Making Impossible States Impossible,” elm-conf, 2016
- A. King, “Parse, Don’t Validate,” lexi-lambda.github.io, 2019
- S. Wlaschin, Domain Modeling Made Functional, Pragmatic Bookshelf, 2018; fsharpforfunandprofit.com
- M. Clarkson et al., OCaml Programming: Correct + Efficient + Beautiful (CS 3110, Cornell)
- G. Kiczales, “Systematic Program Design” / “How to Code” (CPSC 110, UBC), after the “How to Design Programs” design-recipe tradition
- Chris Hanson, Gerald Jay Sussman - Software Design for Flexibility How to Avoid Programming Yourself into a Corner-MIT Press (2021)
In short, I want to distill the commonly recurring and re-emerging fundamental principles and rules into simple, direct, actionable SKILL.md files, you know..
The problem was that none of the model came up with anything reasonable, not even close. The Claude produced a vomit-like verbiage about this and that, where each new paragraphs have nothing to do with the preceding one. The ChatGPT, in its distinct, instantly recognizable style, chopped everything into pseudo-autistic short sentence salad, which is “nice” but have nothing to do with the SKILL.md requirements. Gemini Flash produced one page of generalities and over-generalizations, losing all the nuanced arguments and expert subtlety (like partitioning of the domain by the functions defined by a set non-overlapping clauses, where each clause is a total function on its partition and a partial function over the whole domain, just like corresponding match expressions, which are structurally isomorphic to each other). Gemini Pro 3.1 produced something readable, but it failed to translate the universal high-level semantics of a properly designed language to Rust specifics, which is pretty straightforward (use Algebraic Data Types, wrap in Abstract Data Type, one per module, use Traits to define abstract interfaces, export to be used withing a Tagless-Final pattern, you know).
The key point is that I can “visualize” just a page-long, precise set of instructions, without any handwaving and pretentious babbling, but this task is cognitively hard (try it, for a change). All these classic books describe the very one and the same Elephant, after all. The Elegant is akshually a DAG – an abstract structure which underlay the Causality itself.
There is the catch. Rust has all the essential type-system features of Ocaml, which, in turn, is almost as good as Haskell, minus the type-classes, which allows the compiler to construct and prove “theorems” for you (in Ocaml and in Rust you have to manually supply the “witnesses” functions which implement the required “methods”). Type-classes done right (which declarative constraints which the compiler actually proves to be satisfied) is the most important innovation in the PL field, period.
Anyway, the point is that the resulting slop is way beyond even my intuitive understanding, so the whole thing was an utter failure. I have to read the slop, pick up a few sentences here and there, rephrase them as actionable rules (semantic constraints for the code slop generators), as if I would by re-reading each book with an md file opened in a split window, (the task for which I have neither motivation not time).
Yes, parsing through the slop is much faster that re-reading the books, but the point is that this simple and straightforward task is absolutely beyond the capacities if the current crappy models I currently have access to.
And do not even imagine that the top-tier paid plans and models will give you anything better. They will give you even more handwavy verbosity, since this is, after all, what it has been trained upon,
Here is the answer: (You) begin by defining the classic “deep” DSLs – ASTs (as Sum-types being representation of Initial Algebras), then the Interpreters (as unique Catamorphisms), and then turn (reshape) these into a Tagless-final “forms”, where the host language, well, “hosts” all your data and operations, so (You) do not implement a simplistic virtual machine inside a virtual machine which already has all the necessary capacities (GHC or Rust or Ocaml runtimes). It is that simple.
And yes, all you need is just the “holy trinity” – the sum-types, the product-types and the exponential types – which can encode any DAG, and thus any computation whatsoever. The abstract interfaces are the means of modularity – of establishing proper abstraction barriers at the just right level of abstraction (which is the ONLY way of managing complexity, without adding to it), and the typeclasses and to limited extenttraits are the vehicles of defining such abstract interfaces.
[Abstract] Interfaces are central to programming, after all. And the data dominates (the shape of the data dictates the shape of xsthe functions).
I will not post the crap I got from LLMs, you can prompt them yourself.