Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

A note about "type signature" and "type variables"

convert : Exchange from to -> Currency from -> Currency to

from and to are type variables to ensure things line up.

They are not variables in the regular sense: we cannot use from or to in our function body to + or do anything with. Actually, in our function body, there is no variable from or to

convert : Exchange from to -> Currency from -> Currency to
convert arg1 arg2 =
    -- FYI you can use variable `arg1` or `arg2`
    -- BUT there are no variables `from` or `to` for use here

While reading https://thoughtbot.com/blog/modeling-currency-in-elm-using-phantom-types#constraint-2-conversions there's a problem understanding the type signature of

convert : Exchange from to -> Currency from -> Currency to
convert (Exchange rate) (Currency c) =
    Currency <| round (rate * toFloat c)

I [mentally] map Exchange from to in first line to Exchange rate in second line. Then I am lost how do I get the rate and where do from to go? Could someone give suggestion on how to understand this?

Rewriting the code a bit, I think this type signature won't be confusing:

convert : Exchange from to -> Currency from -> Currency to
convert exchangeFromTo currencyFrom =

So the first argument exchangeFromTo argument variable is type Exchange from to (and currencyFrom second argument is type Currency from)

Now to work with exchangeFromTo, we need to pattern match / destructure it to get the Float value we want out of it. To know what patterns are available to match with in a Custom Type, we can look at the type definition:

type Exchange from to
    = Exchange Float -- this is our only constructor, `Exchange blah` where `blah` will be a `Float` value

and we can use it like this

convert : Exchange from to -> Currency from -> Currency to
convert exchangeFromTo currencyFrom =
    case exchangeFromTo of
        Exchange rate ->
            -- do stuff with variable `rate` which is a `Float`

When there's only 1 pattern, we can also write it in this way (see here for more examples)

convert : Exchange from to -> Currency from -> Currency to
convert exchangeFromTo currencyFrom =
    let
        (Exchange rate) = exchangeFromTo
    in
    -- at this point, we can use the variable `rate` of `Float` type

since (Exchange rate) is equal to exchangeFromTo, we can replace it

convert : Exchange from to -> Currency from -> Currency to
convert (Exchange rate) currencyFrom =

So now we've arrived at where you were confused, the first argument being specified as (Exchange rate) and the rest of the function using variable rate value of Float type

@ingemar0720
Copy link

ingemar0720 commented Feb 17, 2020

What if there are more constructor in this example?, e.g. to re-write type Exchange to be

type Exchange from to
    = Exchange Float
    = Exchange String String

Shall the implementation also add in one argument?

convert : Exchange from to -> Currency from -> Currency to
convert (Exchange rate someInt) currencyFrom =

Or we shall use case of to do pattern match?

convert exchangeFromTo  currencyFrom =
    case exchangeFromTo of
        Exchange rate ->
            -- do stuff with variable `rate` which is a `Float`
        Exchange rate someInt ->
            -- do stuff with variable `rate` which is a `Float` and variable`someInt` which is a `Int`

@choonkeat
Copy link
Author

choonkeat commented Feb 17, 2020

in some languages (Haskell, Erlang, Elixir) you can define multiple function bodies to cover all the patterns

convert (Exchange rate) currencyFrom =
    -- do stuff with `rate`

convert (Exchange rate someInt) currencyFrom =
    -- do stuff with `rate`, `someInt`

Elm does not allow that, preferring you explicitly express all the variants in one place with the usual case ... of in 1 function body

convert exchangeFromTo currencyFrom =
    case exchangeFromTo of
        Exchange rate ->
            -- ...
        Exchange rate someInt ->
            -- do stuff with `rate`, `someInt`

The ability to define multiple function bodies for pattern matching is pretty cool and actually very very mathy. For example, here's factorial (one function matches the exact value 0 another function matches anything else as n)

factorial 0 = 1
factorial n = n * factorial (n - 1)

But I agree with Elm approach for maintainability (don't worry about locating the various functions, missing out on a pattern and making micro decisions on where to put that other function, tracing down the wrong function when another is more matching...)

factorial n = 
    case n of
        0 -> 1
        n -> n * factorial (n - 1)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment