Rick Bradford
Rick Bradford

Reputation: 23

Elm signals and type annotation

From an Elm newbie but long-time Haskeller, a quick query.

The aim: There is a map with towns on it at designated positions on it, and I want to check if the user click is close to a town, and identify that town.

so, I collect the signal as usual:

clickPositionsSignal = sampleOn Mouse.clicks Mouse.position

that gives me a Tuple, which I want to turn into an Int (denoting the number of the nearest town). Towns are designated as

positions : [Position] 
type Position = {number : Int, x : Int, y : Int}

the function to do this is:

whichTown : (Int,Int) -> Int
whichTown (x,y) = 
            let pz = map (\p -> getDistance p.x p.y x y) positions |> head
            in pz.number

Now, I need to apply this function to my clickPositionsSignal.

Looking around various examples, I modified some code to....

whichLocationSignal : Signal Int
whichLocationSignal =           
      let wbl (x,y) = whichTown(x,y)
      in wbl <~ clickPositionsSignal

.... and this works. I get the number of the nearest town.

But this is hopelessly cumbersome and duplicative. The question is why can I not simply write:

whichLocationSignal = whichTown clickPositionsSignal

That line throws up multiple Type Errors which I am not yet experienced enough to interpret

Upvotes: 1

Views: 204

Answers (1)

Apanatshka
Apanatshka

Reputation: 5958

TL;DR

It should be:

whichLocationSignal = whichTown <~ clickPositionsSignal

or

whichLocationSignal = lift whichTown clickPositionsSignal

(which you already figured out yourself)

How to read the type errors

So a full version of your code that gives these type-errors would be:

import Mouse

type Position = {number : Int, x : Int, y : Int}

clickPositionsSignal : Signal (Int,Int)
clickPositionsSignal = sampleOn Mouse.clicks Mouse.position

positions : [Position]
positions = []

getDistance x1 y1 x2 y2 = { number = 0 }

whichTown : (Int,Int) -> Int
whichTown (x,y) = 
  let pz = map (\p -> getDistance p.x p.y x y) positions |> head
  in pz.number

whichLocationSignal : Signal Int
whichLocationSignal = whichTown clickPositionsSignal

The type errors I then get are:

Type error on line 19, column 33 to 53:
     clickPositionsSignal

  Expected Type: (Int, Int)
    Actual Type: Int

Type error on line 19, column 33 to 53:
     clickPositionsSignal

  Expected Type: Signal.Signal
    Actual Type: (Int)

Type error on line 19, column 23 to 32:
     whichTown

  Expected Type: Int
    Actual Type: Signal.Signal Int

I admit, these type errors are confusing.
(You might even say they are incorrect. All I can say to that is this is the current quality of type errors that you get, sorry!)
One basic tip when a type error from Elm doesn't make sense is to see if it makes more sense when you flip the expected/actual. Then the first type error makes no sense. But the second gives some information: clickPositionsSignal is somehow expected to have a type Int, not some Signal. Together with the third error message this starts to make sense: whichTown does something the other way around where an Int should be given but you get a Signal...
At that point you can find the uses of these two together, and once you note that whichTown works on (Int,Int) and clickPositionsSignal : Signal (Int,Int), you've found your error and the compiler messages make some kind of crooked sense.

The fix as stated above is to use lift : (a -> b) -> Signal a -> Signal b to "lift" the function (whichTown) to the Signal "level". Most people prefer to use the infix operator <~.

Upvotes: 1

Related Questions