In Elm Europe 2019, Richard Feldman made a fun talk exploring “What if the only language feature Elm had was custom types?”
Why would you want a language with only custom types? Who cares! It’s fun to explore.
So here’s my what if: “What if Elm had no type classes?”
Note: This is not a proposal. I’m not saying this is a good idea. It’s just fun talking about programming languages!
If you feel like this sounds like an interesting thought experiment – read on!
If you feel like commenting – please do! But please avoid “Why would you do/want/need this?” type of comments – that’s not the point here.
The four type classes of Elm
As far as I know, Elm has four type classes:
- number
- comparable
- appendable
- compappend
Let’s explore what things would look like if those didn’t exist!
appendable
I think the only affected function would be ++. It currently works with both strings and lists, but would have to be restricted to just one of those.
Idea: Remove ++ altogether! And replace it with:
- String interpolation:
"Total: ${String.fromInt total} USD. Your share: ${String.fromInt yourShare} USD." - A variant of
List.appendthat lets you do[1, 2, 3] |> List.append [4, 5, 6]to get[1, 2, 3, 4, 5, 6]. Not sure what a good name would be! Or maybe even allow[1, 2, ...someList]like in JS.
comparable
Here’s what I use comparisons for:
- To figure out which number is bigger.
- To sort things.
There’s one more use case: Set and Dict needs to be able to compare its keys.
Idea:
- Let
<,>,<=,>=,compare,min,max,List.maximum,List.minimumonly work with numbers. (Notice how I’ve already mentioned “number” twice? We’ll get back to that in the section about thenumbertypeclass!) - Introduce something like localeCompare for comparing strings.
-
List.sortBy (\person -> (person.lastName, person.firstName))wouldn’t work anymore. This can be replaced with new sorting API:s! See below. - For data structures such as
SetandDict– I’ll get back to that below.
Sorting
Here are some functions I’ve been toying with:
thenBy : (a -> a -> Order) -> a -> a -> Order -> Order
thenBy f a1 a2 order =
case order of
LT ->
LT
EQ ->
f a1 a2
GT ->
GT
x =
List.sortWith
(\p1 p2 ->
String.localeCompare p1.lastName p2.lastName
|> thenBy String.localeCompare p1.firstName p2.firstName
)
compareMap : (a -> b) -> (b -> b -> Order) -> (a -> a -> Order)
compareMap mapper comparer a1 a2 =
comparer (mapper a1) (mapper a2)
y =
List.sortWith
(\p1 p2 ->
compareMap .lastName String.localeCompare p1 p2
|> thenBy (compareMap .firstName String.localeCompare) p1 p2
)
thenBy2 : (a -> a -> Order) -> (a -> a -> Order) -> (a -> a -> Order)
thenBy2 f1 f2 a1 a2 =
f1 a1 a2
|> thenBy f2 a1 a2
z =
List.sortWith
(compareMap .lastName String.localeCompare
|> thenBy2 (compareMap .firstName String.localeCompare)
)
There’s also elm-sorter-experiment by Richard Feldman.
Set and Dict
Let’s say you have a custom type:
type Status
= New
| Open
| Closed
And now you’d like to make a filters : Set Status. Set needs to be able to order its elements (implementation detail).
Idea: The compiler could just decide that Status values should be ordered in the order they where defined: New, then Open, then Closed.
A downside of that is that if somebody sorted a list of Status, and someone else were in a refactoring mood and felt like ordering all custom types alphabetically:
type Status
= Closed
| New
| Open
Then they would accidentally have changed how that list was sorted. Oops!
So my idea is to have a special function with an annoying name that is only intended to be used for data structures (where the order doesn’t matter, just the fact that things can be ordered, unless I’m mistaken). Something like Basics.orderForUseInDataStructures : a -> a -> Order (but with a better name). Maybe that function shouldn’t return Order to be incompatible with sorting functions (instead, it could return InteralOrder that is otherwise identical to Order).
The idea here is that the compiler could “just know” how to order any values, just like it “just knows” how values are equal (but there’s no equalable type class). And this ordering should only be used for data structures, not sorting a list before displaying it or so.
compappend
compappend is comparable and appendable at the same time. (“Only strings and lists are both comparable and appendable” according to the compiler.)
We’ve already removed comparable and appendable above, so there shouldn’t be a need for this combination anymore.
number
Finally, the tricky one.
Here’s a thread about it: The problems with numbers in Elm
And a GitHub issue which suggests having different operators for Int and Float: https://github.com/elm/compiler/issues/2078
The clever thing about the “different operators” proposal is that it adds a . at the end of operators, just like you add a . in a literal to make it a float. You’d have 5 + 3 and 5.0 +. 3.1. And 5 / 2 == 2 while 5.0 /. 2.0 == 2.5. And 5 > 2 vs 5.0 >. 2.0.
In this universe of “no typeclasses” (for no particular reason), that’s the best I can come up with.
If so, I think it would make sense to separate out most functions from Basics to new Int and Float modules.
(If you want to take everything one step further – what if there was no operators at all (except pipes)? Like, 1 |> Int.add 2 |> Int.equals 3 instead of 1 + 2 == 3. But let’s not go there in this thread.)
