1

I am using a port to pass an object from javascript to elm, and I want to update my model based on the value received from JS. Heres my code:

    type alias Model = { x: String, y: String }
    type Action = Received { x: String, y: String }
    update : Action -> Model -> Model
    update action model =
        case action of
            Received rec -> { model | x = rec.x, y = rec.y }

    port rec : Signal { x: String, y: String }
    result = Signal.map update (Received rec)

However, I get a type mismatch compiler error on the last line, saying that update is receiving an argument of type Signal { x: String, y: String } when it should be receiving { x: String, y: String }

uuba
  • 13
  • 3
  • 1
    Can you include the definition of `Location` from the last line of your snippet? I'm not sure what it is or what role it plays here. – lukewestby Dec 29 '15 at 06:30
  • Sorry, that was a typo. It should be `Received` – uuba Dec 29 '15 at 14:40
  • How are you intending to use `result`? It isn't quite clear from your snippet, and mapping over `update` isn't quite making sense in the context you have it. Usually there's a `foldp` if you're trying to make something stateful. Can you provide more details on how you want to use this? – Chad Gilbert Dec 29 '15 at 15:17
  • @ChadGilbert My intent was to use `Signal.map` to apply my update function to the value of the signal. What do you mean by 'make something stateful'? As in `foldp` should be used when interacting with the model? – uuba Dec 29 '15 at 16:25

1 Answers1

1

So it looks like what you'd like to do is go from a Signal of incoming records from your port to a Signal of the current state of your model. There was mention of foldp in the comments, and it'll come into play so I'll be sure to address it. In order to get a Signal of the current model state from your port, this is what your code could look like:

initialModel : Model
initialModel =
  { x = "0"
  , y = "0"
  }

result : Signal Model
result =
  rec
    |> Signal.map Received
    |> Signal.foldp update initialModel

Let's go over this one step at a time now.

initialModel : Model
initialModel =
  { x = "0"
  , y = "0"
  }

With this assignment we set up an initial state from which to proceed. Elm's Signals must always have an initial value, regardless of whether that initial value is relevant to your program. If you don't care what your initial model is, you can assign the record's fields to be whatever you'd like.

result : Signal Model
result =
  rec
    |> Signal.map Received
    -- ...

Here we use Signal.map to go from the { x : String, y : String } records coming from your port to a Signal of Actions, namely, the Recieved action. So at this stage we have a Signal Action.

|> Signal.foldp update initialModel

In this final step we take that Signal Action from the previous step and fold it into the previous model with Signal.foldp. The first argument is a function that accepts some type a representing incoming new values from another Signal (in this case it's our Signal Action), and the last available value of our state (which is initialModel at the outset and then the last return value of update going forward) and returns the next state. This is how any statefulness is acquired in an Elm application.

result ends up as the Signal for the most recent incarnation of the application's model, which you can then map on to a Signal of Html or Graphics.Element or whatever you'd like using Signal.map again in order to produce some user interface that reacts to updates in your application.

lukewestby
  • 1,207
  • 8
  • 15
  • Thanks for the in-depth explanation! Now that I have a signal of the most recent model, how can I use it to update the actual model? In my view I have a div that prints `model.x` and `model.y`, and it never updates to show the values of x and y that get passed into the port. However, if I do `main = Signal.map show result`, it correctly shows the x and y values from the port. – uuba Dec 29 '15 at 19:01
  • The Signal `result` is the actual model :) That's the Signal that you use to drive your UI, like you've demonstrated by mapping `show` over it into `main`. You can take your view function and, as long as it accepts a `Model` and returns `Html` or `Graphics.Element`, you can plugin into main in the same way: `main = Signal.map view result`. – lukewestby Dec 29 '15 at 19:16
  • By the way, have you seen or tried StartApp? It helps with this type of wiring: https://github.com/evancz/start-app – lukewestby Dec 29 '15 at 19:17
  • Yes, I'm using `StartApp.Simple`. How might I integrate `result` with startapp? I have `main = start { model = initial, update = update, view = view }`. What's confusing to me is that since I pass `initial` into start, how would my app know to re-render the view with `result` as the new model? – uuba Dec 29 '15 at 19:45
  • Ah! I understand the source of confusion now. This is a very commonplace source of confusion and, I think, an issue of documentation. You'll need to use plain `StartApp` instead of `StartApp.Simple` to plumb external Signals into your app's data flow. See http://package.elm-lang.org/packages/evancz/start-app/2.0.2/StartApp#start for the docs. In particular, you'll need to use the `inputs` field to forward things from the `rec` signal into your app's update function. – lukewestby Dec 29 '15 at 20:16