Conditionals in Ginger, Errata

by Brian Picciano - (9 min read)

After publishing the last post in the series I walked away from my computer feeling that I was very clever and had made a good post. This was incorrect.

To summarize the previous post, it’s not obvious which is the best way to structure conditionals in a graphical programming language. My favorite solution looked something like this:

       in -> } -> } -if-> } -0-> } -add-> out
in -1-> } -> }    }       } -1-> } -sub-> out
in -0-> }         }
         in -lt-> }

Essentially an if operator which accepts a value and a boolean, and which has two output edges. If the boolean is true then the input value is sent along the first output edge, and if it’s false it’s sent along the second.

This structure is not possible, given the properties of ginger graphs that have been laid out in other posts in the series.

Nodes, Tuples, and Edges

A ginger graph, as it has been presented so far, is composed of these three elements. A node has a value, and its value is unique to the graph; if two nodes have the same value then they are the same node. Edges connect two nodes or tuples together, and have a value and direction. Tuples are, in essence, a node whose value is its input edges.

The if operation above lies on an edge, not a node or tuple. It cannot have multiple output edges, since it cannot have any edges at all. It is an edge.

So it’s back to the drawing board, to some extent. But luckily I’ve got some more ideas in my back pocket.

Forks and Junctions

In an older conception of ginger there was no tuple, but instead there were forks and junctions. A junction was essentially the same as a tuple, just named differently: a node whose value is its input edges. A fork was just the opposite, a node whose value is its output edges. Junctions and forks naturally complimented each other, but ultimately I didn’t find forks to be useful for much because there weren’t cases where it was necessary to have a single edge be split across multiple output edges directly; any case which appeared to require a fork could be satisfied by directing the edge into a 1-tuple and using the output edges of the 1-tuple.

But now we have such a case. The 1-tuple won’t work, because the if operator would only see the 1-tuple, not its edges. It could be supposed that the graph interpreter could say that an if operation must be followed by a 1-tuple, and that the 1-tuple’s output edges have a special meaning in that circumstance. But making the output edges of a 1-tuple have different meaning in different circumstances isn’t very elegant.

So a fork might be just the thing here. For the example I will represent a fork as the opposite of a tuple: a vertical column of { characters.

       in -> } -> } -if-> { -0-> } -add-> out
in -1-> } -> }    }       { -1-> } -sub-> out
in -0-> }         }
         in -lt-> }

It looks elegant, which is nice. I am curious though if there’s any other possible use-case where a fork might be useful… if there’s not then it seems odd to introduce an entire new element just to support a single operation. Why not just make that operation itself the new element?

Switch it Up

In most conceptions of a flowchart that I’ve seen a conditional is usually represented as a node with a different shape than the other nodes (often a diamond). Ginger could borrow this idea for itself, and declare a new graph element, alongside nodes, tuples, and edges, called a switch.

Let’s say a switch is simply represented by a -<>, and acts like a node in all aspects except that it has no value and is not unique to the graph.

The example presented in the previous post would look something like this:

       in -> } -> } -<> -0-> } -add-> out
in -1-> } -> }    }     -1-> } -sub-> out
in -0-> }         }
         in -lt-> }

This isn’t the worst. Like the fork it’s adding a new element, but that element’s existence is required and its usage is very specific to that requirement, whereas the fork’s existence is required but ambiguously useful outside of that requirement.

On the other hand, there are macros to consider…


Ginger will certainly support macros, and as alluded to in the last post I’d like even conditional operations to be fair game for those who want to construct their own more complex operators. In the context of the switch -<> element, would someone be able to create something like a pattern matching conditional? If the builtin conditional is implemented as a new graph element then it seems that the primary way to implement a custom conditional macro will also involve a new graph element.

While I’m not flat out opposed to allowing for custom graph elements, I’m extremely skeptical that it’s necessary, and would like it to be proven necessary before considering it. So if we can have a basic conditional, and custom conditional macros built on top of the same broadly useful element, that seems like the better strategy.

So all of that said, it seems I’m leaning towards forks as the better strategy in this. But I’d like a different name. “Fork” was nice as being the compliment of a “junction”, but I like “tuple” way more than “junction” because the term applies well both to the structural element and to the transformation that element performs (i.e. a tuple element combines its input edges’ values into a tuple value). But “tuple” and “fork” seem weird together…

Many Minutes Later…

A brief search of the internet reveals no better word than “fork”. A place where a tree’s trunk splits into two separate trunks is called a “fork”. A place where a river splits into two separate rivers is called a “fork”. Similarly with roads. And that is what’s happening, from the point of view of the graph’s structure: it is an element whose only purpose is to denote multiple outward edges.

So “fork” it is.

Other considerations

A 1-tuple is interesting in that it acts essentially as a concatenation of two edges. A 1-fork could, theoretically, do the same thing:

a -foo-> } -bar-> b

c -far-> { -boo-> d

The top uses a tuple, the bottom a fork. Each is, conceptually, valid, but I don’t like that two different elements can be used for the exact same use-case.

A 1-tuple is an established concept in data structures, so I am loath to give it up. A 1-fork, on the other hand, doesn’t make sense structurally (would you point to any random point on a river and call it a “1-fork”?), and fork as a whole doesn’t really have any analog in the realm of data structures. So I’m prepared to declare 1-forks invalid from the viewpoint of the language interpreter.

Another consideration: I already expect that there’s going to be confusion as to when to use a fork and when to use multiple outputs from a node. For example, here’s a graph which uses a fork:

a -> { -op1-> foo
     { -op2-> bar

and here’s a graph which has multiple outputs from the same node:

a -op1-> foo
  -op2-> bar

Each could be interpreted to mean the same thing: “set foo to the result of passing a into op1, and set bar to the result of passing a into op2.” As with the 1-tuple vs 1-fork issue, we have another case where the same task might be accomplished with two different patterns. This case is trickier though, and I don’t have as confident an answer.

I think an interim rule which could be put in place, subject to review later, is that multiple edges from a node or tuple indicate that that same value is being used for multiple operations, while a fork indicates something specific to the operation on its input edge. It’s not a pretty rule, but I think it will do.

Stay tuned for next week when I realize that actually all of this is wrong and we start over again!