Daniel Earwicker Chief Software Architect
FISCAL Technologies Ltd

Immuto - Radical Unification


TYPESCRIPT IMMUTO 2016-09-22

Immuto continues to evolve rapidly. To ensure that I comply with Semantic Versioning, in which major version 0 implies an unstable API, I've been making major breaking changes every day or so.

The major shift since the first write-up is left-to-right cursor composition. Example - here's the signature of a function that gets a book from a shelf:

declare function getBook(shelf: Shelf, id: number): Book;

And here's one that gets a shelf from a shop:

declare function getShelf(shop: Shop, id: number): Shelf;

If we have a shelfId and a bookId, to go straight from shop to book:

const book = getBook(getShelf(shop, shelfId), bookId);

Ugh. The nesting makes it confusing. This is why people like methods on objects:

const book = shop.getShelf(shelfId).getBook(bookId);

That's the clarity of left-to-right composition. In Immuto we have clean pure-data interfaces, and we declare actions on them and form them into reducers. One kind of action is called a reference and it builds a function that works a bit like getBook and getShelf, except it deals with cursors to data. So it suffered from the same nested composition ugliness.

What we need, taking a leaf out of functional programming, is a piping operator. I've added a method called $ to the Cursor interface, which means "look up", and corresponds to the familiar . operator in many object orientated languages.

So if my Shop has a collection shelves (of type Shelves) in which each item is a Shelf, and a Shelf has a collection books (of type Books) in which each item is a Book, I can get a cursor to a book like this:

const book = shop.$(Shop.shelves)
                 .$(Shelves.at(shelfId))
                 .$(Shelf.books)
                 .$(Books.at(bookId))

It makes complicated manoeuvres pretty readable and logical. Each step is like:

whatIGot.$(whatIWant)

So it's easy to understand by analogy with the ordinary . operator, although a bit more syntactically noisy. It would be nice if I could implement the [] operator, and so get:

const book = shop[Shop.shelves]
                 [Shelves.at(shelfId)]
                 [Shelf.books]
                 [Books.at(bookId)]

But indexers are very special in JavaScript and you can't just implement them like any function.

One other change I made yesterday which I've hinted at above is that I've ripped out the special treatment of collections. It was an unnecessary concept that needed special explanation, it was a bit too magical which made the explanation difficult, and it baked in too much functionality that you might not want. Not all collections need to support random access deletion, for example. Also there are fundamental difference between kinds of collections: if you delete item 4 from an array, you renumber items 5, 6, 7… whereas that doesn't happen with a map. Do I hide that difference or let it leak out? These are all decisions I shouldn't be taking.

So now collections are just a data type with a reducer and some actions, same as anything else:

export type Books = { [id: number]: Book };

export namespace Books {
    export const empty: Books = {};
    export const at = objectByNumber(Book);
    export const reduce = reducer(empty).action(at);
}

I give the collection type a name, then I write a namespace for it, exposing only the actions I want. The objectByNumber function provides a way to get a cursor to a Book out of a collection of Books, i.e. it returns a traversal function suitable for passing to the new .$() piping operator.

There are now quite a few examples of this in immuto-react and immuto-example.

Time reversible events 2023-04-07
Language Smackdown: Java vs. C# 2023-03-07
Domesday '86 Reloaded (Reloaded) 2021-02-07
The Blob Lottery 2020-09-27
Abstraction is a Thing 2020-03-07
Unfortunate Bifurcations 2019-11-24
Two Cheers for SQL 2019-08-26
Factory Injection in C# 2019-07-02
Hangfire - A Tale of Several Queues 2019-05-24
How Does Auth work? 2018-11-24
From Ember to React, Part 2: Baby, Bathwater, Routing, etc. 2018-03-18
From Ember to React, Part 1: Why Not Ember? 2017-11-07
json-mobx - Like React, but for Data (Part 2) 2017-02-15
Redux in Pieces 2017-01-28
Box 'em! - Property references for TypeScript 2017-01-11
TypeScript - What's up with this? 2017-01-01
MobX - Like React, but for Data 2016-12-28
Eventless - XAML Flavoured 2016-12-24
Immuto - Epilogue 2016-12-20
Immuto - Radical Unification 2016-09-22
Immuto - Working with React (An Example) 2016-09-16
Immuto - Strongly Typed Redux Composition 2016-09-11
TypeScript - What is a class? 2016-09-11
TypeScript and runtime typing - EPISODE II 2016-09-10
TypeScript and runtime typing 2016-09-04
What's good about Redux 2016-07-24
TypeScript multicast functions 2016-03-13
Introducing doop 2016-03-08
TypeScript is not really a superset of JavaScript and that is a Good Thing 2015-07-11
A new kind of managed lvalue pointer 2014-04-27
Using pointer syntax as a shorthand for IEnumerable 2014-04-26
Adding crazily powerful operator overloading to C# 6 2014-04-23
Introducing Carota 2013-11-04
Want to comment on anything? Create an issue!