Proposal: Defining custom numeric types

Edit: see this reply


Elm has built-in typeclasses like number and comparable (maybe they aren’t actually called typeclasses). For example, number means that the value can either be Int or Float.

I don’t think there is currently any need to be able to define custom typeclasses, but the ability to extend the built-in ones would be very useful.

Typeclasses in Elm basically define what operators are supported by a type. For example, the number typeclass includes all types that support +, -, and other arithmetic operators. Extending these typeclasses allows us to make custom types work with the built-in operators.

What this means is that typeclasses could be extended by simply defining the functions that implement these operators. For example, here is a type that represents a fraction:

module Fraction exposing (..)
type alias Fraction =
    { numerator : Int
    , denominator : Int
    }

Currently, there is no way to make it possible to do fraction1 + fraction2; we have to do Fraction.add fraction1 fraction2 (Fraction is a module). This is very verbose. Another problem is that we can’t pass a Fraction as an argument to functions like List.sum.

Like I said earlier, Elm typeclasses define what operators a type supports. I think it should be possible for a custom type to extend a typeclass simply by defining a function for all of the necessary operators. It would be done with something like this:

-- Fraction refers to a type
Fraction.compare : Fraction -> Fraction -> Order
Fraction.compare a b =
    -- ...

Defining the function shown above would automatically make Fraction a comparable type. So now we can do things like fraction1 < fraction2. More importantly, we can do min fraction1 fraction2 without having to redefine min.

There should also be a syntax for explicitly saying that a type is part of a typeclass. It needs to work well with types that use type variables. Maybe something like this:

type alias Fraction [ comparable, number ] =
    -- ...

What do you think of this idea?

2 Likes

The general idea here is Abstract Data type and there have been talks about bringing something like this into Elm since forever. There is a meta-issue that captures more information (it links to other issues where discussions about various options happened). As you can see in that issue, the first discussion about this was in 2012.

2 Likes

What I am actually referring to here is constrained type variables; that’s what they are referred to here. It might also be useful to add a new constrained type variable called list that includes things like Array, List, and String. For this, it might only be necessary to define the implementation of the cons :: operator to make a new list type work with things like List.foldl and List.filter.

1 Like

For extending number to work, we might have to merge / and // to a single / operator that works with any number as long as they have the same type.

I made a full example here

This would mostly benefit libraries that define custom numeric types. A good example is the Quantity type in elm-units. It defines many convenience functions like max and abs. If it were possible to extend number, and the elm-units package utilized this feature, then it would not have to redefine these convenience functions because the ones in the core libraries would work. It’s also worth mentioning that number is a superset of comparable

If you combine / and // then what’s the result of 5 / 2? Is it 2.5 or is it 2?

2 Likes

It could be either. It’s a matter of API design mostly.

I would suggest that returning an Int makes sense in this case.

(/) : Comparable t => t -> t -> t

If type annotations are used to define what 5 and 2 are then this isn’t a problem. In this case, there needs to be a way to be explicit about the types of 5 and 2. Maybe if their types aren’t defined with a type annotation then it should figure it out based on whether or not there is a decimal point:

5 / 2 == 2
5.0 / 2.0 == 2.5

@lpil what does the Comparable t => mean

It’s a constraint, it says type t is a member of the Comparable type class.

Honestly, as @pdamoc pointed out, this discussion has been rehashed for nearly a decade now. It’s safe to say if there was enough interest in bringing typeclasses to elm, it would’ve happened by now.

The trend of elm’s development of the last few versions has been to take things away and reduce complexity; typeclasses add that complexity back and “my code is more verbose without them” isn’t a particularly persuasive argument.

Good point. Simplicity is what makes Elm very special and fun to use. That’s why I did not propose the ability to define custom typeclasses. In fact, now that you have mentioned reducing Elm’s complexity, I think it should only be possible to extend the number typeclass. I don’t see any practical use case for extending things like appendable, and I no longer think there should be a list typeclass because of how different variants of list have slightly different behavior.

Now I am only proposing the ability to define custom numeric types.

Now that I am thinking of this proposal differently, maybe the syntax for making a custom numeric type should be different.

Declaring that a type is a numeric type would look like this:

numeric type Fraction =
    { ... }

Numeric types have to implement five functions: addition, subtraction, multiplication, division, and compare. Maybe the syntax for defining these functions could look something like this:

mumeric add : Fraction -> Fraction -> Fraction
fraction1 + fraction2 =
    -- ...

numeric compare : Fraction -> Fraction -> Order
compare fraction2 fraction2 =
    -- ...

Also I no longer think / and // should be merged. // should be for Int, and / should be for all other number types, including custom ones.

For types like the Quantity type in elm-units, there should be a way to only have to implement a map function to make it a number. This is how Quantity is defined:

type Quantity number units
    = Quantity number

For this, defining numeric map would be the only function needed to make it a number:

numeric map : ( number -> number ) -> Quantity number u -> Quantity number u
map func ( Quantity value ) =
    Quantity <| func value

Actually maybe whole thing isn’t a good idea. Quantity in elm units is too different from normal numbers and having a map function could cause type safety to be lost.. If there is many practical use cases for using fractions, it should be part of the core library.

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.