Question

We have a function that sums a list of items by their .cost values. We want to use it to sum another list of items with .cost, but the items are different types.

Is there a way to write annotations to match “anything with this type of field”?

type alias Foo = { cost : Int }
type alias Bar = { cost : Int, isIron : Bool }

sumItems : List Foo -> Int
sumItems items =
    List.foldl (\x acc -> x.cost + acc) 0 items


fooSum = sumItems listOfFoos
barSum = sumItems listOfBars

Duck

Answer

We can define a type that just has some fields like this.

sumItems : List ({ a | cost : Int }) -> Int
sumItems items =
    List.foldl (\x acc -> x.cost + acc) 0 items

Foo and Bar satisfy this type, so we can now reuse sumItems.

Bonus

If you’re using the generic type a lot, you can still use a type alias to keep things more succinct.

This example shows how we can replace something in a list of “records with an id field” generically.

type alias WithId a = { a | id : String }


updateById : String -> (WithId a -> WithId a) -> List (WithId a) -> List (WithId a)
updateById id getNewItem list =
    List.map
        (\x ->
            if x.id == id then
                getNewItem x
            else
                x
        )
        list

See the docs for more detail on dealing with records.