sanctuary-js / sanctuary

:see_no_evil: Refuge from unsafe JavaScript
https://sanctuary.js.org
MIT License
3.04k stars 95 forks source link

Higher order compose-s #462

Open masaeedu opened 6 years ago

masaeedu commented 6 years ago

It'd be nice to have some utilities to compose functions of 2, 3, etc. arguments with another that transforms the final argument. In Haskell, for example, you can have:

> import Data.Monoid
> import Data.Map

> compose2 = fmap . fmap
> :t compose2
compose2
  :: (Functor f1, Functor f) => (a -> b) -> f1 (f a) -> f1 (f b)

> -- Example usage

> :t mappend
mappend :: Monoid a => a -> a -> a

> :t singleton
singleton :: k -> a -> Map k a

> -- How to compose mappend after singleton?

> insert = compose2 mappend singleton
> :t insert
insert :: Ord k => k -> a -> Map k a -> Map k a

And so on for higher orders with compose3 = fmap . fmap . fmap for composing 3 argument functions etc.

safareli commented 6 years ago

fmap for (a ->) type is compose right, so compose2 = (.) . (.)

masaeedu commented 6 years ago

@safareli Right, but if you can write it so it works with all functors instead of just functions, why not?

gabejohnson commented 6 years ago

@masaeedu compose is a member of Semigroupoid so it works for more than just functions.

Can you give an example of how you would want to use compose2 with functors that aren't functions?

masaeedu commented 6 years ago

@gabejohnson Ah, I wasn't aware of that. I guess . satisfies multiple typeclasses. A compose<N> function as I have it up there simply fmaps over functors nested N levels deep. E.g. you can compose a String -> Int with an [String] to get an [Int], or compose2 a String -> Int with a [Map String String] to get [Map String Int].

Maybe it should be called map2 etc. in Sanctuary.

davidchambers commented 6 years ago

The blackbird makes an appearance!

const compose2 = S.compose (S.compose) (S.compose);
const insert = compose2 (S.concat) (S.singleton);

insert ('z') (3) ({x: 1, y: 2});  // => {x: 1, y: 2, z: 3}

Defining S.compose2 and S.compose3 seems reasonable to me. Others may disagree. Feel free to submit a pull request, Asad. :)