Prismatic/plumbing — pull request #7 by jamesmacaulay (2013-04-14 17:06:27+00:00)
Title: Schemaless graph node functions

I love the flexibility and power of Plumbing's graphs, and I think it would be great to be able to use them in more ways. When I first started learning about Plumbing, I thought that fnks were "just" map-destructured functions and didn't realize that they provided a schema which graphs depended on. I assumed I could use plain single-map-argument functions in graphs alongside fnks, but that's not the case.

These commits allows graphs to include node functions which don't have i/o schemas. The examples I added to `graph_examples_test.clj` illustrate the semantics:

```clojure
;; You can also include non-fnk functions in a graph, in which case they
;; always act solely on the input map and never depend on any particular
;; keys being present. Keywords can also be used as functions in this way.

(deftest schemaless-functions-test
(let [g (graph/->graph {:id (comp str :id)
:handle :handle
:name (fnk [first-name last-name]
(str first-name " " last-name))})]
(is (= {:id "1234" :handle "matilda" :name "Matilda Wormwood"}
(graph/run g
{:id 1234
:handle "matilda"
:first-name "Matilda"
:last-name "Wormwood"})))))

;; If a fnk uses a key that exists both in the input and output, the output
;; value is used as its argument.

(deftest fnks-act-on-output-test
(let [g (graph/->graph {:x (comp inc :x)
:y (comp dec :x)
:z (fnk [x y] [x y])})]
(is (= {:x 2 :y 0 :z [2 0]}
(graph/run g {:x 1 :y "unused"})))))
```

One advantage demonstrated above is that schemaless functions can output to any key, even if that key was used as part of its input. This doesn't introduce a cyclic dependency because all schemaless functions are guaranteed to operate on the original untouched input map.

The main use case I have in mind is a basic "presenter" pattern of transforming a map of model data into some partially altered form. For example, consider the following graph:

```clojure
(def user-presenter-graph
(->graph {:id :id
:name :name
:projects (fnk [id] (get-projects {:user-id id}))
:groups (fnk [id] (get-groups {:user-id id}))}))
```

This graph could be used to compose the context of a web view, with all the benefits of decoupling transformation semantics from the specifics of computation. Without the ability to use schemaless functions, you'd have to jump through more hoops to get the data into the form you want (mainly because outputting `(fnk [name] name)` to the `:name` key isn't allowed).

I'm not sure if this is a direction you want to take the project, and it's very possible that I haven't considered all the implications of allowing graphs to do these things. But here it is anyway, to do with as you will :)

Cheers,
James