@evancz and others helped me realize saying that “x is less expressive than y” makes little sense.
When I first used the word in my thread (“Is it fair to say abstraction and expressiveness are Elm’s weak points?”) it wasn’t much focused on its particular meaning in that context, and I got a little arrogant and stubborn when someone contradicted me.
At this point though I’m left wondering how exactly one could express the concept I had in mind.
I was thinking about 2 things:
The problem that this guy faces while trying to implement a select widget.
Basically, he wants to show a select box with n values and change the view when the selection is changed. He creates a union type with the n values to be chosen from, and he gets stuck.
The problem is the select widget only allows for the onSelect event, which embeds the selected String in a message and calls the update function. He is left juggling to and from between his value types and the corresponding strings to be displayed, losing the advantages of exhaustive pattern matching.
I also found someone else trying to implement a (Elm) code generation tool to make sure to have enum types and a corresponding list in sync.
The fact that you can’t have user defined types as dict keys, since the comparable property is embedded in the compiler.
If I needed to do either of those, I’d have to find ways to work around my problem (like a code generation tool, or a function mapping my types to other data). Regardless of whether I should do it, it’s something I can’t achieve neatly in Elm. “(lack of) Expressiveness” is not the right term, so how would you call this?
As a side note, sorry if I’m spamming; Evan himself said to create a new thread if there were any follow up questions though, so I felt allowed to continued inquiring.
It sounds like some of what you’re describing here is that the solution to some problems in Elm might be verbose or require some extra boilerplate. Part of that is that Elm forces you to handle all the corner cases such as empty lists, unexpected strings, or parse errors.
People often like to solve these problems with abstraction and metaprogramming. Elm purposely limits some kinds of the first and flat out doesn’t have the second.
Some problems are cumbersome to solve because you’re using a non-idiomatic approach (such as having to deal with a bunch of Maybes because you’re using an array index + iteration solution instead of functional list transformations)
Other problems don’t currently have a great solution (such as getting a list of all the values in an enum-like union type), requiring you to do more manual work to make your program work.
I actually think the term “expressiveness” is appropriate. The colloquial definition of the word is “effectively conveying thought or feeling” and it seems to me that is what you are trying to communicate.
The Wikipedia article on expressive power in computer science explains that there are two senses in which the term is used. One describes the theoretical capabilities of a language and is used in formal descriptions of languages, and the other describes a language’s practical effectiveness and is common in informal discussions. Just because the second definition is informal doesn’t mean it’s invalid, however. (In fact, the article even notes that there have been efforts to formalize this second definition of the term.)
In practical discussions of programming languages, we take it for granted that the systems we’re discussing are Turing complete. In this context, the question of whether it is possible to solve any arbitrary computing problem in the given environment is not relevant. We are instead concerned with how much effort is required to solve the subset of problems which we commonly encounter.
Turing completeness is kind of overrated. […] Sendmail configuration files are apparently Turing complete, PostScript is Turing complete, but you wouldn’t write Pac-Man in any of these languages. They’re not really geared up for writing actual practical software, stuff that interacts with the outside world.
So I think it’s totally valid to use “expressiveness” to describe the relative effort of solving problems in one language versus another. I agree with Evan, however, that we should try to be specific about the sorts of problems we have in mind and be open to solutions which are slightly different but accomplish the same thing.
“It’s cumbersome to do x in Elm” is a legitimate statement to make. Lots of things are cumbersome to do in Elm that are trivial in JS. But there are also things that are cumbersome to do in JS that are trivial in Elm.
Having ‘no exceptions in production’ or ‘side-effect free functions’ is very cumbersome to do in JS, you need to be very careful, have lots of tests and code review. You need to make sure junior devs have any tiny change carefully reviewed for accidental effects or mutations.
If you’ve worked on a reasonably sized JS application you’ll have experienced how difficult it is to keep mutation and side-effect related bugs out, so much so that most people just give up and accept it’s impossible.
What is cumbersome in a language reflects the trade offs that language made and the audience it targets.
The dictionary meaning of expressive that I think you are trying to convey is, ‘serving to express; indicative of power to express’. Thesaurus gives some suggested alternatives for this meaning: articulate; thoughtful; eloquent. I think expressiveness is a good word for what you are tyring to talk about, all it takes is to mention that you mean it in a different way to the formal meaning of it as understood within computer science.
In addition to my comments in the other thread, I find Elm to be powerful in how it scales, through its module system and package system with enforced semantic versioning. (The Standard ML Signature and Structures can be represented quite well in Elm in other ways). Tagged union types eloquently name choices in your programs. Evan has produced the most thoughtful language many of us have ever used.
An important skill to learn when structuring more complex pieces of software, is how to use higher order functions, to allow functionality from another module or author to be injected or composed. In functional languages, higher order functions = call backs in imperative languages. The classic example is map over a list, where you supply the function to be applied to each element, as a call back or function pointer. Below is an example set of types that I used to structure a content editor; different editors are implemented for read/preview/write modes of operation and these orthogonally combine with templates and layouts. A single codebase for client or server side rendering was built around this, using these higher order types to articulate its structure.
type alias LinkBuilder msg =
String -> Html.Attribute msg
type alias Template msg =
LinkBuilder msg -> Editor msg -> Zipper Content -> Html msg
type alias Layout msg =
Template msg -> Template msg
type alias Editor msg =
Zipper Content -> Html msg
view : Dict String (Layout msg) -> Dict String (Template msg) -> LinkBuilder msg -> Editor msg -> Model.Content -> Html msg
There are many ways you can express code ideas well in Elm, and on many different ‘scales’. I don’t see it as limits of the language itself but limits of the programmer - you can’t be as expressive as a beginner as you can when you get really into it and learn and learn, just as a novice could not sing the opera as expressively as the maestro. I think in the learning regard Standard ML and Haskell as taught at university level CS courses provides a solid foundation; perhaps Standard ML is better for Elm, as you don’t miss so many features from Haskell that way.
The Expression Problem, a term coined by the person who first published the idea of type classes in “Making ad-hoc polymorphism less ad-hoc”. Someone using Elm years ago wrote an excellent article exploring this in Elm and Haskell here. I very highly recommend that link in particular! Interestingly, type classes do not address this in a direct way, they are just a way to leave off arguments.
I might look to other languages where (<) cannot be used on arbitrary types. This includes many languages, so maybe there is a name for this fact in Python or Ruby. Elm currently does not have “user-definable type classes” or “user-extension of type classes”. The fact that comparable is a closed set of types right now could be addressed by user-extension, or more generic rules that just apply to any type or record. So if you want an FP framing, this would be “current lack of type class extension mechanism” or something like that.
As an aside, the deriving part of type type classes is a code generation mechanism. So you may want to say “Elm lacks a code generation mechanism, so sometimes you write more code than in Haskell, which ties its code generation to the type class feature via deriving.”
@rtfeldman this discussion got split into three threads now, so i’ve decided to post here my thoughts on the whole set together.
In some way, it’s basically the same that @joelq said above on verbosity\boilerplate, and since @Maldus512 is looking for the “right word” to describe all of the points he’s interested - i’ve provided another option
Most of the basic issues with Elm, the language, are usually solved with writing another bit of code. When code base grows, these bits are adding up and form the “noise” i’m talking about.