It turns out it is far more effective to add and remove tags from objects than it is to add and remove behaviors directly. Tags are simply a nice way to group behaviors and apply them to objects.
( object/tag-behaviors :editor.markdown ) Here’s an example of making all markdown editors wrap lines and eval on change: As such we can develop a behavior in isolation and then automatically attach it to all objects that have a certain tag. Since then, however, they’ve become an important part of the engine despite serving a very simple purpose: they allow you to assign behavior to “kinds” of objects without being aware of the objects themselves. I didn’t mention Tags in my Conj talk because at the time it wasn’t entirely clear if they were the right solution. There is, however, one final way to add behavior to an object: tagging. This allows you to contextualize anything inside of Light Table, based on whatever conditions present themselves at runtime. Only being able to specify them at object definition time would be pretty limiting though, so you can also add and remove behaviors to any object with a simple function.
( behavior* :read-only :triggers # :triggers :behaviors :cur-line 0 :init ( fn. Here’s an example of one that could be added to an editor to make it read-only: But in each of those cases, eval does something different.īehaviors are a way of creating bite-sized, reusable reactions to messages. For the most part an editor is an editor whether it happens to contain plain text, JavaScript, or Clojure. Given that, we want to be able to compose these reactions to create new versions of the same object. What tends to define variation in evented applications isn’t the state of objects themselves, but how they react to certain messages. So what is the thing we want to compose if it’s not the state? It’s the reactions that objects have when events flow across the system. They’re just state and something outside of them breathes a little life into the party. It’s important to note that just like in CES these objects don’t do anything. If we wanted to add a :notifier to Light Table we’d use (object/create :notifier), which simply sticks a copy of that map into a big data structure containing all our objects. All objects inside of LT have a set of triggers (basically events that can be fired), a set of behaviors (things that will react to those events), and an init function that is a bit like a constructor, except what it returns is interpreted as its UI. Everything thereafter becomes the key-value pairs of the map. ( object* :notifier :triggers :behaviors :init ( fn ))įirst we give the object type a name, :notifier in this case. Here’s an example of what an object definition looks like: As such, we just use ClojureScript maps to represent objects instead of the groupings of many maps that was used in CES. It’s also not likely that disparate kinds of objects are going to share bits of state. Unlike in CES, it’s not particularly likely that, for example, different kinds of editors in Light Table are going to have wildly different state. Like with CES, I think the best way to introduce the idea is to walk through each piece to see what its role is. It has many of the same properties of a CES engine, but is a better fit for an evented architecture where you don’t have as many variations on the state of similar things (variation, instead, comes from how items react to events). What I came up with is what we’re calling a Behavior-Object-Tag engine. What we have is an event driven application and so we can’t just take the CES approach directly. We don’t have a constantly running loop and we aren’t checking state every 16ms. Games have a relatively similar set of requirements and we saw in the previous post that a Component-Entity-System engine worked nicely to satisfy these kinds of goals. The reason why stems from the architectural goals of the project: In my last post I talked about building a game in ClojureScript using a Component-Entity-System engine and I hinted that the ideas that underly that architecture inspired how we designed Light Table.
Here’s a video of this talk from the Clojure Conj 2012