Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In FP how do you set a reciprocal relationship?

In FP where there is no mutable state and every operation returns a new state of the world. Given: I have a contact list and an individual contact.

I add Dirk to my address book. Dirk is a child of my address book. My address book is the parent of Dirk. Being that I can't set both references at the same time I have a dilemma. The parent-child relationship should define an infinite cycle where I can step from parent to child to parent to same child forever.

Using JavaScript syntax:

var addresses = new AddressBook();
var dirk = new Contact(addresses, 'Dirk', ...);

On the second line I am passing in the address book without Dirk. Dirk has a parent reference to an address book without him in it.

I suspect the answer but I want to be sure. Am I actually going to mutate state to set this up properly or is there some technique I'm overlooking?

like image 706
Mario Avatar asked Mar 24 '23 04:03

Mario


1 Answers

If you want this kind of thing to work the way that it would in your JavaScript example (so you could directly look up the actual address book in the actual children), you have to make the address book mutable. It's not even because of the initial creation of the parent and child (that can be managed, in some functional languages more easily than in others), but because if you then go on to add further references to the address book, the old entries will still hold their outdated versions of the address book.

In Clojure, it's tempting to use an Atom or Ref to hold the entire address book in such a case and then also put an Atom or Ref pointing to the address book in each child, but Clojure reference types are only really designed to hold immutable data and nesting them may lead to problems.

A better solution is to give symbolic names to your entities (keywords, numbers, UUIDs are all fine) and store them in a map somewhere. Using a single atom could look like this:

(def state (atom {:contacts {:dirk ...}
                  :address-books {}}))

Then you can add Dirk to a new address book (creating it along the way in the form of a hash map) like so:

(swap! state (fn [state-map]
               (update-in state-map [:address-book :my-address-book]
                 (fn [abook]
                   (let [entries (get abook :entries [])]
                     (assoc abook :entries (conj entries :dirk)))))))

Note that this adds Dirk to the address book in the form of a symbolic reference (:dirk) to be looked up in the top-level state map under the :contacts key. If you also want the Dirk contact to maintain a list of address books of which it is a member, use a further update-in adding the appropriate information to the Dirk contact, possibly removing some nesting with ->:

(-> state-map
    (update-in [...] ...)
    (update-in [...] ...))
like image 78
Michał Marczyk Avatar answered Mar 25 '23 18:03

Michał Marczyk