monet / monet.js

monet.js - Monadic types library for JavaScript
https://monet.github.io/monet.js/
MIT License
1.6k stars 114 forks source link

Applicative.apOn(value: Applicative<V>) proposal. #232

Closed iLikeKoffee closed 3 years ago

iLikeKoffee commented 3 years ago

Hello. First of all - thank you for awesome library. This is really great. I've got a little feature request.

AFAIK, common usage of Applicatives in haskell - applying function with N arguments to values. Just, something like this(example from here):

pure (+) <*> Just 3 <*> Just 5 
> 8

If i want to achieve the same behaviour with Monet, i should do something like this:

const maybeA = Maybe.of(10),
      maybeB = Maybe.of(12);

const curriedSum = a => b => a + b;
const applicative = Identity.of(curriedSum);

maybeB
  // This cannot be chained, so if we have, for example, function of 3 or more arguments, code will look much worse,
  // Something like this: maybeD.ap(maybeC.ap(maybeB.ap(maybeA.ap(applicative)))) and so on.  
  .ap(maybeA.ap(applicative))  
  .forEach(v => console.log(v))

My proposal is to create an alias for Applicative.ap with swapped arguments(this and value) order, Quick & dirty proof of concept:

import {Maybe, Identity} from 'monet';

Identity.prototype.apTo = function(value){
  return value.ap(this)
} 

const maybeA = Maybe.of(10),
      maybeB = Maybe.of(12);

const curriedSum = a => b => a + b;
const applicative = Identity.of(curriedSum);

applicative
  .apTo(maybeA)
  .apTo(maybeB)
  // ^ this can be chained as many times as number of arguments of our curried function and requires no nesting.
  .forEach(v => console.log(v))

JS cannot apply functions in haskell-like style (I mean firstArg f secondArg), so this alias seems suitable to me. If there is no objections against this proposal i would gladly implement it and provide a PR.

P.S. PoC in CodeSandbox

ulfryk commented 3 years ago

@iLikeKoffee I really like this idea, just have to think about typesafety.

declare const applicative: Maybe<(a: number) => (b: number) => number>;

applicative
  .flatMap(fn => maybeA.map(a => fn(a))) // ts knows that `fn` is function accepting number, returning function
  .flatMap(fn => maybeB.map(b => fn(b))) // ts knows that `fn` here is a funciton accepting number, returning number
  .forEach(v => console.log(v)) // ts knows that `v` is a number

But above example is ugly :(

We should investigate if there is a nice way to express apTo in TS and if not , maybe add it with some tradoffs (runtime checks? ).

But anyway - let's move this discussion to #206

(and yes, it's a pity that JS doesn't have infix operators 🙃 )

iLikeKoffee commented 3 years ago

Closing, as duplicate of #206