Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pipeK for monads #380

Closed
gabejohnson opened this issue Apr 11, 2017 · 18 comments
Closed

Add pipeK for monads #380

gabejohnson opened this issue Apr 11, 2017 · 18 comments

Comments

@gabejohnson
Copy link
Member

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 fantasyland/fantasy-land#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.

@gabejohnson gabejohnson changed the title Add pipeK for monad Add pipeK for monads Apr 11, 2017
@davidchambers
Copy link
Member

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?

@gabejohnson
Copy link
Member Author

gabejohnson commented Apr 11, 2017

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.

@gabejohnson
Copy link
Member Author

Maybe

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

?

@gabejohnson
Copy link
Member Author

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]

@davidchambers
Copy link
Member

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)])

@Avaq
Copy link
Member

Avaq commented Apr 13, 2017

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. 🤷‍♂️

@gabejohnson
Copy link
Member Author

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

Aldwin's implementation looks good.

@davidchambers
Copy link
Member

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);
}

@davidchambers
Copy link
Member

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

@hbarcelos
Copy link

Is this released?

I'm getting:

TypeError: _sanctuary2.default.pipeK is not a function

Version

└── sanctuary@0.14.1

@gabejohnson
Copy link
Member Author

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

@davidchambers
Copy link
Member

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

@hbarcelos
Copy link

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

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

@masaeedu
Copy link
Member

masaeedu commented Jul 13, 2018

@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).

@davidchambers
Copy link
Member

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

@gabejohnson
Copy link
Member Author

How is this different from my proposal above?

@masaeedu
Copy link
Member

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.

@masaeedu
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants