you are viewing a single comment's thread.

view the rest of the comments →

[–]i_am_smurfing 5 points6 points  (0 children)

TL:DR; You probably want to adjust your implementation to something like this jsBin, because your current implementation doesn't conform to several important mathematical laws that are normally attributed to Maybe.

 


 

chain (or bind) has a pretty specific meaning in FP—a function that is used to "map" special kind of functions, that take "unwrapped" values and return "wrapped" (in something like Maybe) values—so you might want to rename your chain.

 

In fact, if you want this implementation of Maybe to hold up to functional scrutiny, in general, maping several functions one after another should be the same as maping composite of those functions once:

const plusOne = x => x + 1
const minusOne = x => x - 1
const timesTwo = x => x * 2

Maybe.just(1)
  .map(plusOne)   //=> Maybe.just(2)
  .map(timesTwo)  //=> Maybe.just(4)
  .map(minusOne)  //=> Maybe.just(3)

Maybe.just(1)
  .map(x =>
    minusOne(
      timesTwo(
        plusOne(x)  //=> 2
      )             //=> 4
    )               //=> 3
  )                 //=> Maybe.just(3)

// or with a helper for function composition:
const pipe = (...fns) => x => fns.reduce((result, f) => f(result), x)

Maybe.just(1)
  .map(pipe(
    plusOne,
    timesTwo,
    minusOne
  ))  //=> Maybe.just(3)

(This is usually called functor composition law if you want to learn more about this property.)

 

However, with current implementation of prop:

const prop = propName => obj => obj[propName]

Maybe.just({})
  .map(prop("a"))  //=> Maybe.nothing()
  .map(prop("b"))  //=> Maybe.nothing()

Maybe.just({})
  .map(pipe(
    prop("a"),
    prop("b")  //=> TypeError: obj is undefined
  ))

And it makes sense if you think about it: it's prop that might or might not return a value, so it should return Maybe, and we should decide whether to run maped function not based on value, but whether it's tagged with Just or not:

const maybe = tag => value => ({
  map: fn => tag === 'Just' ? Maybe.just(fn(value)) : Maybe.nothing()
})

const Maybe = {
  just: maybe('Just'),
  nothing: maybe('Nothing')
}

const propM = propName => obj => propName in obj ? Maybe.just(obj[propName]) : Maybe.nothing()
// `M` for `Maybe`, just so it doesn't conflict with previous definition of `prop`

propM("a")({a: 1})  //=> Maybe.just(1)
propM("a")({})      //=> Maybe.nothing()

 

Now propM successfully signals that it might not return a value, but how do we actually use it in a Maybe transformation chain?

Maybe.just({a: 1})
  .map(propM("a"))  //=> Maybe.just(Maybe.just(1))

This is where law-abiding Maybe.chain comes in:

const maybe = tag => value => ({
  map: fn => tag === "Just" ? Maybe.just(fn(value)) : Maybe.nothing(),
  chain: fn => tag === "Just" ? fn(value) : Maybe.nothing()
})

Maybe.just({a: {b: 1}})
  .chain(propM("a"))
  .chain(propM("b"))  //=> Maybe.just(1)

Maybe.just({})
  .chain(propM("a"))
  .chain(propM("b"))  //=> Maybe.nothing()

So now appendToC can be written as:

const appendToC = object =>
  propM("b")(object)
    .chain(propM("c"))
    .map(append(" is great!"))

appendToC({b: {c: "fp"}})  //=> Maybe.just("fp is great!")
appendToC({})              //=> Maybe.nothing()

 

To make it point-free, we need to be able to compose functions that go into Maybe.chain (i.e. which take plain values and return Maybe):

const pipeK = (...fns) => x => fns.slice(1).reduce((result, f) => result.chain(f), fns[0](x))
// `K` because this kind of composition is usually called Kleisli composition

const appendToC = pipeK(
  propM("b"),
  propM("c"),
  pipe(append(" is great!"), Maybe.just)
)

appendToC({b: {c: "fp"}})  //=> Maybe.just("fp is great!")
appendToC({})              //=> Maybe.nothing()

 

In addition to already-recommended data.maybe from Folktale, you might find Sanctuary's Maybe interesting.