I thought it would be interesting to build a visual programming interface such as Grasshopper or Max. The key components of such an interface:
The user creates a program by creating nodes and connecting them together, creating a directed graph.
There are various types of node that each have their own behavior, and a set number of inputs, and outputs.
Inputs and outputs are strongly typed; you can’t connect a String output to an Int input for example.
Any number of instances of a node can be created.
The created graph must be acyclic; you might call it a Directed Acyclic Graph.
How would you model such an interface in Elm? I want to use the type system to create this as much as possible. Nodes, inputs, and outputs seem to lend themselves well to union types, but it’s unclear how I can model the state of each node in a way that makes it easy to query all of the nodes connected to it, without having double references where adjacent nodes have to “agree” that they are connected to each other.
These are great resources, thank you! I’m not that worried about the view implementation yet, but I’m sure looking at Kite’s source will help later.
After thinking about this for a while, I decided to write my own Graph with elm-community/graph as a reference. Something like this:
type alias Graph = Dict NodeId Node
type alias Port = { id : NodeId, index : Int }
type Edge
= Float Port Port
| Texture Port Port
| ...
type alias Node n =
{ id : NodeId
, nodeType : NodeType
, incoming : Array (Maybe Edge)
, outgoing : Array (Maybe Edge)
}
where NodeType is an enum of all the different possible “types” of nodes.
While I think this would work fine, I was hoping to use the type system to enforce that you can’t connect nodes via incompatible edges or create multiple instances of the same node “type” that are different. For instance, with this system I could potentially make two instances of a node with nodeType = BlurTexture, where one has inputs [Float, Texture] and the other has [Texture, Texture]. This shouldn’t be possible because only one set of input types is valid.
Since I’m assuming that you never have the situation of one node having more than one parent, you are actually only looking at a subset of DAGs, namely trees. You might also get some inspiration from looking at the way the-sett/tea-tree was written… Although that was possibly designed for much larger structures than you had in mind.
Actually nodes can have any number of parents and any number of children, so I can’t model this structure as a tree. For example you might have a NodeAdd that has two Float inputs and one Float output, but also a NodeRGBSplit that has one Texture input and three Texture outputs. However, the tea-tree that you linked looks interesting—maybe I can approach the API in a similar way.