0

Tying to learn more about applicatives.

I have the following definitions:

class Applicative' f where
    pack :: a -> f a
    unpack :: f a -> a

instance Applicative' Box where
    pack x = Box x
    unpack (Box x) = x

instance Applicative' Bag where
    pack x = Bag x
    unpack (Bag x) = x

instance Applicative' Basket where
    pack x = Basket x
    unpack (Basket x) = x

pack1::(Applicative' f)=>(a->b)->f a->f b
pack1 fn ta = pack (fn (unpack ta))

pack2::(Applicative' f)=>(a->b->c)->f a->f b->f c
pack2 fn ta tb = pack(fn (unpack ta) (unpack tb))

What I am looking for is a way generalise this concept (something similar to sequenceA function), so it should possible to use this function (lets call it someFn) the following way:

somefn (\x->x+1) (Box 2)
Box 3

somefn (\x->\y->x+y) (Box 1) (Box 2)
Box 3

somefn (\x->\y->\z->x+y+z) (Box 1) (Box 2) (Box 3)
Box 6

Not sure how I can achieve this in Haskell.

duplode
  • 33,731
  • 7
  • 79
  • 150
coder_bro
  • 10,503
  • 13
  • 56
  • 88
  • 2
    Rather than `Applicative`, these looks more like isomorphisms in the vein of [`Control.Newtype`](http://hackage.haskell.org/package/newtype-0.2/docs/Control-Newtype.html) and/or [`Control.Lens.Wrapped`](http://hackage.haskell.org/package/lens-4.16.1/docs/Control-Lens-Wrapped.html). – duplode Jun 14 '18 at 08:19

4 Answers4

4

You are looking for the family of liftA* functions:

liftA :: Applicative f => (a -> b) -> f a -> fb
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
liftA3 :: Applicative f => (a -> b -> d -> e) -> f a -> f b -> f c -> f d

In Haskell these functions are defined separated. But I guess you want to generalize it to one function. That's impossible in Haskell since it would require to change the signature of your function. The Haskell approach is to sequence the operators <$> and <*> as follows:

liftA f a = f <$> a 
liftA2 f a b = f <$> a <*> b
liftA3 f a b c = f <$> a <*> b <*> c
liftA4 f a b c d = f <$> a <*> b <*> c <*> d -- (actually not pre-defined in haskell)
.
.
.
and so on

What is happening here is that f <$> a is lifting (pushing) your function within the Applicative container and then you sequence that function throughout a, b, c and d.

Hope it helps.

lsmor
  • 4,698
  • 17
  • 38
0

The function you want would need to be of type:

somefn :: Applicative' t => (a -> b) -> (t a -> t b)
somefn :: Applicative' t => (a -> b -> c) -> (t a -> t b -> t c)
somefn :: Applicative' t => (a -> b -> c -> d) -> (t a -> t b -> t b -> t c -> t d)

But these is no type which encompasses all of these so no such function exists. In particular there is no way to decide whether somefn (\x y -> (x, y)) (Box 3) should be Box (\y -> (3,y)) or \(Box y) -> Box (3,y).

Look at eg liftA2 or liftA3 for normal Applicatives.

Dan Robertson
  • 4,315
  • 12
  • 17
0

As already noted in other answers, you can't write a function with the signature you desire. Instead, you can do something similar to what Applicative does, by defining analogues to the (<$>) and (<*>) operators.

You can easily enough define these functions:

(<@>) :: Applicative' f => (a -> b) -> f a -> f b
f <@> x = pack . f . unpack $ x

(<%>) :: Applicative' f => f (a -> b) -> f a -> f b
f <%> x = pack . unpack f . unpack $ x

so that you can then write

(\x y z->x+y+z) <@> Box 1 <%> Box 2 <%> Box 3 -- results in Box 6

Or, if you don't mind enabling a couple extensions, you could take a different approach. Your Applicative' is a stronger constraint than Applicative, and so you can automatically derive Functor and Applicative instances for every Applicative' instance, getting you (<$>) and (<*>) for free:

{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}

instance Applicative' f => Functor f where
  fmap f x = pack . f . unpack $ x

instance Applicative' f => Applicative f where
  pure = pack
  f <*> x = pack . unpack f . unpack $ x

Now we can write

(\x y z->x+y+z) <$> Box 1 <*> Box 2 <*> Box 3 -- results in Box 6

and, happily, we also gain access to the liftA family of functions, meaning that for a reasonably small number of arguments, you can in fact use a function equivalent to the one you wanted:

import Control.Applicative (liftA3)

liftA3 (\x y z->x+y+z) (Box 1) (Box 2) (Box 3) -- results in Box 6
amalloy
  • 89,153
  • 8
  • 140
  • 205
0

You can unify these things. We'll need a heterogeneous list representing some type constructor mapped over a list of types:

{-# LANGUAGE GADTs, DataKinds, PolyKinds, TypeOperators, PatternSynonyms #-}

import Data.Kind (Type)
import Data.Functor.Identity

infixr 4 :<, ::<
data Rec :: [k] -> (k -> Type) -> Type where
  Nil :: Rec '[] f
  (:<) :: f x -> Rec xs f -> Rec (x ': xs) f

-- For convenience and clarity:
type HList xs = Rec xs Identity

pattern HNil :: () => xs ~ '[] => HList xs
pattern HNil = Nil

pattern (::<) :: () => xxs ~ (x ': xs) => x -> HList xs -> HList xxs
pattern x ::< xs = Identity x :< xs

{-# COMPLETE HNil, (::<) #-}

Now we can express our version of Applicative:

class Appl f where
  appl :: (HList xs -> r) -> Rec xs f -> f r

We can define appl in terms of Applicative:

applApplic :: Applicative f => (Rec xs Identity -> r) -> Rec xs f -> f r
applApplic f xs0 = f <$> gather xs0
  where
    gather :: Applicative f => Rec xs f -> f (Rec xs Identity)
    gather Nil = pure HNil
    gather (x :< xs) = (\y ys -> y ::< ys) <$> x <*> gather xs

We can also define the Applicative methods using appl:

fmapAppl :: Appl f => (a -> b) -> f a -> f b
fmapAppl f xs = appl (\(q ::< HNil) -> f q) (xs :< Nil)

pureAppl :: Appl f => a -> f a
pureAppl x = appl (\HNil -> x) Nil

liftA2Appl :: Appl f => (a -> b -> c) -> f a -> f b -> f c
liftA2Appl f xs ys = appl (\(x ::< y ::< HNil) -> f x y) (xs :< ys :< Nil)

You can use appl as a sort of n-ary lift:

appl (\(x ::< y ::< z ::< HNil) ->
       x : y ++ z) (readLn @Int :< readLn @[Int] :< readLn @[Int] :< Nil)

Type inference, unfortunately, is not great here.

dfeuer
  • 48,079
  • 5
  • 63
  • 167