ekmett / bifunctors

Haskell 98 bifunctors, bifoldables and bitraversables
Other
57 stars 42 forks source link

Add Bilift #56

Open Icelandjack opened 7 years ago

Icelandjack commented 7 years ago

Analogous to Control.Applicative.Lift

data Lift f a = Pure a | Other (f a)

we can define

data Bilift f a b = Bipure a b | Biother (f a b)
Icelandjack commented 7 years ago

If #12369 goes through we can define it as a data instance

data family Lift (f :: k) :: k

data instance Lift (f::Type -> Type)         (a::Type)           = Pure   a   | Other   (f a)
data instance Lift (f::Type -> Type -> Type) (a::Type) (b::Type) = Bipure a b | Biother (f a b)
Icelandjack commented 7 years ago

I suppose Maybe is an instance of this

data family   Maybe (f :: k) :: k
data instance Maybe                   (a::Type) = Nothing | Just a
data instance Maybe (f::Type -> Type) (a::Type) = Pure a  | Other (f a)

hm

Icelandjack commented 7 years ago

This could be given a Biapplicative instance, similar to original lift. We can use a Biapplicative f constraint but a Biapply f constraint suffices.

instance Biapply f => Biapplicative (Bilift f) where
  bipure :: a -> b -> Bilift f a b
  bipure = Bipure

  (<<*>>) :: Bilift f (a -> a') (b -> b') -> (Bilift f a b -> Bilift f a' b')
  Bipure f g  <<*>> Bipure x y  = Bipure (f x) (g y)
  Bipure f g  <<*>> Biother fxy = Biother (bimap f g fxy)
  Biother fga <<*>> Bipure x y  = Biother (bimap ($ x) ($ y) fga)
  Biother fga <<*>> Biother fxy = Biother (fga <<.>> fxy)

Random thought, we have (??)

(??) :: Functor f => f (a -> b) -> a -> f b
fab ?? a = fmap ($ a) fab

already, which is a generalisation of flip = (??) @((->) _). A similar thing can be done for Bifunctor

(???) :: Bifunctor f => a -> b -> f (a -> a') (b -> b') -> f a' b'
(a ??? b) xs = bimap ($ a) ($ b) xs

(???) :: Bifunctor f => a -> p (a -> a1) (a -> a2) -> p a1 a2
a ??? xs = bimap ($ a) ($ a) xs

is it interesting? Almost seems like an inverse of closed (unclosed?)

closed :: Closed p => p a b -> p (x -> a) (x -> b) 
Icelandjack commented 7 years ago

Seems like an Iso

closing
  :: (Closed p, Bifunctor p') 
  => x' 
  -> y'
  -> Iso (p a b)               (p' a' b') 
         (p (x -> a) (x -> b)) (p' (x' -> a') (y' -> b'))
closing a b = iso closed (bimap ($ a) ($ b))
RyanGlScott commented 7 years ago

My first impression is that Bilift seems like a sensible thing to have. After all, we have Applicative transformers, so why not Biapplicative transformers?

If you'd care to whip up a pull request, I'll take a look at it.

Icelandjack commented 7 years ago

How about the dependencies, Biapply is from semigroupoids which depends on bifunctors

RyanGlScott commented 7 years ago

True. I see two options:

  1. For consistency with Lift from transformers, we just make it instance Biapplicative f => Biapplicative (Bilift f). This is a stronger constraint than is necessary, but then again, so are several other instances in the Applicative diaspora.
  2. We put Bilift in semigroupoids instead so that we can give the instance a Biapply context instead.

I don't have a strong opinion on which direction to take.

Icelandjack commented 7 years ago

The situation with Lift (as with Monoid a => Monoid (Maybe a)) is unfortunate and I worry about digging a deeper hole

It's worth pointing out that semigroupoids has

instance Apply f => Applicative (MaybeApply f)

for the type newtype MaybeApply f a = MaybeApply (Either (f a) a) so I suppose we can add

-- -semigroupoids- package 
import qualified Data.Bifunctor.Sum as Bi

newtype MaybeBiapply f a b = MaybeBiapply (Bi.Sum f (,) a b)
  deriving newtype
    (Bifunctor, Bifoldable)

instance Biapply f => Biapplicative (MaybeBiapply f)
-- -bifunctors- package
data Bilift f a b = Bipure a b | Biother (f a b)

instance Biapplicative f => Biapplicative (Bilift f)

— this would be a mixture of option 1. and 2. mirroring the situation with Lift / MaybeApply