Possible to map an arbitrary depth JSON tree to a recursive type over a port?

237 views
Skip to first unread message

Micah Geisel

unread,
Feb 14, 2016, 2:41:25 PM2/14/16
to Elm Discuss
Hi folks!

I've been bashing my brain against an Infinite Type compiler error, but the more I look into it, the more it seems that it may not be possible to do what I'm trying to do (at least at present). Hopefully, I'm wrong! Here's a simplified reproduction of the issue:

type alias Node = { children : Children }


type
Children = Children (List Node)


deserializeJsonNode jsonNode
=
 
{ jsonNode
 
| children =
   
Children <| List.map deserializeJsonNode jsonNode.children
 
}


port initialModel
: {} -- ?


model
=
  deserializeJsonNode initialModel

The idea is that I have a JSON tree of arbitrary depth, and I want to initialize the Elm program with that tree as its initial state, something like this:

var tree =
 
{ children: [
   
{ children: [
     
{ children: [] },
     
{ children: [] },
   
]},
   
{ children: [] }
 
]};


Elm.embed(Elm.App, element, { initialModel: tree });

There seems to be three hurdles to overcome here, and I've only figured out the first one:

1. The naive definition of the Node type would have an infinite type. I've figured out how to avoid this by wrapping the children in a contrived Children constructor to break the recursion cycle. Not super pretty, but it works.
2. Ports only accept a whitelist of primitive types, so I'm pretty sure I can't define a concrete type for the `initialModel` port, otherwise it'd be an infinite type (same issue as issue 1, really, but without the solution). I'm pretty sure the port must have a type declaration of some kind, though, so it seems I'm caught between a rock and a hard place.
3. The compiler tells me that the recursive deserializeJsonNode function has an infinite type. I'm not sure where to even start! Normally, I'd start with declaring types for everything to try to elucidate the problem, but because of issue 2, I can't even do that.

A tree structure isn't exactly exotic, so I'm guessing I'm not the first person to run into this. Has anyone figured this out?

Max Goldstein

unread,
Feb 14, 2016, 3:49:33 PM2/14/16
to Elm Discuss
Oh, it's possible. Encoding is a snap, but decoding in the following snippet fails because of this issue. There are some workarounds on that thread that may help.

import Json.Encode as Encode
import Json.Decode as Decode exposing (Decoder, (:=))
import Html

type Tree = Node (List Tree)

exampleTree : Tree
exampleTree =
  Node [Node [], Node [], Node [ Node [] ]]
  
encodeTree : Tree -> Encode.Value
encodeTree (Node children) =
  let
    encodedChildren = List.map encodeTree children
  in
    Encode.object
      [("children", Encode.list encodedChildren)]
 
decodeTree : Decoder Tree
decodeTree =
  Decode.object1 Node ("children" := (Decode.list decodeTree))
 
encoded : String
encoded =
  Encode.encode 2 (encodeTree exampleTree)
  
decoded : Result String Tree
decoded =
  Decode.decodeString decodeTree encoded
    
main = Html.text (toString decoded)

Micah Geisel

unread,
Feb 14, 2016, 4:39:37 PM2/14/16
to Elm Discuss
Oh, interesting. You're suggesting passing in a JSON String and decoding that in Elm to Elm types. I didn't think about that! It gets around the infinite port type issue. It adds some complexity, and the port type checker can't really do much, but that's better than the impossible situation I find myself in trying to pass a JSON Object through the port. I'll try this out in a branch. Thanks!

Micah Geisel

unread,
Feb 14, 2016, 4:49:02 PM2/14/16
to Elm Discuss
Thinking about this some more, I came up with another potential solution. Instead of trying to pass the entire tree object through the port, walk the tree in JS and pass it through the port one node at a time. Something like:

var tree =
 
{ id: 1, children: [
   
{ id: 2, children: [
     
{ id: 3, children: [] },
     
{ id: 4, children: [] },
   
]},
   
{ id: 5, children: [] }
 
]};


var app = Elm.embed(Elm.App, element);

// psuedo code starts here
addChildToParent = function(node, parentNode) {
  app.ports.addChildToParent(node, parentNode)
  node
.children.each(function() {
    addChildToParent
(this, node);
 
});
};

addChildToParent
(tree, null);
// end psuedo code
 

Then the elm code can figure out which parent to add a child to, based on the ids. Thoughts?

Micah Geisel

unread,
Feb 14, 2016, 5:05:30 PM2/14/16
to Elm Discuss
I also realized that calling the tree "JSON" is a bit misleading, I really just mean plain old JavaScript object.

Max Goldstein

unread,
Feb 14, 2016, 7:27:18 PM2/14/16
to Elm Discuss
I think can pass a JS object, call the port Json.Encode.Value, and decode with decodeValue.

I can't go into more detail on the other questions right this moment; someone else want to take a swing?

Micah Geisel

unread,
Feb 15, 2016, 12:09:55 AM2/15/16
to Elm Discuss
I think I can further simplify my question: 

Given a JS tree of arbitrary depth, passed in through an Elm port:

// JavaScript


var tree =
 
{ children: [
   
{ children: [
     
{ children: [] },
     
{ children: [] },
   
]},
   
{ children: [] }
 
]};

Elm.worker(Elm.App, { model: tree });

-- Elm
port model
: ???

What is the type of the port? AFAICT, this scenario is impossible, due to only primitive types being allowed through ports, and thus the type signature would be infinite. Am I wrong?

Micah Geisel

unread,
Feb 15, 2016, 12:41:59 AM2/15/16
to Elm Discuss
Derp. After actually going and reading about Json.Encode and Json.Decode, I can see that you have already answered my question! Indeed, it seems that those libraries seem to be targeted at this exact quandary. I assumed they were for Json stringifying, and etc. I will dive into them, presently. Thank you for pointing me in the right direction, Max!

Micah Geisel

unread,
Feb 15, 2016, 1:47:28 AM2/15/16
to Elm Discuss
To clarify the solution for posterity: Elm ports only allow primitive types through... and additionally, `Json.Decode.Value`s.

Janis Voigtländer

unread,
Feb 15, 2016, 2:01:37 AM2/15/16
to elm-d...@googlegroups.com

That’s not quite true. There are other things that may pass through as well, for example Lists and Maybes, which are neither primitive types, nor Json.Decode.Values. See http://elm-lang.org/guide/interop#customs-and-border-protection.


2016-02-15 7:47 GMT+01:00 Micah Geisel <origino...@gmail.com>:
To clarify the solution for posterity: Elm ports only allow primitive types through... and additionally, `Json.Decode.Value`s.

--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Janis Voigtländer

unread,
Feb 15, 2016, 2:04:46 AM2/15/16
to elm-d...@googlegroups.com

Or, actually, yes, you are right. The Json.Decode.Value gets through and then is decoded to some of these other things like Lists and Maybes.

Reply all
Reply to author
Forward
0 new messages