In my previous post about propagators, we looked at how to define simple one- and two-way relations and we looked at generic operators, which were used extending the definition of conflicts to include sets. In this post, we will examine how to define mathemtical relations between cells containing not only simple values, but also sets. We will use these relations to build a version of the celcius to fahrenheit relation that works not only between cells containing numbers, but also between cells containing sets.

All the code here uses version 0.2.0 of the propaganda library. The code in it’s full form is available at github.

We want generic versions of addition, subtraction, multiplication and
division that will accept two numbers, a set and a number, and two
sets. To do this, we write a function `generic-set-operator`

that takes
an operator and returns a generic operator based on the given operator.

```
(defn generic-set-operator
[op]
(doto (go/generic-operator op)
(go/assign-operation
(fn [s v] (into #{} (for [elm s] (op elm v))))
set? any?)
(go/assign-operation
(fn [v s] (into #{} (for [elm s] (op v elm))))
any? set?)
(go/assign-operation
(fn [s1 s2] (into #{} (for [e1 s1 e2 s2] (op e1 e2))))
set? set?)))
(def plus (generic-set-operator +))
(def minus (generic-set-operator -))
(def multiply (generic-set-operator *))
(def divide (generic-set-operator /))
(plus 1 2)
;; => 3
(plus #{1 2 3} 4)
;; => #{5 6 7}
(plus 4 #{1 2 3})
;; => #{5 6 7}
(plus #{1 2 3} #{1 2 3})
;; => #{2 3 4 5 6}
```

We can lift the operators to propagator constructors.

```
(def plusp (function->propagator-constructor plus))
(def minusp (function->propagator-constructor minus))
(def multiplyp (function->propagator-constructor multiply))
(def dividep (function->propagator-constructor divide))
```

Finally, we can create the two-way relations for sums and products.

```
(defn sum
[system a b c]
(-> system
(plusp a b c)
(minusp c a b)
(minusp c b a)))
(defn prod
[system a b c]
(-> system
(multiplyp a b c)
(dividep c a b)
(dividep c b a)))
(-> (make-system)
(prod :a :b :c)
(add-value :a 10)
(add-value :b 2)
(get-value :c))
;; => 20
(-> (make-system)
(prod :a :b :c)
(add-value :a 2)
(add-value :c 84)
(get-value :b))
;; => 42
```

In the previous post, we did not have sum and product relations, so we had to write two function for defining the two-way relation. This time around, we can use these new propagator constructors to define our relation.

```
(defn c-f-relation
[system f c]
(let [minus-32 (gensym "minus-32")
five-ninths (gensym "five-ninths")
f-minus-32 (gensym "f-minus-32")]
(-> system
(add-value minus-32 -32)
(add-value five-ninths 5/9)
(sum f minus-32 f-minus-32)
(prod five-ninths f-minus-32 c))))
```

The constants we need to define are stored under three new cells
allocated for the purpose. `gensym`

is used to come up with unique
identifiers for those cells.

The `c-f-relation`

function works both ways, so our examples from the
previous post will still give us the results we want.

```
(-> (make-system)
(c-f-relation :temp-f :temp-c)
(add-value :temp-f 100N)
(get-value :temp-c))
;; => 340/9
(-> (make-system)
(c-f-relation :temp-f :temp-c)
(add-value :temp-c 340/9)
(get-value :temp-f))
;; => 100N
(-> (make-system)
(c-f-relation :temp-f :temp-c)
(add-value :temp-f 100N)
(add-value :temp-c 38N)
(get-value :temp-c))
;; throws ExceptionInfo Inconsistency
```

Using the `extend-merge`

function from the previous post, we can have
our relations working on sets, as `c-f-relation`

only uses generic
operators that work on both sets and numbers.

```
(let [my-merge (doto (default-merge)
extend-merge)
my-contradictory? (default-contradictory?)]
(-> (make-system my-merge my-contradictory?)
(c-f-relation :temp-f :temp-c)
(add-value :temp-f #{100N 200N})
(get-value :temp-c)))
;; => #{280/3 340/9}
```

As conflicts only occur when the there is an empty intersection between sets, we can have several restrictions on the same cell to refine the value of the temperature in celcius

```
(let [my-merge (doto (default-merge)
extend-merge)
my-contradictory? (default-contradictory?)]
(-> (make-system my-merge my-contradictory?)
(c-f-relation :temp-f :temp-c)
(add-value :temp-f #{100N 200N})
(add-value :temp-c #{110/4 280/3})
(get-value :temp-c)))
;; => 280/3
```

And, as data flows both ways, the temperature in fahrenheit will automatically have been restricted.

```
(let [my-merge (doto (default-merge)
extend-merge)
my-contradictory? (default-contradictory?)]
(-> (make-system my-merge my-contradictory?)
(c-f-relation :temp-f :temp-c)
(add-value :temp-f #{100N 200N})
(add-value :temp-c #{110/4 280/3})
(get-value :temp-f)))
;; => 200N
```

In this second tutorial we have explored how to define mathemtical relations that not only operate on numbers, but also across compound data structures, in this case sets. We have redefined our temperature relation to use generic two-way operators which has two consequences: we do not need to define the bijective relation explicitly, and the relation will work on both numbers and sets.

I hope you’ve enjoyed reading through the tutorial. If you’re interested in other applications of propagators, I can recommend the article The Art of the Propagator.

comments powered by Disqus