Is it fair to say abstraction and expressiveness are Elm's weak points?

I’m a computer science student trying to approach Elm and functional programming in general. I have to prepare a presentation over Elm as a language as part of an exam.
I’ve spent the past days studying all of its features and limits. I’ve come to the point where I should draw some conclusions, but while understanding how it works is relatively easy I’m hesitant about my personal judgement. I can’t tell whether the drawbacks I feel in Elm come from actual problems or just from my inexperience in functional programming (I’ve coded nothing but a few tutorial examples in Elm).
Could someone more experienced than me tell me if the following statements are objectively true, subject of opinion or just nonsense?

  • Elm is a fast, simple, easily approachable purely functional language designed to build small client-side web application.
  • While statically typed and safe, it lacks in expressive power and abstraction (I’m thinking about the absence of typeclasses and subtyping, and the fact that the only polymorphism present is represented by extensible records)
  • This lack of expressiveness translates into the difficulty of growing large and generalized libraries and composing work, although those requirements are not so needed in client-side web development
1 Like

No, I don’t think so. Functional languages are by and large are all very expressive, as higher-order functions and composition results in terse, reusable code. There is no fiddling around with counters, memory management, or loops. No variable instantiation, constructors, or OOP boilerplate.

Moreover, Elm’s union types are incredibly expressive, allowing you to model most if not all of your business domain in types. For example, can you tell me what this function does just by looking at its type signature?

func : Shape -> Volume

You probably can, despite the fact that I have obfuscated the name of the function and its implementation. Is Elm’s ability to do this not expressive?

Lack of typeclasses will result in the repetition of some code, but code that must be repeated can be neatly tucked away into its own module, exposing only the requisite functions for manipulation of the types in question. At the same time, the benefit of not having typeclasses means that you can look at any piece of Elm code and understand what it is doing rather quickly; you do not have to spend time reading the types and understanding what behavior each operator or other interface introduces. This is a trade-off between languages like Elm and Haskell.

3 Likes

By expressiveness I didn’t refer to code clarity, but what the language can actually do (what semantic it can “express”). In this sense a language like C (or assembler for that matter) is massively more expressive than Elm, because you can simply do everything without restriction (of course, with all the negative consequences of such power).
I say this because I found many cases when the programmer wanted to do something, but Elm isn’t equipped for the task. For example, there are many threads in the mailing list asking how to express inheritance between (extensible) records (tl;dr: you can’t), if there will be support for language macros, and so on.
This guy wanted an exhaustive check on all possible values of a select box, but Elm has no way to do that.
Among all the functional languages I’ve looked into Elm is the one with the least number of constructs in it. Compared to the likes of Haskell and OCaml there are very few things you can do with types, and this is what I mean with reduced expressiveness.

Now, whether it would be GOOD for Elm to allow those things, it’s a completely different matter. One of my doubts was if I had understood correctly the tradeoff that Elm picks between power and simplicity, and what you say confirms I was right.
In fact, after facing Haskell and OCaml as first experiences of functional languages, Elm’s friendly syntax and architecture were a blessing.

1 Like

I am not referring to code clarity either. I am referring to how well the language can be used to express ideas, or the breadth of ideas that can easily be expressed in the language. The more expressive a language is, the fewer lines of code (or characters) required to express an idea or write some code that achieves a particular task (technically I could express any idea in machine code, it just wouldn’t be very readable and would be extremely long). C is one of the least expressive languages by this definition.

I think those conclusions are pretty good.

Objectively you can indeed say that elm’s type system is pretty basic, when compared to most
other functional languages. The big differences are typeclasses (aka ad-hoc polymorphism) and
higher-kinded types.

But then, as you mention, these type-level features aren’t as useful in the web domain.

When you say “lack of expressiveness translate into the difficulty of growing large applications” I think you
can be a bit more precise: we’ve not yet found a satisfying way of composing (pieces of) applications.
When you subscribe to the idea of “one source of truth”, the model, but at the same time want to keep your model flat (as in, not nested), it becomes really hard to compose applications.

There is some exiting progress in this area by the purescript folks, specifically Phil Freeman.
paper

Anyway, my conclusion is that elm is a language well-suited to its domain, and it makes some interesting but ultimately good tradeoffs.

3 Likes

Not only small. It’s easier to develop and maintain large applications with Elm than with JS even though it’s true that Elm isn’t as powerful as, for example, Haskell and has some limitations.

I think by focusing on some aspects Elm rejected for now, you don’t give it a fair hearing. It’s like writing about Eiffel and claiming that because it does not have “goto” it’s less expressive.

And definitely that last statement about difficulty of growing large libraries can only be answered by practitioners with deep insight, and requires much more study than your casual dive gives you warrant to state.

I think by focusing on some aspects Elm rejected for now, you don’t give it a fair hearing. It’s like writing about Eiffel and claiming that because it does not have “goto” it’s less expressive.

Keep in mind that those are just my conclusions. In my presentation I go through Elm’s constructs and features, so it’s not like I’m focusing only on its downsides.
Also, my point here is exactly that a language not having a goto command is less expressive; it’s a tradeoff between what you whant it to allow and what it makes sense to allow. So, is it not true that Elm is limited in that regard (compared to languages like Haskell or Purescript) to be more approachable, fast and simple?

And definitely that last statement about difficulty of growing large libraries can only be answered by practitioners with deep insight, and requires much more study than your casual dive gives you warrant to state.

That’s why I’m asking better advice here :wink:

If I were writing an essay comparing Elm to other programming languages, I’d say something like this:

11 Likes

Having done both back-end and front-end Web development professionally for 5 years apiece, I am extremely skeptical that things like typeclasses are more useful on the back-end than on the front-end. I’d be very curious to see a defense of that claim using specific examples.

Considering how many Elm programmers I know (myself included) who strongly disagree with both of these assertions, I think it would be more accurate to say “some people like Elm overall despite disliking some of its individual design choices.”

@Maldus512, C lets you dereference void pointers. Does that make it more expressive?

You seem to be saying that if a language lets you do X, even if X is extremely dangerous, foolish, or beyond mere mortals, it’s more expressive. If that’s the definition of expressive, I don’t think that is helpful.

They’re all Turing complete, so in my opinion all these languages are equally expressive. But some ways of expression yourself should not be used in polite society. And some ways help only in limited situations, and some ways hurt more than help.

1 Like

It is exactly what I am saying, referring to theoretical expressive power. I also stated clearly that wether expressive power is a good thing or not is heavily debatable.

some ways help only in limited situations, and some ways hurt more than help.

Yes. I like Elm’s simplicity (a lot), but there are things that Elm doesn’t allow that some people have been asking.
I, for one, don’t advocate nor reject any of that. Nevertheless, they exist.
The perfect example is functions that should be polymorphic that cannot be because of Elm limited abstraction power: search for “map”, and tell me that this is the best solution from a stylistical point of view.
The other side of that coin is Elm’s speed and simplicity, which I actually like and prefer; however, I am trying to carry on an objective analysis of Elm’s features and limits.

Elm is very “do or do not,” which unfortunately excludes academics in general and, more frustratingly, theoretical computer scientists. I don’t believe you’ll get a sound answer to your questions here.

Best of luck with your exam,
Will

It’s just my opinion, but I am not sure if objective analysis of the language is possible at all. Language usage is subjective to problem and to people.

Some problems are nicer solved in one language, some in another.

Some people would prefer features of one language, some people of another.

You can’t objectively say that one natural language is better then the other, right? Why programming language is any different?

2 Likes

You can probably select some problem as an example and make your analysis based on solution to this problem in different languages.

It’s true that Elm doesn’t have typeclasses, but I think what people are objecting to here is that your stated goal is to be objective, but your word choice consistently editorializes certain design choices as sacrifices - and many of us don’t see those choices as sacrifices.

Suppose you wrote “Isn’t it true that Python lacks direct memory management capabilities and has limited expressivity compared to C and Rust?” If I’m a Python fan reading that sentence, I wouldn’t say you’re lying, but I also wouldn’t use the word “fair” to describe that sentence as an objective assessment of Python’s design. It’s pretty clear from the way it’s written what the author’s preferences are.

You’ve implied several times in this thread that you see higher kinded polymorphism as an objectively net positive language feature which Elm has sacrificed for the sake of other benefits. In contrast, I see HKP as a net negative for Elm in the same way that I see direct memory management as a net negative - while understanding that both language features might make sense for other languages - so I don’t consider it a sacrifice for Elm not to have it.

Quite the contrary, as a longtime Elm user who understands how HKP works in other languages, I would be disappointed if it got added to Elm - for my own enjoyment of the language, setting aside the impact it would have on others. I almost laughed out loud when I read the word “should” in this sentence:

If you ask me, for Elm this is the best solution from a stylistical point of view.

I actively prefer List.map : (a -> b) -> List a -> List b and Array.map : (a -> b) -> Array a -> Array b and so on, to map : Functor f => (a -> b) -> f a -> f b or Mappable or any other name you might choose. When I started using Rust, I found Iterable polymorphism using traits less stylistically appealing to me than Elm’s “containers do not share code in that way” style.

I want to be extremely clear about this, because your comments in this thread suggest more than a little skepticism that preferences like mine actually exist. Like only beginners could possibly feel that way, and all experts who learn how these features work will obviously embrace them as objectively superior.

I appreciate that you’ve taken the time to ask Elm programmers what we think about the language’s pros and cons. If you take only one thing away from the responses you’ve gotten, I hope it would be that there exist expert functional programmers who actively prefer Elm’s set of language features around polymorphism to Haskell’s, PureScript’s, etc, and do not consider it a sacrifice for Elm to have made these choices.

If you want examples of sacrifices Elm has made for the sake of other goals, I would look immediately to FFI. By forcing JS interop to go through strictly controlled channels that take more work than FFI takes in (for example) PureScript or BuckleScript, Elm has sacrificed adoption speed for the sake of reliability and long-term ecosystem strength.

Another sacrifice Elm has made is conciseness for simplicity and explicitness. Two often-requested features are Objects (that is, React components) with their own local mutable state, and JSON decoders automatically derived from type aliases. Both are language features that would let people write more concise code in certain circumstances, at the cost of (among other things) making that code less explicit to read and at the cost of making the language more complex. These costs have seemed unacceptably high, so in both cases conciseness has been consciously sacrificed for the sake of other design goals.

I see all of these as sacrifices worth making, whereas I don’t see HKP or direct memory management as sacrifices at all; I just think the language would be be worse if it had them.

9 Likes

Ok, taking a step back here.

I understand the only problem here is my choice of words. Let’s start by saying that I personally agree, word by word, with everything you just said.

You’ve implied several times in this thread that you see higher kinded polymorphism as an objectively net positive language feature which Elm has sacrificed for the sake of other benefits.

Then I have expressed myself badly. Quite honestly, I barely understand what higher kind polymorphism is, let alone advocating for it.
Seriously, if there is one thing I am sure I learned from the few experiences I’ve had with Elm, is that its smaller set of language constructs is a precious feature. I genuinely like it, prefer it in comparison with more complex (complicated even) functional languages.

I got stubborn around the fact that I consider the lack of something (namely, HKP, typeclasses, etc…) to be automatically a tradeoff. In this particular case, I can wholeheartedly agree it’s worth. I can see my choice of words may have been a little harsh.
Also, I probably liked a little to much the idea of explaining a neat tradeoff in my presentation of Elm.

If you take only one thing away from the responses you’ve gotten, I hope it would be that there exist expert functional programmers who actively prefer Elm’s set of language features around polymorphism to Haskell’s, PureScript’s, etc, and do not consider it a sacrifice for Elm to have made these choices.

Got it. You are right, being a beginner in the functional world I took for granted that someone who actually understands and works with more complex constructs would prefer to have them in any situation. I’ll take note of it.

8 Likes

Thank you for the thoughtful response! I hope the presentation goes great! :grinning:

1 Like

Hi,

Elm and the ML language family in general has ‘parametric polymorphism’. This means that type expressions can contain parameters. These parameters are universally quantified, which basically means “for all”. So to take a simple example, the identity function:

identity : a -> a

The parameter is a, and the above reads as, the identity function has the type, for all a the function goes to a.

===

Extensible records are also parameterized, and therefore should be classed as ‘parametric polymorphism’. However, they are not polymorphic in the way you might expect, which is ‘subtype polymorphism’. Subtype polymorphism is where one type is a sub-type of another, which means that it can be substituted into any situation where the later could be used, but not necessarily have the same type. This is found most commonly in OO languages.

Extensible records are quite close to being sub-types, which can lead to confusion in Elm when you try and use them as such. The reason Elm does not have sub-typing is that it also has full type inference - you do not need to define the types of anything the compiler will infer it for you. When sub-typing is added to that the type inference becomes undecidable - the compiler cannot decide which type to infer.

Just about any syntactically well-formed construct can be pulled out and made into its own first class function in functional languages. Consider the map function.

Functional languages add a new level of abstraction when compared with imperative languages without first class functions. Here is an example, in some C-like language:

for (int i = 0; i < 10; i++)
{
    do_something(i);
}

for (int i = 0; i < 10; i++)
{
    do_something_else(i);
}

And in Elm:

do10 func =  List.map func <| List.range 0 9

do10 something
do10 somethingElse

The point here, is that in the functional language I was able to break apart the for-loop and the function applied inside of it, into ‘orthogonal’ components of the problem. I was then able to combine these pieces of code together.

In the imperative language, each time I had to write out the for-loop construct, there was no way to create a short-hand for it. Except in C I could have passed in function pointers to another function which encodes the loop - function pointers are kind-of what first class functions are, just implemented in a safer way with type-checking to avoid all the errors that are possible to make with function pointers.

I believe this is one of the most powerful ways in which FP derives its expressiveness and allows the construction of abstractions without lots of special syntax. Only the syntax for writing and applying functions is needed.

1 Like