Closed Avaq closed 3 years ago
I realized later that from the perspective of combinatory logic, flipping the current ap
is a bad idea. It would no longer be equal to the common S
combinator.
On the subject of combinators, I now believe Sanctuary should provide all the common combinators, even though several are specializations of functions Sanctuary already provides.
If you are interested in pursuing this change, Aldwin, please create a branch named avaq/ap
, flip the argument order, update the corresponding test cases, and finally create experimental branches for at least two projects that depend on Sanctuary (pointing to avaq/ap
). I realize that this would be a significant amount of work, but we should not change a crucial function unless we are convinced that a change is warranted.
Are you still in favour of this change, Aldwin?
The arguments in favour of and those against cancel each other out for me. I prefer to leave things as is, but only by a small margin.
Consider it considered!
Actually why did we never consider just adding ap_
or flap
(:grin:*) as I call it in my code-bases?
* What makes flap
a great name is that its primary purpose is to use it when lift3
is not big enough, so in my code, it only ever appears in pipelines with 4 or more consecutive flaps. The name is derived from flip
and ap
, of course.
I like the idea of adding ap_
. :)
Just did some local tests of this to better understand the effect of ap_
to liftN
. Perhaps it is useful for others too.
liftA4 :: Applicative f => (a -> b -> c -> d -> e) -> f a -> f b -> f c -> f d -> f e
liftA4 f a b c d = liftA3 f a b c <*> d
https://hackage.haskell.org/package/base-4.16.1.0/docs/src/GHC-Base.html#liftA3
// lift4 :: Apply f => (a -> b -> c -> d -> e) -> f a -> f b -> f c -> f d -> f e
const lift4 = f => a => b => c => d => S.ap (S.lift3 (f) (a) (b) (c)) (d)
// lift5 :: Apply f => (a -> b -> c -> d -> e -> f) -> f a -> f b -> f c -> f d -> f e -> f f
const lift5 = f => a => b => c => d => e => S.ap (lift4 (f) (a) (b) (c) (d)) (e)
// ap_ :: Apply f => f a -> f (a -> b) -> f b
const ap_ = S.flip (S.ap)
// lift4_ :: Apply f => (a -> b -> c -> d -> e) -> f a -> f b -> f c -> f d -> f e
const lift4_ = f => a => b => c => d => ap_ (d) (S.lift3 (f) (a) (b) (c))
// lift5_ :: Apply f => (a -> b -> c -> d -> e -> f) -> f a -> f b -> f c -> f d -> f e -> f f
const lift5_ = f => a => b => c => d => e => ap_ (e) (lift4_ (f) (a) (b) (c) (d))
const verify = a => b => c => d => (
a === 1 && b === 2 && c === 3 && d === 4
? a + b + c + d
: new TypeError ('Argument mismatch')
)
const verify2 = a => b => c => d => e => (
e === 5
? verify (a) (b) (c) (d) + e
: new TypeError ('Argument mismatch')
)
const one = S.Just (1)
const two = S.Just (2)
const three = S.Just (3)
const four = S.Just (4)
const five = S.Just (5)
console.debug (
lift4 (verify) (one) (two) (three) (four), // Just (10)
lift4_ (verify) (one) (two) (three) (four), // Just (10)
lift5 (verify2) (one) (two) (three) (four) (five), // Just (15)
lift5_ (verify2) (one) (two) (three) (four) (five), // Just (15)
)
I would argue that the current argument order does not favour composition with partial application. I would want the following two programs to be equivalent.
However, they are not, because
S.ap
takes the function argument first, rather than last, necessitating aflip
beforeap
is useful:Furthermore, the observation that
ap
is the only Fantasy Land function in Sanctuary that does not follow the convention that its last argument is thethis
to the Fantasy Land method reinforces my view that the arguments should be flipped. This has also led me to flip the arguments in the latest version of Fluture.