Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Elm, how to use comparable type in a tagged unions types?

Tags:

elm

I can define a tagged unions type like that:

type Msg
  = Sort (Product -> Float)

But I cannot define it like:

type Msg
  = Sort (Product -> comparable)

The error says:

Type Msg must declare its use of type variable comparable...

But comparable is a pre-defined type variable, right?

How do I fix this?

like image 992
Nan Li Avatar asked Jan 30 '26 02:01

Nan Li


1 Answers

This question feels a little like an XY Problem. I'd like to offer a different way of thinking about passing sorting functions around in your message (with the caveat that I'm not familiar with your codebase, only the examples you've given in your question).

Adding a type parameter to Msg does seem a bit messy so let's take a step back. Sorting involves comparing two of the same types in a certain way and returning whether the first value is less than, equal to, or greater than the second. Elm already has an Order type using for comparing things which has the type constructors LT, EQ, and GT (for Less Than, EQual, and Greater Than).

Let's refactor your Msg into the following:

type Msg
    = Sort (Product -> Product -> Order)

Now we don't have to add a type parameter to Msg. But how, then, do we specify which field of Product to sort by? We can use currying for that. Here's how:

Let's define another function called comparing which takes a function as its first argument and two other arguments of the same type, and return an Order value:

comparing : (a -> comparable) -> a -> a -> Order
comparing f x y =
    compare (f x) (f y)

Notice the first argument is a function that looks similar to what your example was trying to attempt in the (Product -> comparable) argument of the Sort constructor. That's no coincidence. Now, by using currying, we can partially apply the comparing function with a record field getter, like .name or .price. To amend your example, the onClick handler could look like this:

onClick (Sort (comparing .name))

If you go this route, there will be more refactoring. Now that you have this comparison function, how do you use it in your update function? Let's assume your Model has a field called products which is of type List Product. In that case, we can just use the List.sortWith function to sort our list. Your update case for the Sort Msg would look something like this:

case msg of
    Sort comparer ->
        { model | products = List.sortWith comparer model.products } ! []

A few closing thoughts and other notes:

This business about a comparing function comes straight from Haskell where it fulfills the same need.

Rather than defining the Sort constructor as above, I would probably abstract it out a little more since it is such a common idiom. You could define an alias for a generalized function like this, then redefine Msg as shown here:

type alias Comparer a =
    a -> a -> Order

type Msg
    = Sort (Comparer Product)

And to take it one step further just to illustrate how this all connects, the following two type annotations for comparing are identical:

-- this is the original example from up above
comparing : (a -> comparable) -> a -> a -> Order

-- this example substitutues the `Comparer a` alias, which may help further 
-- your understanding of how it all ties together
comparing : (a -> comparable) -> Comparer a
like image 189
Chad Gilbert Avatar answered Jan 31 '26 21:01

Chad Gilbert



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!