Sanctuary: Add pipeK for monads

Created on 11 Apr 2017  ·  18Comments  ·  Source: sanctuary-js/sanctuary

Ramda has Kleisli composition via composeK and pipeK. We probably only need one of those.

It is worth considering instead a specification for Categories as suggested in https://github.com/fantasyland/fantasy-land/issues/208 and implementing a Kleisli type that exposes its own compose.

This would mean pipe would be sufficient if it were implemented in terms of compose.

Most helpful comment

S.pipe(S.map(S.chain, [f, g, h]))
const pipeK = S.compose(S.pipe, S.map(S.chain))

I'm happy defining it myself for my sporadic use-cases. :man_shrugging:

All 18 comments

This would mean pipe would be sufficient if it were implemented in terms of compose.

Wouldn't we still need to put something into a Kleisli at the beginning and take it out at the end?

I'm thinking about some like Kleisli Arrows with a compose:

compose :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)

I haven't thought out the particulars, but calling the result is going to return a monad.

Maybe

Kleisli :: Monad m => forall a b. m -> (a -> b) -> (a -> m b)

?

Another option is to just make Kleisli a wrapper like Endo.

const k = Kleisli(a => [a]); // The Kleisli identity?
const l = Kleisli(a => [a, a]);
compose(l, k)(1); // [1, 1]

Since pipeK would exist only for convenience, we should compare usage examples. Let's use this as the starting point:

S.pipe([S.chain(f), S.chain(g), S.chain(h)])

With pipeK:

S.pipeK([f, g, h])

With Kleisli:

S.pipe([S.Kleisli(f), S.Kleisli(g), S.Kleisli(h)])
S.pipe(S.map(S.chain, [f, g, h]))
const pipeK = S.compose(S.pipe, S.map(S.chain))

I'm happy defining it myself for my sporadic use-cases. :man_shrugging:

@davidchambers, I was thinking the same thing yesterday. I think we should still add it though.

Aldwin's implementation looks good.

Aldwin's definition is very elegant, but it relies on partial application. Since we don't use S functions internally, this implementation is our best option:

function pipeK(fs, x) {
  return Z.reduce(function(x, f) { return Z.chain(f, x); }, x, fs);
}

Gabe, would you like to submit a pull request for this?

Is this released?

I'm getting:

TypeError: _sanctuary2.default.pipeK is not a function

Version

└── [email protected]

@hbarcelos it's merged into master, but didn't make it in time for the last release. How did you find it?

I hope to release v0.15.0 this weekend, by the way. :)

@gabejohnson I saw this issue, saw the merged label and tried to use this function 😅.

@davidchambers OK, I'll wait until then :)

@gabejohnson @davidchambers Any interest in revisiting this and doing it in terms of Category (i.e. just have pipe work on categories, then have a category for kleisli arrows).

That sounds good to me, @masaeedu, provided the performance is acceptable.

How is this different from my proposal above?

It's probably not, but it looks like the implementation ended up being a specialized pipeK for one reason or another. A pipeC function that accepts the typerep of a category would result in pipeC(Function) being pipe and pipeC(Kleisli) being pipeK.

It would look something like this (but with the implementations fetched from the env by typerep):

const Fn = (() => {
  const id = x => x
  const compose = f => g => a => f(g(a))

  return { id, compose }
})()

const Kleisli = M => {
  // :: a -> m a
  const id = M.of
  // :: (b -> m c) -> (a -> m b) -> (a -> m c)
  const compose = bmc => amb => a => M.chain(bmc)(amb(a))

  return { id, compose }
}

const pipeC = C => arr => arr.reduce((p, c) => C.compose(c)(p), C.id)

// Monad for demonstrating kleisli composition
const Arr = (() => {
  const of = x => [x]
  const chain = f => x => x.reduce((p, c) => [...p, ...f(c)], [])
  const range = n =>
    Array(n)
      .fill(undefined)
      .map((_, i) => i + 1)

  return { of, chain, range }
})()

const pipe = pipeC(Fn)
const pipeK = M => pipeC(Kleisli(M))

const tests = [
  pipe([x => x + 1, x => x * 2])(5),

  pipeK(Arr)([Arr.range, x => (x % 2 ? [x, '👏'] : [x, '👏 👏'])])(4)
]

console.log(tests)

There's many interesting things that form categories (among them lenses), and pipeC would serve as a convenience function for composing more than two items in any category.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

arsaniwilliam picture arsaniwilliam  ·  9Comments

davidchambers picture davidchambers  ·  9Comments

kurtmilam picture kurtmilam  ·  11Comments

blackxored picture blackxored  ·  10Comments

fnune picture fnune  ·  3Comments