Search code examples
elmelm-signal

Cannot read property 'kids' of undefined - or how to break a circular dependency of signals in Elm?


While elm-make succeeds, I get the following error in the browser:

Cannot read property 'kids' of undefined

I assume it's because I have a circular dependency of signals:

model -> clicks -> model

Here is the relevant code:

model : Signal Model
model =
  Signal.foldp update initialModel clicks


clicks : Signal Action
clicks =
  let
    clickPosition = Mouse.position
      |> Signal.sampleOn Mouse.clicks
    tuplesSignal = Signal.map3 (,,) clickPosition Window.dimensions model      
  in
    ...

It feels like model is implemented as a common practice in Elm, so I should challenge the clicks -> model dependency.

Here is some context:

I'm building a sliding puzzle game using canvas:

enter image description here

When user clicks a tile that can move, it should move. Otherwise, the click should be ignored.

clicks will produce the following actions: Left, Right, Up, Down

For example, if user clicks on tiles 12, 11, 8, 15 (in this order), clicks should be: Down -> Right -> Up

The problem is, that in order to calculate which tile was clicked, I need to know the board dimensions (e.g. 4 rows and 4 columns in the picture above). But, board dimensions are stored in the model (imagine user interface that allows users to change board dimensions).

How do I get out of this circular dependency?


Solution

  • In this case I think you should go more low-level in what you call an input, and accept click positions as inputs. Then you can compose an update function in your Signal.foldp out of two others:

    1. The first turns the clicks and model into a Maybe Direction (assuming the Left, Right, Up, Down are constructors of type Direction)
    2. The second function that takes a Direction value and the model to calculate the new model. You can use Maybe.map and Maybe.withDefault to handle the Maybe part of the result of the first function.

    Separating these two parts, even though you produce and consume Direction immediately, makes your system more self-documenting and shows the conceptual split between the raw input and the restricted "actual" inputs.

    P.S. There are conceivable extensions to the Elm language that would allow you to more easily write this input preprocessing in signal-land. But such extensions would make the signal part of the language so much more powerful that it's unclear if it would make "the right way" to structure programs harder to discover.