Trait/impl syntax

if elm has traits/typeclasses it should look like this

Comparable.elm

trait Comparable a exposing
    ( order
    , lt
    , gt
    )

-- no default implementation
order : a -> a -> Order

(<) : a -> a -> Bool
(<) a b =
    order a b == LT

(>) : a -> a -> Bool
(>) a b =
    order a b == GT

Thing.elm

module Thing exposing (..)
import Comparable exposing ((<), (>))

type Thing
    = Low
    | High

impl Comparable Thing
    order : Thing -> Thing -> Order
    order a b =
        case (a,b) of
            (Low, High) -> LT
            (High, Low) -> GT
            _ -> EQ
Low > High -- False
High < High -- False
Low < High -- True
Comparable.order Low Low -- EQ
1 Like

I would love rust-like traits for Elm!
I think they would strike the balance between simplicity and expressiveness nicely.
With traits and associated types you can even build abstractions for iteration (Foldable) without needing to add higher kinded types to the language.

Keep in mind that elm already has syntax for infix operators which looks a little different:

-- INFIX OPERATORS


infix right 0 (<|) = apL
infix left  0 (|>) = apR
infix right 2 (||) = or
infix right 3 (&&) = and
infix non   4 (==) = eq
infix non   4 (/=) = neq
infix non   4 (<)  = lt
infix non   4 (>)  = gt
infix non   4 (<=) = le
infix non   4 (>=) = ge
infix right 5 (++) = append
infix left  6 (+)  = add
infix left  6 (-)  = sub
infix left  7 (*)  = mul
infix left  7 (/)  = fdiv
infix left  7 (//) = idiv
infix right 8 (^)  = pow
infix left  9 (<<) = composeL
infix right 9 (>>) = composeR

(https://github.com/elm/core/blob/22eefd207e7a63daab215ae497f683ff2319c2ca/src/Basics.elm#L68-L89)

1 Like

some ideas for syntax for using traits in type annotations

import TraitA
import TraitB

-- `a` must have TraitA and TraitB

- TraitA a, TraitB a
doThing1 : a -> a

'a: TraitA + TraitB
doThing2 : a -> a

maybe there should also be supertraits (where traits that require other trait(s) to be implemented). currently, comparable is like a supertrait of number. here is an example function:

twiceMax : number -> number -> number
twiceMax a b =
    2 * (if a < b then b else a)

if traits exist but not supertraits, the type annotation could look like this:

'a: Number + Comparable
twiceMax : a -> a -> a

supertrait syntax:

'a: Comparable
trait Number a exposing

if supertraits exist, the type annotation would be:

'a: Number
twiceMax : a -> a -> a

but maybe that’s not explicit enough. maybe the supertraits should still be explicitly listed only if twiceMax directly uses the supertrait. but supertraits can still be used like this (Number.elm):

abs : a -> a
abs n =
    if n < 0 then -n else n

Now I’m seeing lifetimes XD

2 Likes

Rust has a derive syntax for automatically generating an implementation of a trait. I think Haskell has it too. rust example:

#[derive(Eq)]
struct Point {
    a: i32,
    b: i32,
}

i don’t think custom types should be derivable in elm (rust does it with macros), and there shouldn’t be an explicit syntax for it. instead, things like Eq should be implicitly derived when possible. i think it basically already does this with comparable tuples.

elm should have a Debug trait. things like Dict have a custom implementation of Debug.toString, and i think custom types should also have this ability.

Dicts also have a custom implementation of equality (they’re implemented as red-black trees which don’t have the property that they’re automatically structurally equal if they contain the same elements (the structure depends on the insertion order); so the compiler has a special case that compares Dicts only by their contents not by their structure.

(By the way, I find these kinds of decisions the most irritating thing about Elm. „Nobody should have the need for a custom implementation of ==, except we already found a case while building the standard library.“)

4 Likes

i don’t know of any 3rd party packages that would benefit from custom impl Eq, but there are so many that could benefit from custom impl Debug such as elm-ui, elm-deque, elm-bigint, etc.

I don’t think this is how it happened in this case. Nobody said ‘Nobody should have a need for…’.

Implementing language features like this takes time, consideration and care. It just hasn’t happened yet, because plainly copying haskell’s typeclasses wasn’t a satisfying solution.

3 Likes