As someone who’s been programming in Haskell for over a decade, including many years professionally, it’s frustrating not to see a better discussion of the use of sum types - Option/Maybe is brushed off as ”…inherently messier to use than languages with null but I tend to feel that it’s worth the pain” but when you are used to using sum types everywhere, you start being able to encode invariants of your program into the types, and the compiler guides you through all the changes you need to make.
For the cases mentioned in the post, the types a, Maybe a, [a] and NonEmpty a get you an extremely long way, but there’s so much more. The type
data These a b = This a | That b | These a b
gives us “at least one a and one b, maybe both”.
The definition of the standard FingerTree structure has invariants around parts of the three having one, two, three or four elements exactly, so uses
data Digit a = One a | Two a a | Three a a a | Four a a a a
to enforce that - using Vec a would just be wrong here, but we can be explicit about every single case and know out code is likely to be correct.
In industry I’ve used these ideas a lot, we spent a lot of time thinking about exactly what state are valid in our system and then ensuring that only those states are possible. Sometimes you need more than null, you actually need to know why a value doesn’t exist: data Trust = NoneProvided Reason | Zero Currency | NonZero CashAmount Currency to butcher an example for a system I’m currently working on.
It may feel tedious to enumerate the possible states, but it eventually becomes routine, and the huge benefit in maintainability far outweighs the upfront cost.
I too found this article strange to read, in the sense that I have never experienced any of the problems the author brings up, regarding it being messy to work with types that maintain invariants. I wish he had given some examples of real code so I knew what he was talking about.
How refreshing to read about aspects of modeling a domain, rather than just technologies, frameworks, computing infrastructure, and more technologies.
One of the realities of software development is that domain experts often do not know what they want. Iterative development based on feedback is intended (in an ideal world) to address that.
While people are bringing up dependent types, a more apt analog is refinement types: types constrained by an additional boolean predicate. Here’s how you’d do OneOrMore<T> in Liquid Haskell:
{-@ type OneOrMore a = {v:[a] | len v > 0} @-}
Unlike dependent types, refinement types can share the same API as the base type, which makes using them easier. Like dependent types, static checking refinement types is really hard.
I think there’s some cool design space to split refinement types into a static type part and a runtime contract part. Kind of like what the OP does with assert!(v.len() > 0), except the sprinkling is handled for you.
Yes, but are there any programming languages that make structures like this easy to work with?
For example if you have “at least one” and (T, List) and you want to insert into the front of the list it is a bit awkward. I think you could probably make a generic type that made this easy to work with (or even adding a min parameter to existing list types) but I wouldn’t want to pass around funny tuples.
We use the NonEmpty type in Haskell all the time, it has mostly the same interface as list. IMO the basic list of structures you need for 99% of cases representing relationships between data are:
a
Maybe a
[a]
NonEmpty a
Beyond that is quite rare, and at that point you should really be using custom types that only allow the valid states.
Good article and it is a problem I have run into more than a few times over the decades.
It’s interesting that the author chalks incorrect choice of multiplicity up to miscommunication between domain experts and programmers.
That happens, but it is the exception in my experience. Much more common is that the domain experts get it wrong initially. The spec clearly says there will be 1 address. The programmer, having gone down this road before, double-checks and asks specifically if the software will need to handle multiple addresses. The domain experts say no, it should be exactly 1, and their answer is unambiguous.
Then six months later, the domain experts and programmers discover together that the answer was wrong.
Hmm, kinda depends on who you’re considering the domain experts. In my experience the root cause of these situations is that the “domain expert” doesn’t actually know shit about the domain, and is just a manager of some sort that either manages or represents the people that will use the software, which are the actual domain experts, but have no political power in the organization, so no one listens to them.
In particular, there is almost always a gap between domain experts (the people who have a need which can be met by creating a new, or adapting an existing, program) and programmers (the people who write programs).
Why haven’t we yet made programming approachable enough that the domain experts can be the programmers rather than having to delegate to programmers? The immediate cynical answer that comes to mind is that we programmers like our job security. But I wonder if there are other, better reasons.
What do you think about this essay which argues that things like Visual Basic and HyperCard were on the right track, but then the late 90s web boom (and, according to a later essay, open source), halted progress in that area?
I’m not hwayne, but I agree with him—it’s a lot harder than we think it is. Basically, programming requires tracking detail, enough detail that would daunt most people. Witness the number of articles about fallacies that programmers (people trained to track such details) make about human names, addresses, phone numbers or dates, just to name a few areas.
Here’s my question to you—how do you define “computer literacy”?
Poppycock. There are few imaginary products I can think of that would be more valuable to their creator than the “AI that replaces programmers”, it’s just not something we have any idea how to do.
Small parts of programming do get automated over the years, with things like garbage collection and managed runtimes, but so far this has always lead to an increase in the kinds of tasks we expect computers to handle, rather than doing the same basic tasks with fewer programmers. This makes sense because it gives the business an advantage over competitors in whatever their core business happens to be. They’d (the companies that survive) rather do more and charge more / get more customers, than do the same for slightly less.
First of all, I’ll say that I agree with hwayne and think that’s the primary reason we don’t have many non-programmer friendly coding/automation tools.
The first essay you linked alludes to this, but I think the point should be emphasized, there’s an incentive mismatch between programmers and end-users. Programmers often like to program because they enjoy the act of programming. Look at how many links we get on this forum about programmers waxing and waning about the joys of TUIs, architectural simplicity, or networks run for and by skilled operators. These are all things that are immaterial to, or even detrimental toward, the user experience of a non-programming SME. Even with today’s world of skilled programmers running large cloud systems, programmers still complain about how much they need to accommodate the whims of non-technical users.
This isn’t unique to programming. Trades folks in a lot of trades often talk shop about better access platforms/crawl spaces, higher quality parts, more convenient diagnostic tools, and other stuff that non-tradespeople would find spurious expenses/concerns that sometimes may even make the tradesperson’s work less aesthetic (say in a residence.) I think there are many complicated factors that make this incentive mismatch worse in programming than in trades. As long as this incentive mismatch exists, I think you’ll only see limited progress toward non-technical programming accessibility.
Having been in the position of “software engineer for SME’s” a few times… Making really good software that you would actually want to use in production is a craft, a skill of its own, and one that takes a lot of time and work to learn. Most software people are interested in software for its own sake, because the craft is fun. Most SME’s are not, and so they will learn as much as is necessary to bang together a solution to their problem and it doesn’t really matter how nasty it is. They want to be working on their subject matter, not understanding cache lines or higher order functions.
We can rephrase the question: “Why haven’t we yet made woodworking approachable enough that the people who use furniture can be the carpenters rather than having to delegate to carpenters?” Sure, if you are actually interested in the process of building furniture then you can make lots of amazing stuff as a non-professional, and there’s more sources out there than ever before for an interested novice getting started. But for most people, even assembling IKEA furniture is more work and suffering than they really want to expend.
I think the whole idea is to make the “band together something that solves the problem” option more possible and more common.
So many people spend so much of their lives using computers to manually do trivially automated things, but the things are all too bespoke for a VC funded startup to tackle making a “product”.
This works pretty well as long as the tools those people build are only used by that person. Which is pretty important! The problem appears when someone’s bespoke little tool ends up with its tendrils throughout an organization, and now suddenly even if it isn’t a “product” it is essential infrastructure.
I would say it’s for the same reason why programmers can’t be the domain experts; expertise in any field takes time, effort and interest to develop.
For example, a tax application where all the business rules were decided by developers and a tax application developed by accountants would probably both be pretty terrible in their own ways.
A lot of the other responses I almost entirely agree with, but to add my own experience:
I’ve been a part of some implementations of these types of tools, and also read a lot about this subject. Most people building these tools aren’t building “programming that’s easy for non-developer” but “I find ____ easy so I’m going to remove features so that it’s more approachable”. It also leads a lot to either visual programming languages, which don’t directly solve the complexity issues, or config languages, which lack the necessary surface area to be usable for many tasks.
A prior team of mine tried to go down the config route, building out 2 different config languages that “can be used by managers and PMs to configure our app so that we can focus on features.” Needless to say, that never happened. No one did any research on prior attempts to do build these types of languages. No one tested with PMs and managers. It ended up being built by-devs-for-devs.
There’s also this idea that floats around software that somehow simpler languages aren’t “real” languages, so they often get a lot of hate. For many years I’ve heard that Go isn’t for real devs, that it’s only for stupid Google devs who can’t be bothered to learn a real language like Java. JS is still considered by many to be a joke language because it’s for the web and “real” developers program servers, desktops, and mobile. Way back in the day, Assembly was for the weak, “real” devs wrote out their machine code by hand/punch card. Unless we can overcome that instance of what a “real” programming language is, we’ll likely continue to struggle to find and build accessible languages.
One of the few people I know writing about approach-ability of programming, and attempting to actually build it is Evan C. I won’t claim that Elm is perfect, I do think we can do better, but Evan has worked very hard to make it approachable. So much so that both its error message approach and its Elm Architecture have permeated many other languages and frameworks without people realizing it.
When you start learning a programming language, how much time do you spend stuck on syntax errors? [..] how many people do not make it past these syntax errors?
Most terminal tools came into existence well before our industry really started focusing on making apps and websites feel great for their users. We all collectively realized that a hard to use app or website is bad for business, but the same lessons have not really percolated down to tools like compilers and build tools yet.
The answer to me comes down to time. I can gather requirements, speak to stakeholders, and organize projects, or I can write code, test code, and deploy code. I do not have the time (or attention span, really) for both.
People have been trying this for a very long time. It results in very bad programs. The idea that programming can be magic’d away and we can fire all the programmers is held only by marketing departments and (for some reason) a few programmers.
Of course, I can make my own ZeroOrMoreVec<T> and OneOrMoreVec<T> types
My mind started to go there as soon as they introduced the problem of modeling a non-empty collection. And I seem to recall a language or framework that supports this — IIRC you could parameterize a collection by min and/or max size and the type system was pretty smart about tracking this. So for example Vec<Min,Max>.pop() returns Vec<Min-1,Max-1> and of course won’t compile if Min==0.
Doing this in general (rather than special casing some collections or types in the compiler) is dependent types, which he alluded to at the end. This example is actually the canonical example of dependent types. As a name, “dependent types” doesn’t really convey what it is, though: it makes values into types. The “dependent” bit is from the idea that the type of a thing depends on its value. Here, for example, Vec<String, 5> (not real notation!) would be an item which has both the value that it has five items in it and a type that it has five items in it. That’s incredibly powerful, but it also comes with the cost of that much more type machinery to learn and maintain, and is (therefore?) very far from mainstream.
As someone who’s been programming in Haskell for over a decade, including many years professionally, it’s frustrating not to see a better discussion of the use of sum types - Option/Maybe is brushed off as ”…inherently messier to use than languages with null but I tend to feel that it’s worth the pain” but when you are used to using sum types everywhere, you start being able to encode invariants of your program into the types, and the compiler guides you through all the changes you need to make.
For the cases mentioned in the post, the types
a
,Maybe a
,[a]
andNonEmpty a
get you an extremely long way, but there’s so much more. The typegives us “at least one
a
and oneb
, maybe both”.The definition of the standard
FingerTree
structure has invariants around parts of the three having one, two, three or four elements exactly, so usesto enforce that - using
Vec a
would just be wrong here, but we can be explicit about every single case and know out code is likely to be correct.In industry I’ve used these ideas a lot, we spent a lot of time thinking about exactly what state are valid in our system and then ensuring that only those states are possible. Sometimes you need more than
null
, you actually need to know why a value doesn’t exist:data Trust = NoneProvided Reason | Zero Currency | NonZero CashAmount Currency
to butcher an example for a system I’m currently working on.It may feel tedious to enumerate the possible states, but it eventually becomes routine, and the huge benefit in maintainability far outweighs the upfront cost.
I too found this article strange to read, in the sense that I have never experienced any of the problems the author brings up, regarding it being messy to work with types that maintain invariants. I wish he had given some examples of real code so I knew what he was talking about.
How refreshing to read about aspects of modeling a domain, rather than just technologies, frameworks, computing infrastructure, and more technologies.
One of the realities of software development is that domain experts often do not know what they want. Iterative development based on feedback is intended (in an ideal world) to address that.
While people are bringing up dependent types, a more apt analog is refinement types: types constrained by an additional boolean predicate. Here’s how you’d do
OneOrMore<T>
in Liquid Haskell:Unlike dependent types, refinement types can share the same API as the base type, which makes using them easier. Like dependent types, static checking refinement types is really hard.
I think there’s some cool design space to split refinement types into a static type part and a runtime contract part. Kind of like what the OP does with
assert!(v.len() > 0)
, except the sprinkling is handled for you.Multiplicities are hard if you don’t study your theory.
And now you combine them with tuples.
Of course, you can throw dependant types and interfaces to it to make it nicer.
Laurence Tratt has been publishing PLT papers for almost two decades, I think he knows the theory.
Yes, but are there any programming languages that make structures like this easy to work with?
For example if you have “at least one” and
(T, List)
and you want to insert into the front of the list it is a bit awkward. I think you could probably make a generic type that made this easy to work with (or even adding a min parameter to existing list types) but I wouldn’t want to pass around funny tuples.We use the NonEmpty type in Haskell all the time, it has mostly the same interface as list. IMO the basic list of structures you need for 99% of cases representing relationships between data are:
Beyond that is quite rare, and at that point you should really be using custom types that only allow the valid states.
Good article and it is a problem I have run into more than a few times over the decades.
It’s interesting that the author chalks incorrect choice of multiplicity up to miscommunication between domain experts and programmers.
That happens, but it is the exception in my experience. Much more common is that the domain experts get it wrong initially. The spec clearly says there will be 1 address. The programmer, having gone down this road before, double-checks and asks specifically if the software will need to handle multiple addresses. The domain experts say no, it should be exactly 1, and their answer is unambiguous.
Then six months later, the domain experts and programmers discover together that the answer was wrong.
Hmm, kinda depends on who you’re considering the domain experts. In my experience the root cause of these situations is that the “domain expert” doesn’t actually know shit about the domain, and is just a manager of some sort that either manages or represents the people that will use the software, which are the actual domain experts, but have no political power in the organization, so no one listens to them.
This is tangential, but:
Why haven’t we yet made programming approachable enough that the domain experts can be the programmers rather than having to delegate to programmers? The immediate cynical answer that comes to mind is that we programmers like our job security. But I wonder if there are other, better reasons.
I think the more likely answer is that making programming approachable is a lot harder than we think it is.
What do you think about this essay which argues that things like Visual Basic and HyperCard were on the right track, but then the late 90s web boom (and, according to a later essay, open source), halted progress in that area?
I’m not hwayne, but I agree with him—it’s a lot harder than we think it is. Basically, programming requires tracking detail, enough detail that would daunt most people. Witness the number of articles about fallacies that programmers (people trained to track such details) make about human names, addresses, phone numbers or dates, just to name a few areas.
Here’s my question to you—how do you define “computer literacy”?
Poppycock. There are few imaginary products I can think of that would be more valuable to their creator than the “AI that replaces programmers”, it’s just not something we have any idea how to do.
Small parts of programming do get automated over the years, with things like garbage collection and managed runtimes, but so far this has always lead to an increase in the kinds of tasks we expect computers to handle, rather than doing the same basic tasks with fewer programmers. This makes sense because it gives the business an advantage over competitors in whatever their core business happens to be. They’d (the companies that survive) rather do more and charge more / get more customers, than do the same for slightly less.
That essay seems to confused open source with not charging money for things…
First of all, I’ll say that I agree with hwayne and think that’s the primary reason we don’t have many non-programmer friendly coding/automation tools.
The first essay you linked alludes to this, but I think the point should be emphasized, there’s an incentive mismatch between programmers and end-users. Programmers often like to program because they enjoy the act of programming. Look at how many links we get on this forum about programmers waxing and waning about the joys of TUIs, architectural simplicity, or networks run for and by skilled operators. These are all things that are immaterial to, or even detrimental toward, the user experience of a non-programming SME. Even with today’s world of skilled programmers running large cloud systems, programmers still complain about how much they need to accommodate the whims of non-technical users.
This isn’t unique to programming. Trades folks in a lot of trades often talk shop about better access platforms/crawl spaces, higher quality parts, more convenient diagnostic tools, and other stuff that non-tradespeople would find spurious expenses/concerns that sometimes may even make the tradesperson’s work less aesthetic (say in a residence.) I think there are many complicated factors that make this incentive mismatch worse in programming than in trades. As long as this incentive mismatch exists, I think you’ll only see limited progress toward non-technical programming accessibility.
Having been in the position of “software engineer for SME’s” a few times… Making really good software that you would actually want to use in production is a craft, a skill of its own, and one that takes a lot of time and work to learn. Most software people are interested in software for its own sake, because the craft is fun. Most SME’s are not, and so they will learn as much as is necessary to bang together a solution to their problem and it doesn’t really matter how nasty it is. They want to be working on their subject matter, not understanding cache lines or higher order functions.
We can rephrase the question: “Why haven’t we yet made woodworking approachable enough that the people who use furniture can be the carpenters rather than having to delegate to carpenters?” Sure, if you are actually interested in the process of building furniture then you can make lots of amazing stuff as a non-professional, and there’s more sources out there than ever before for an interested novice getting started. But for most people, even assembling IKEA furniture is more work and suffering than they really want to expend.
I think the whole idea is to make the “band together something that solves the problem” option more possible and more common.
So many people spend so much of their lives using computers to manually do trivially automated things, but the things are all too bespoke for a VC funded startup to tackle making a “product”.
This works pretty well as long as the tools those people build are only used by that person. Which is pretty important! The problem appears when someone’s bespoke little tool ends up with its tendrils throughout an organization, and now suddenly even if it isn’t a “product” it is essential infrastructure.
I think that’s actually a good thing / goal, and work on “making programming accesible” should work on reducing the ways in which that is a problem.
Note that “a dev with a stick up their ass seeing it will say mean things” is not by itself a valid problem for anyone but that dev ;)
I would say it’s for the same reason why programmers can’t be the domain experts; expertise in any field takes time, effort and interest to develop.
For example, a tax application where all the business rules were decided by developers and a tax application developed by accountants would probably both be pretty terrible in their own ways.
A lot of the other responses I almost entirely agree with, but to add my own experience:
I’ve been a part of some implementations of these types of tools, and also read a lot about this subject. Most people building these tools aren’t building “programming that’s easy for non-developer” but “I find ____ easy so I’m going to remove features so that it’s more approachable”. It also leads a lot to either visual programming languages, which don’t directly solve the complexity issues, or config languages, which lack the necessary surface area to be usable for many tasks.
A prior team of mine tried to go down the config route, building out 2 different config languages that “can be used by managers and PMs to configure our app so that we can focus on features.” Needless to say, that never happened. No one did any research on prior attempts to do build these types of languages. No one tested with PMs and managers. It ended up being built by-devs-for-devs.
There’s also this idea that floats around software that somehow simpler languages aren’t “real” languages, so they often get a lot of hate. For many years I’ve heard that Go isn’t for real devs, that it’s only for stupid Google devs who can’t be bothered to learn a real language like Java. JS is still considered by many to be a joke language because it’s for the web and “real” developers program servers, desktops, and mobile. Way back in the day, Assembly was for the weak, “real” devs wrote out their machine code by hand/punch card. Unless we can overcome that instance of what a “real” programming language is, we’ll likely continue to struggle to find and build accessible languages.
One of the few people I know writing about approach-ability of programming, and attempting to actually build it is Evan C. I won’t claim that Elm is perfect, I do think we can do better, but Evan has worked very hard to make it approachable. So much so that both its error message approach and its Elm Architecture have permeated many other languages and frameworks without people realizing it.
The Syntax Cliff
Compilers as Assistants
Compiler Errors for Humans
The answer to me comes down to time. I can gather requirements, speak to stakeholders, and organize projects, or I can write code, test code, and deploy code. I do not have the time (or attention span, really) for both.
People have been trying this for a very long time. It results in very bad programs. The idea that programming can be magic’d away and we can fire all the programmers is held only by marketing departments and (for some reason) a few programmers.
My mind started to go there as soon as they introduced the problem of modeling a non-empty collection. And I seem to recall a language or framework that supports this — IIRC you could parameterize a collection by min and/or max size and the type system was pretty smart about tracking this. So for example
Vec<Min,Max>.pop()
returnsVec<Min-1,Max-1>
and of course won’t compile ifMin==0
.Doing this in general (rather than special casing some collections or types in the compiler) is dependent types, which he alluded to at the end. This example is actually the canonical example of dependent types. As a name, “dependent types” doesn’t really convey what it is, though: it makes values into types. The “dependent” bit is from the idea that the type of a thing depends on its value. Here, for example,
Vec<String, 5>
(not real notation!) would be an item which has both the value that it has five items in it and a type that it has five items in it. That’s incredibly powerful, but it also comes with the cost of that much more type machinery to learn and maintain, and is (therefore?) very far from mainstream.