3
votes

I am just starting out learning Elm and I'm having some trouble understanding why I'm getting a type mismatch when passing a custom type into a method that expects... well, what I'm calling a partial type annotation.

Here's the code I'm using:

import Graphics.Element exposing (show)
import Debug

type User =
  User { username : String, followers : List User }

type Action = Follow

fromJust : Maybe a -> a
fromJust x = case x of
    Just y -> y
    Nothing -> Debug.crash "error: fromJust Nothing"

update : User 
  -> Action 
  -> { user | followers : List User } 
  -> { user | followers : List User }
update actor action user =
  case action of  
    Follow -> { user | followers = user.followers ++ [actor] }

getUsers : List User
getUsers =
  [
    User { username = "UserA", followers = [] },
    User { username = "UserB", followers = [] }
  ]

main =
  let 
    users = getUsers 
    first = fromJust (List.head users)
    last = fromJust (List.head (List.reverse users))
  in
  show (update first Follow last)

And the error output from elm-lang.org/try:

Type Mismatch

The 3rd argument to function update is causing a mismatch.

43| show (update first Follow last) Function update is expecting the 3rd argument to be:

{ a | followers : List User }

But it is:

User

Hint: I always figure out the type of arguments from left to right. If an argument is acceptable when I check it, I assume it is "correct" in subsequent checks. So the problem may actually be in how previous arguments interact with the 3rd.


If I change the type annotation for update to expect a User instead, I get a different Type Mismatch, saying I should change the types back. :confused:

1

1 Answers

2
votes

It's because of the recursive type. Elm doesn't seem to have particularly intuitive (to a Haskeller anyway) handling of these, and you have to put some explicit unwraps and wraps in there to make it all work. I assume the compiler guided you to the definition of the User type that you have, as it did me when I started playing around with it.

The result of it all is though, values of type User in your example aren't records, they're data constructors which take records as a parameter. You have to pattern match against those values in order to get the record out, and remember to include the constructor whenever you return such a value. Fortunately Elm has good pattern matching support or this would be horrific.

So if you change your update function to

update : User 
  -> Action 
  -> User
  -> User
update actor action (User user) =
  case action of  
    Follow -> User { user | followers = user.followers ++ [actor] }

it all works. Note that the parameters are now all User types, and the user parameter is being deconstructed so we can get at the record inside. Finally, the result is reconstructed with the User data constructor.