The Tuvalu Interpretation

10 Oct 2018

Once upon a time, a polymath (Devine Lu Linvega, @neauoire) created Riven (named for the fifth Age created by Gehn). Riven is a javascript library, meant to be the bare minimum necessary for flow-based programming. Linvega used it to write Oscean, a serverless wiki.

Flow-based programming derives from the direct-manipulation "interface" of a physical patch bay or breadboard. It decouples the modules of an architecture from each other, because they cannot know which module or modules are on the other end of any connection. Many, many tools, including for example the HTML5 audio api, use this metaphor.

Riven distinguishes itself from these by having precisely two ways of nodes to communicate with one another. One is a request/answer pattern similar to (in object oriented programming) any method that does not mutate the receiver and returns some value, or a "GET" in a RESTful api. The other is a send/receive pattern similar to (in object oriented programming) any method that does not return any value, or a "POST" or one of the other verbs in a RESTful api.

So where a HTML5 audio api uses a directed graph, Linvega's Riven uses a directed graph with two colors of edges. I thought this was interesting, and so I studied it by making a derivative work, a library called luminous-amphibian (part of my game modeling project volcano-beginner), and then I interviewed Linvega about Riven.

@johnicholas: "Hi, I wrote a thing based on your Riven library. Would you be willing to answer some questions about Riven and software architecture?"

@neauoire: "Let me have a look : ) Yes, ask me anything!"

@johnicholas: "I'm sure you're familiar with the 'Berlin Interpretation', a set of factors developed at the International Roguelike Development Conference 2008 to characterize the genre of Roguelikes. For example, the Berlin Interpretation includes factors like random environment generation and permadeath. What points are necessary and/or sufficient to capture your intent?


  • Basics
    • .create()
    • .connect()
  • Connection
    • .Send()
    • .Receive()
    • .Request()
    • .Answer()
  • Helpers
    • .signal()
    • .mesh()
  • Convenience
    • .bind
    • .bang

@johnicholas: What design principles were you paying attention to that I am missing?

@neauoire: I've built this as a way to organize and visualize the sites I was working on. It's not designed to have any kind of drag-drop interface, but instead, to have the graph object code on one side, and the rendered resulting graph on the other. Riven is not really designed for interaction design, or real time projects even tho it could. Ideally, nodes should not preserve any of the data, but only be operating on the input it receives.

@johnicholas: So "signal" is unlike the others, in that it returns a node by name. Its behavior contrasts with "send", which acts like a "POST" to a family of nodes, the ones at the destination of all the send/receive edges starting at this node, and with "request", which acts like a "GET" to a family of nodes, the ones at the destination of the request/answer edges starting at this node. If you systematically renamed all of the nodes (s/a/x/g;s/b/y/g;s/c/z/g) in a Riven graph, then everything would work identically - except if a node uses "signal". In that case you would need to change the source code of the node as well as the source code of the graph. Do you think that signal is central, crucially important to the idea of Riven or is it a wart on the design?

@neauoire: Signal is not very important, it's to focus the output of a node in a specific way, I've added a good usecase of signal in the conditional example. Signal's purpose is also to test if there's a signal between two nodes, and to follow that signal across the graph.

@johnicholas: What principle led you to prefer the one-argument signal method over alternatives, such as a variadic .signal("parser", "hello") that might be easier to use?

@neauoire: It used to be like that, but the point now is that you can follow the signal across multiple nodes, like: Ø("root").signal("body").signal("element").signal("children"), its name is meant to be a reference to "following the data".

@johnicholas: What were your intentions regarding ports, they seem to have dropped away from relevance?

@neauoire: I've been trying to not expose them as much that's true. I only seldom had to target a port by name, and it always turned out to be from a mistake in the design of the webapp. So I'm gradually cleaning up the port UX.

@johnicholas: Would you be comfortable calling a hypothetical C++ or Java or C# library a 'Rivenlike' if it had the elements you listed above?

@neauoire: Sure! Although, I'm currently rebuilding the .mesh behaviour, so I'd wait on that part for now.

@johnicholas: Are you attached to the vocabulary you chose connect/send/receive/bind/answer etc, or would you consider that superficial?

@neauoire: I am not attached, you can choose which ever words you prefer, a sort of .send, .onSend, might make more sense for you.

@johnicholas: There's a tension between, on the one hand, the notion of ports (which you mentioned you are exposing less) and your mesh-as-black-box plans, and on the other hand, a flat, shared namespace. Would you consider "a flat, shared namespace" to be an essential factor in defining what a Rivenlike is?

@neauoire: I think both can co-exist, I've been thinking that it would be nice if I could reduce the number of direct id node calls(Ø("database")) in my wiki code, I like that an interaction goes through an MVC-type flow, and does not go backward. Although, it's nice to have, and it makes tester and itterating a lot faster. I would like to make it so the children of mesh type nodes are not publicly accessible too.

@johnicholas: There's a tension between your mesh-as-black-box plans and the fact that until recently grouping nodes with mesh doesn't mean anything operationally. Would you consider "the grouping of nodes into hierarchy doesn't mean anything operationally" to be an essential factor in defining what a Rivenlike is?

@neauoire: I'm not sure yet which way this will go. As of today, I use the node graph as a way to keep an oversight of how the data flows across my apps. I don't think there's a "hierarchy" system in Riven, it's just some nodes can be "tagged" as being part of one part of the system or another.

@johnicholas: If I understand correctly, the values that flow among the various nodes are currently plain JSON structures, acyclic and/or immutable. Would you say passing plain values (such as protobufs or s-expressions) is an essential factor in making something Rivenlike, or would passing a reference to a service, a function object, or an opaque object with some methods on it be a reasonable thing to do in a Rivenlike?

@neauoire: Well, while I don't pass functions along the graph, it could be done and it would not make it any less Riven. It's just how I see it, data flows through "machines", so I don't send machines down the signal, but it could be done.

@johnicholas Cool, thanks! If I understand correctly, you were in Tuvalu recently. Do you want to name this interpretation after Tuvalu instead of Berlin?

@neauoire: Tuvalu sounds better :)