Search code examples
user-interfacedictionarytypeselm

Mysterious Type Mismatch in Elm


The following code is supposed to produce a button, which when pressed creates a box (and a button to remove the box on it)

import Graphics.Input
import Signal (..)
import Signal
import Mouse
import Text (Text, asText, plainText)
import Graphics.Element (..)
import Graphics.Collage (..)
import Color (..)
import List
import Dict

--                  x    y   id   |         id
type Event = Add (Float, Float, Int) | Remove (Int) | Maybe

makebox : Int -> Element
makebox id =
  let (w, h) = (30, 30)
  in flow down
    [ layers [plainText "aaaa", collage w h [square 30 |> filled red]]
    , button_remove id ]

add = Signal.channel (0,0,0)
remove = Signal.channel (0)

button_add x y = Graphics.Input.button (Signal.send add (x, y, 2)) "add a box"

button_remove id = Graphics.Input.button (Signal.send remove (id)) "remove me"

main =
  let update event =
    case event of
      Add (x, y, id)  ->  Dict.insert id ((x,y), ((makebox id)))
      Remove (id)     ->  Dict.remove id
      Maybe          ->  identity
  in Signal.map (\dict ->  flow down
                            [ button_add 10.0 20.0 --makes add & remove buttons
                            , collage 500 500 (List.map (\(Just ((x,y), makebox)) -> move (x,y) makebox)
                                                (Dict.values dict)) --draws the dict
                            ]) --map function argument
  (foldp update Dict.empty
    (merge
      (Add    <~ (Signal.subscribe add)) --pipes button channels into events
      (Remove <~ (Signal.subscribe remove)))) --map signal argument

However, it produces this type error:

Type mismatch between the following types on line 40, column 14 to 20:

   ((Int, Int))

   Maybe.Maybe

It is related to the following expression:

   update

I don't see where this error is coming from, where is the Maybe.Maybe being passed into update, and how do I fix it?


Solution

  • TL;DR

    Find & replace the line with the collage call in main to this:

                                , collage 500 500 (List.map (\((x,y), makebox) -> move (x,y) (toForm makebox))
    

    Quick code cleanup

    That's a lot of code. I had to change the style a little to understand, how it works. What I did was move a lot more functions to the top-level and give them type annotations. I also changed the button_add/remove to camelCase, because that's the standard Elm naming convention. This is the result:

    import Graphics.Input
    import Signal (..)
    import Signal
    import Mouse
    import Text (Text, asText, plainText)
    import Graphics.Element (..)
    import Graphics.Collage (..)
    import Color (..)
    import List
    import Dict
    
    --                  x    y   id   |         id
    type Event = Add (Float, Float, Int) | Remove (Int) | Maybe
    
    type alias Model = Dict.Dict Int ((Float,Float), Element)
    
    makebox : Int -> Element
    makebox id =
      let (w, h) = (30, 30)
      in flow down
        [ layers [plainText "aaaa", collage w h [square 30 |> filled red]]
        , buttonRemove id ]
    
    add : Signal.Channel (Float,Float,Int)
    add = Signal.channel (0,0,0)
    
    remove : Signal.Channel Int
    remove = Signal.channel 0
    
    buttonAdd : Float -> Float -> Element
    buttonAdd x y = Graphics.Input.button (Signal.send add (x, y, 2)) "add a box"
    
    buttonRemove : Int -> Element
    buttonRemove id = Graphics.Input.button (Signal.send remove id) "remove me"
    
    update : Event -> Model -> Model
    update event =
      case event of
        Add (x, y, id)  ->  Dict.insert id ((x,y), makebox id)
        Remove (id)     ->  Dict.remove id
        Maybe           ->  identity
    
    -- move' : Just ((Float,Float),Element) -> Form
    move' (Just ((x,y), makebox)) = move (x,y) makebox
    
    view : Model -> Element
    view dict = 
      flow down
        [ buttonAdd 10.0 20.0 --makes add & remove buttons
        , collage 500 500 (List.map move' (Dict.values dict)) --draws the dict
        ]
    
    input : Signal Event
    input = merge
      (Add    <~ (Signal.subscribe add)) --pipes button channels into events
      (Remove <~ (Signal.subscribe remove))
    
    model : Signal Model
    model = foldp update Dict.empty input
    
    main : Signal Element
    main = view <~ model
    

    We still get the same, not very helpful type error. But now it's on the move' function. I added the type signature I thought of in comments.

    The problem(s)

    There are two problems in this piece of code:

    1. move' takes an Element (among other things) and tries to move it, but move works on Forms. So that requires a call to toForm, before it works.
    2. This is where the type error comes from: Dict.values gives a list of values, not a list of Just values (of type Maybe.Maybe).

    The solution is therefore a move' function like so:

    move' : ((Float,Float), Element) -> Form
    move' (a,b) = move a (toForm b)