3

I am just about to learn functional programming using fp-ts and I am just asking myself what would be the proper functional way to »convert« such a thing to the functional paradigm:

//OOP:

interface Item {
  name: string;
}

class X {
  private readonly items: { [s:string]: Item[] } = {};

  add(i: Item): Item {
    if(!this.items.hasOwnProperty(i.name))
      this.items[i.name] = [];

    if(this.items[i.name].indexOf(i) < 0)    
      this.items[i.name].push(i);

    return i;
  }
}

So, I guess I should go this way:

import * as O from 'fp-ts/es6/Option';
import * as E from 'fp-ts/es6/Either';

// using interfaces from above

interface state {
  items: { [s:string]: Item[] }
}

export const createState = (): State => ({ items: {} });


export const add = (s: State, i: Item) => pipe(
  // using IO here?
  E.fromPredicate(
    () => s.hasOwnProperty(i.name),
    () => []
  )
  
    
)

// to use it:

import { createState, add } from './fx';

let state = createState();

// update
state = add(state, {name: 'foo'})

Since the add() operation involves the modification of state, should it rely on IO? If add returns a new state object, it is a pure function, so it wouldn't need to use IO? So the question I am posing here maybe a little broad, but: what are the recommended techniques/pattern here?

philipp
  • 15,947
  • 15
  • 61
  • 106

1 Answers1

1

Since the add() operation involves the modification of state, should it rely on IO?

Yes, add() won't return anything, but has a stateful effect, so it should return IO<void>

If add returns a new state object, it is a pure function, so it wouldn't need to use IO?

Correct.

What are the recommended techniques/pattern here?

Functional programmers generally avoid mutable state at all costs.

What you're trying to implement is a copy-on-write multimap. You shouldn't need anything from fp-ts for this.

type MyItem = { name: string };

// we've made the store polymorphic
type MyObj<Item> = { [s: string]: Item[] };

// this is only necessary if you want to expose an implementation-independent api.
export const empty = {};

// i before o, in case we want currying later, o will change more.
const add = <Item>(k: string, v: Item, o: MyObj<Item>) => 
  // abuse the spread operator to get create a new object, and a new array
  ({ ...o, [k]: [v, ...(o[k] || [])] });

// specialization for your item's case
export const addMyItem = (i: MyItem, o: MyObj<MyItem>) => add(i.name, i, o);

You can then do:

const a = addMyItem({ name: "test" }, addMyItem({ name: "test" }, empty));
414owen
  • 791
  • 3
  • 13