> APL makes it really convenient to take a different tack. Instead of designing abstractions, we can carefully design our data to be easily operated on with simple expressions. Where you would normally see a library function or DSL term, this approach just uses primitives directly
But in doing that, aren't you simply moving the complexity of the abstractions from the functions code into the data structure? Sure you can use generic operators now, but then you need to carefully understand what the value pairs represent in terms of domain logic and how to properly maintain the correct structure in every operation. Someone reading the program for the same time will have just the same difficulties understanding what the code does, not in terms of primitives, but in terms of business domain meanings.
I mean there is an improvement in your approach, but I don't think it comes from putting the complexity at a different place, but because this way you get to see the code and the actual actual values at the same time.
I have the insight that what makes complex programming easy is this juxtaposition of runtime data and code operations visible together. That's why IDE tools have building better and better debuggers and inspectors to let you see what the program is doing at each step.
In that context, creating new good concise abstractions is a good thing, wether you abstract parts of the operations or parts of the data structure.
You're absolutely right that this approach encodes domain complexity in the data, and I agree that's not the magic sauce per se.
Honestly, it's just really hard to convey how simple code can be. Imagine a nerual net inference engine in 30 straightforward lines of code and no libraries! [0] On one page you really can read off the overall algorithm, tradeoffs made, algorithmic complexity, and intended highlights.
Abstractions encourage hiding of information whereas subordination encourages keeping it implicit.
Are you familiar with Observability 2 and wide events? In this data-first model we effectively encode all program behavior into a queryable data structure. I'll often breakpoint something I'm working on and iteratively drill down into the data to get to the root of a problem. That's just so friggin cool, IMHO.
Abstraction almost always manages complexity by introducing some control flow barrier like an API call. Observing said control flow is a lot more indirect, though, either via statistical sampling or in flight observation. In contrast, imagine if you could simply throw SQL queries at your application and you get an idea of what I'm stumbling to convey here.
> But in doing that, aren't you simply moving the complexity of the abstractions from the functions code into the data structure?
The core idea behind most of functional programming and APL is that data don't change. Policies do. So you design a set of primitives that can represent any data types by combination, then build a standard library that combine and split them, as well as transform them into each other. Then you have everything you need for expressing policies.
Let's take music notation as an analogy. You only need the staff for pitch and the note-head for duration. And with those (plus other extra symbols), you can note down most music. You can move higher up with chords, but you don't really need to go much higher than that.
Data are always external to the business domains. Only policies are internal. Those policies need semantics on data, so we have additional policies that takes rough data and transform them into meaningful one. What Java, C#, and other do is fixing those semantics in the code and have the code revolve around them. But those labels are in fact transient in either the short or long term. What you really need is a good way to write policies and reasons about them.
I like Go because it lets you define data semantics well, but don't confine your policies.
> Someone reading the program for the same time will have just the same difficulties understanding what the code does, not in terms of primitives, but in terms of business domain meanings.
That's what naming and namespacing is there for. Also docstrings.
Seeing that diamond metaphor, and then learning how APL sees "operators" as building "functions that are variants of other functions"(1), made me think of currying and higher-order functions in Haskell.
The high regularity of APL operators, which work the same for all functions, force the developer to represent business logic in different parts of the data structure.
That was a good approach when it was created; but modern functional programming offers other tools. Creating pipelines from functors, monads, arrows... allow the programmer to move some of that business logic back into generic functions, retaining the generality and capacity of refactoring, without forcing to use the structure of data as meaningful. Modern PL design has built upon those early insights to provide new tools for the same goal.
if I could write haskell and build an android app without having to be an expert in both haskell and low level android sdk/ndk, I'd be happy to learn it properly.
It is! (https://github.com/cnlohr/rawdrawandroid) - and that's just writing a simple an android app in C. If you want to access the numerous APIs to do anything useful, it's more pain.
> If you mean LLMs, I actually view them as a regression with respect to basically every one of the "characteristics of notation" desired by the article.
LLMs are not used for notation; you are right that they're not precise enough for accurate knowledge.
What LLMs do as a tool is solving the Frame Problem, allowing the reasoning system to have access to the "common sense" knowledge that is needed for a specific situation, retrieving it from a humongous amount of background corpus of diverse knowledge, in an efficient way.
Classic AI based on logical inference was never able to achieve this retrieval, thus the unfulfilled promises in the 2000s to have autonomous agents based on ontologies. Those promises seem approachable now thank to the huge statistical databases of all topics stored in compressed LLM models.
A viable problem-solving system should combine the precision of symbolic reasoning with the breadth of generative models, to create checks and heuristics that guide the autonomous agents to interact with the real world in ways that make sense given the background relevant cultural knowledge.
What matters is if the current output after each request complies with the given specifications, and if it's possible to solve the bugs until the code converges into stability.
> how do you know that the code it just created actually does what you want it to
I believe that problem can be solved, and will be with the correct new tools.
I think a new type of "low-code" programming language will evolve tailored for domain experts that don't know coding, but understand how to guide an LLM.
The development environment should be a crossover between a project-management tool (Jira or Azure devops style) with literate programming: something that allows organising all the separate parts that compose the program in a way understandable by the human and the AI bot.
Online notebooks are a good first step in that direction, in special the "reactive" kind where the runtime logic does not depend on the order you change things in the page.
The classical answer of why more hardware resources are needed for the same tasks is that the new system allows for way much more flexibility. A problem domain can be thoroughly optimized for a single purpose, but then it can only be used for that purpose alone.
This is quite true for LLMs. They can do basic arithmetic, but they can also read problem statements in many diverse mathematical areas and describe what they're about, or make (right or wrong) suggestions on how they can be solved.
Classic AIs suffered the Frame problem, where some common-sense reasoning depended on facts not stated in the system logic.
Now, LLMs have largely solved the Frame problem. It turns out the solution was to compress large swathes of human knowledge in a way that can be accessed fast, so that the relevant parts of all that knowledge are activated when needed. Of course, this approach to flexibility will need lots of resources.
The article is deliberately ignoring the history of software, which in the 90's gave a very clear definition of "open source" by the OSI as a synonym of "free software", which also historically means the four freedoms defined by Richard Stallman.
You may want to ignore what words have come to mean and try to derive a literal meaning from their constituent words, but then you need to fight established common use.
Maybe it's not deliberate, maybe they are just unaware, the 90's is a long time ago now.
It feels like OSI and Open Source advocates spend a lot of time and effort trying to fight people from using "open source" and "free software" in their literal meanings rather than the other way round. The established common use is only really within the communities that use them.
> Maybe it's not deliberate, maybe they are just unaware, the 90's is a long time ago now.
The history is much discussed. This ignorance would be fixed by a Wikipedia article. And I doubt an open source developer with forceful unconventional opinions about the definitions of open source and FOSS never saw FSF or OSI mentioned.
> The established common use is only really within the communities that use them.
This is tautological. Terms are used in the communities which use them.
The article was directed to the community which uses terms like open source and FOSS. I am confident my comments took less time to write than the article.
> Isn't that the way with everything when there's a difference of opinion on the internet. The ones that have the most funding/largest platform end up winning the argument.
No. Most attempts to change the definition of open source come from come from companies attempting to profit from the change. Most resistance comes from individuals protecting a gift economy.
You've just described how laws actually work - but we have created modern judiciary system so that it will tend to produce outcomes considered fair by the majority. Algorithmic enforcement of justice without human deliberation of case-by-case specifics would be worse that the worst horror stories about soulless bureaucracies.
That's why we have judges and lawyers, so that the outcome can be decided as a communal process instead of just one person deciding what is punishable - even if the person is the developer building the automated justice dispenser and they'll be not around when the decision is taken, it would still be made by the whims of a single enforcer.
You've just observed the fact that even the least ambiguous and subjective language possible still requires interpretation, not that laws are meant to be ambiguous or subjective.
It could, but it doesn't want to. The whole MS Office dominance came into being by making sure other tools can't properly open documents created by MS tools; plus being able to open standard formats but creating small incompatibilities all around, so that you share the document in MS format instead.
But in doing that, aren't you simply moving the complexity of the abstractions from the functions code into the data structure? Sure you can use generic operators now, but then you need to carefully understand what the value pairs represent in terms of domain logic and how to properly maintain the correct structure in every operation. Someone reading the program for the same time will have just the same difficulties understanding what the code does, not in terms of primitives, but in terms of business domain meanings.
I mean there is an improvement in your approach, but I don't think it comes from putting the complexity at a different place, but because this way you get to see the code and the actual actual values at the same time.
I have the insight that what makes complex programming easy is this juxtaposition of runtime data and code operations visible together. That's why IDE tools have building better and better debuggers and inspectors to let you see what the program is doing at each step.
In that context, creating new good concise abstractions is a good thing, wether you abstract parts of the operations or parts of the data structure.
reply