There is a bit of overlap between Bifunctor and Arrow methods:
class Bifunctor p where
first :: (a -> a') -> p a b -> p a' b
second :: (b -> b') -> p a b -> p a b'
bimap :: (a -> a') -> (b -> b') -> p a b -> p a' b'
class Arrow (~~>) where
...
first :: (a ~~> a') -> (a, b) ~~> (a', b)
second :: (b ~~> b') -> (a, b) ~~> (a, b')
(***) :: (a ~~> a') -> (b ~~> b') -> (a, b) ~~> (a', b')
The Bifunctor class comes with laws completely analogous to those of Functor.
The Arrow class comes with a number of laws different laws and a somewhat cryptic warning about (***): "Note that this is in general not a functor." Surprisingly (to me) there's only one law about (***):
first f >>> arr (id *** g) = arr (id *** g) >>> first f
The Arrow (->) instance and the Bifunctor (,) instance match up exactly, so that bimap #(,) = (***) #(->). Is there some special significance to this? Is there a meaningful hypothetical
class Foo (~~>) p where
biFoo :: (a ~~> a') -> (b ~~> b') -> p a b ~~> p a' b'
If so, does that admit functional dependencies?
Arrow is a (somewhat bastardized) precursor to a class of cartesian closed categories, or a least cartesian monoidal categories. Specifically, to monoidal categories whose tensor product is (,) and unit element ().
Recall that a monoidal category is characterised by the tensor product as a bifunctor, so there's your connection between Arrow and Bifunctor.
*** has in fact more laws than you listed, only, the library chooses to formulate those in terms of first instead. Here's an equivalent definition of the class:
class (Category k, Category k') => EnhancedCategory k k' where
arr :: k a b -> k' a b
-- arr id ≡ id
-- arr (f . g) = arr f . arr g
class (EnhancedCategory (->) a) => Arrow a where
(***) :: a b c -> a b' c' -> a (b,b') (c,c')
-- (f***id) . (g***id) ≡ (f.g)***id
-- (id***f) . (id***g) ≡ id***(f.g)
-- arr fst . (f***id) ≡ f . arr fst
-- arr snd . (id***g) ≡ g . arr snd
-- ¿ arr swap . (f***g) ≡ (g***f) . arr swap ?
-- ((f***g)***h) . assoc ≡ assoc . (f***(g***h))
diag :: a b (b,b)
first :: Arrow a => a b c -> a (b,d) (c,d)
first f = f***id
second :: Arrow a => a b c -> a (d,b) (d,c)
second g = id***g
(&&&) :: Arrow a => a b c -> a b d -> a b (c,d)
f&&&g = (f***g) . diag
Incidentally, it is also possible to remove the arr for lifting pure functions, and instead give the superclass only the dedicated methods fst, snd and assoc. I call that class Cartesian. This allows defining “arrow” categories that don't contain arbitrary Haskell functions; linear maps are an important example.
Arrow is equivalent to Strong + Category.
You can choose a different notion of strength to get a different kind of Arrow.
class Category a => ArrowChoice a where
arr :: (b -> c) -> a b c
(+++) :: a b c -> a b' c' -> a (Either b b') (Either c c')
In other words, the tensor product of your Cartesian closed category needn't be (,) exactly. Any tensor product you can come up with has a corresponding notion of strength, each of which would give you a corresponding variety of Arrow.
Notably, many profunctors are both Strong and Choice, so your Foo (which basically generalises Strong over a tensor product p) doesn't have a functional dependency.
The Control.Arrow module in base unfortunately muddles the hierarchy together a little bit (for example, their ArrowChoice has Arrow as a superclass).
Related
Bifunctors have a map function with this signature:
bimap :: (a -> b) -> (c -> d) -> p a c -> p b d
You could also have a map like this:
othermap :: ((a, c) -> (b, d)) -> p a c -> p b d
Types with this function are a strict subset of bifunctors (you can always define bimap using othermap but not vice versa). Is there a name for the second signature?
Follow-up: what about this intermediate function?
halfothermap :: ((a, c) -> b) -> (c -> d) -> p a c -> p b d
A type which is a Bifunctor need not have the same number of a values as b values. Consider, for example,
data TwoLists a b = TwoLists [a] [b]
It is easy to implement bimap, but othermap is a real problem, especially if one of the lists is empty.
othermap f (TwoLists [] (b:bs)) = TwoLists [] _
What can you do here? You need to call f to convert all the bs to a list of type [d], but you can only call that function if you have an a in hand.
Perhaps even worse is a type which doesn't really ever have b values at all:
data TaggedFunction k a b = TaggedFunction a (k -> b)
instance Bifunctor (TaggedFunction k) where
bimap f g (TaggedFunction a b) = TaggedFunction (f a) (g . b)
How can you implement othermap for this type? You could update the function, because you have an a in hand and will have a b by the time you need a d. But there's no way for you to replace the a with a c, because you can't get hold of a b to call othermap's function with.
So you can't put this function in Bifunctor. Perhaps you're asking, why not put this in a new class? I think leftroundabout is right that the class is too constraining to be useful. othermap can be defined only when you have the same number of as and bs in your structure, i.e. when your structure is some functor f wrapped around a tuple of type (a, b). For example, instead of TwoLists we could have defined
newtype PairList a b = PairList [(a, b)]
and that can have an othermap definition. But it would just be
othermap f (PairList vs) = PairList (fmap f vs)
Likewise instead of TaggedFunction we could have defined
newtype MultiFunction k a b = MultiFunction (k -> (a, b))
but the othermap definition is again just a wrapped call to fmap:
othermap f (MultiFunction g) = MultiFunction (fmap f g)
So perhaps the best way to imagine defining this abstraction would be as, not a typeclass function, but an ordinary function that operates over a type that captures this composition:
newtype TupleFunctor f a b = TupleFunctor (f (a, b))
othermap :: Functor f => ((a, b) -> (c, d))
-> TupleFunctor f a b -> TupleFunctor f c d
othermap f (TupleFunctor x) = TupleFunctor (fmap f x)
(Expanding on a comment by #glennsl...)
The type p a c "contains" both a and c.
But a function of type (a, c) -> (b, d) requires values of type a and c to be called, and a value of type p a c doesn't necessarily have both.
othermap #Either :: ((a, c) -> (b, d)) -> Either a c -> Either b d
othermap f (Left x) = ?
othermap f (Right y) = ?
There's no way for othermap to produce values of type b or d, because it can't call f in either case.
The type of the first argument implies a bifunctor that is isomorphic to (,), which is not true of all possible bifuntors. othermap is, in fact, strictly more specific than bimap. (There seems to be a contravariant relationship here that I won't try to make precise.)
I am learning from the "Free Applicative Functors". Surely, the question I am going to ask is kind of aside with respect to main idea of the paper, but still...
...on the page 6 there is an attempt to generalize Functor to MultiFunctor:
class Functor f ⇒ MultiFunctor f where
fmap0 :: a → f a
fmap1 :: (a → b) → f a → f b
fmap1 = fmap
fmap2 :: (a → b → c) → f a → f b → f c
...
I can not see how this definition is justified from the category theory's viewpoint: fmap2 seems to be just a bifunctor, i.e. a functor defined on a product category. By definition, product category is given by all possible ordered pairs of objects and morphisms are pairs as well, hence: fmap2 :: (a -> a', b -> b') -> (f a, f b) -> (f a', f b') looks and feels like more appropriate signature.
I can understand the way of thinking standing behing the (a -> b -> c) -> f a -> f b -> f c choice: it is just the most obvious way to take known (a -> b) -> f a -> f b signature and force it to work with binary functions, rather then unary. But isMultiFunctor (given by the definition above) actually a bi-/multifunctor in the sense that category theory expects it to be?
P.S. The reason why I am curious is that it seems like one can't get to the Applicative by generalizing Functor, though paper states that one can.
I think the category theory angle you are taking is wrong. There is a Bifunctor class (with a map of type (a -> b) -> (c -> d) -> f a c -> f b d) but that is not what this generalisation is. If one uncurries some functions then the signature of fmap2 looks like:
fmap2 :: ((a,b) -> c) -> (f a, f b) -> f c
And by considering fmap2 id, we see that what we are implementing is not a bifunctor but a cartesian functor (i.e. a monoidal functor between cartesian categories), with fmap2 id :: (f a, f b) -> f (a,b) being the natural transformation:
One can then get an applicative from this Multifunctor generalisation. Just change pure for fmap0 and (<*>) for fmap2 ($).
Let's start with the obvious: fmap0 is pure.
Here's one you made a mistake on: fmap2 is liftA2.
(bimap is very different - (a -> b) -> (c -> d) -> f a b -> f c d)
And if you go back to the definition of Applicative, you see that it has a default implementation of (<*>), which is liftA2 id, which allows you to define it in terms of pure and either liftA2 or (<*>).
So yes, that class is equivalent to Applicative.
I have the following definitions for a monoidal category class (Similar to the standard library, but providing inverses of the necessary natural isomorphisms):
class (Category r, Category s, Category t) => Bifunctor p r s t | p r -> s t, p s -> r t, p t -> r s where
bimap :: r a b -> s c d -> t (p a c) (p b d)
--
class (Bifunctor b k k k) => Associative k b where
associate :: k (b (b x y) z) (b x (b y z))
associateInv :: k (b x (b y z)) (b (b x y) z)
--
class (Bifunctor b k k k) => HasIdentity k b i | k b -> i
class (Associative k b, HasIdentity k b i) => Monoidal k b i | k b -> i where
idl :: k (b i a) a
idr :: k (b a i) a
idlInv :: k a (b i a)
idrInv :: k a (b a i)
--
The problem with composing morphisms in a monoidal category using (.) is that the objects may be associated differently. For instance Monoidal Hask (,) (), we might want to compose a morphism of type x -> ((a, b), c) with a morphism of type ((a, ()), (b, c)) -> y. To make the types fit, the natural isomorphism given by bimap idrInv id . associate has to be applied.
Does the Haskell type system enable an automatic way of determining an appropriate isomorphism, based on the desired domain and and codomain type? I can't figure out how to do it.
I figured it out, sort of. The basic idea is to use a multi-parameter type class with a normalizing function and its inverse as methods. The normalizer associates everything to the right, recursively. The type class needs an instance for each case of the recursion. Then, to convert from one way to associate stuff to another, just compose the normalizer for the type of the first morphism and the inverse normalizer for the type of the second morphism.
I will link code here as soon as I will have published it.
For my work with hxt I implemented the following function:
-- | Construction of a 8 argument arrow from a 8-ary function. Same
-- implementation as in #Control.Arrow.ArrowList.arr4#.
arr8 :: ArrowList a => (b1 -> b2 -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> c)
-> a (b1, (b2, (b3, (b4, (b5, (b6, (b7, b8))))))) c
arr8 f = arr ( \ ~(x1, ~(x2, ~(x3, ~(x4, ~(x5, ~(x6, ~(x7, x8)))))))
-> f x1 x2 x3 x4 x5 x6 x7 x8 )
As mentioned in the haddock comment the above function arr8 takes an 8-ary function and returns a 8 argument arrow. I use the function like this: (x1 &&& x2 &&& ... x8) >>> arr8 f whereby x1 to x8 are arrows.
My question: Is there a way to avoid the big tuple definition? Is there a more elegant implementation of arr8?
Info: I used the same code schema as in the function arr4 (see source code of arr4)
This works, though it depends on some quite deep and fragile typeclass magic. It also requires that we change the tuple structure to be a bit more regular. In particular, it should be a type-level linked list preferring (a, (b, (c, ()))) to (a, (b, c)).
{-# LANGUAGE TypeFamilies #-}
import Control.Arrow
-- We need to be able to refer to functions presented as tuples, generically.
-- This is not possible in any straightforward method, so we introduce a type
-- family which recursively computes the desired function type. In particular,
-- we can see that
--
-- Fun (a, (b, ())) r ~ a -> b -> r
type family Fun h r :: *
type instance Fun () r = r
type instance Fun (a, h) r = a -> Fun h r
-- Then, given our newfound function specification syntax we're now in
-- the proper form to give a recursive typeclass definition of what we're
-- after.
class Zup tup where
zup :: Fun tup r -> tup -> r
instance Zup () where
zup r () = r
-- Note that this recursive instance is simple enough to not require
-- UndecidableInstances, but normally techniques like this do. That isn't
-- a terrible thing, but if UI is used it's up to the author of the typeclass
-- and its instances to ensure that typechecking terminates.
instance Zup b => Zup (a, b) where
zup f ~(a, b) = zup (f a) b
arrTup :: (Arrow a, Zup b) => Fun b c -> a b c
arrTup = arr . zup
And now we can do
> zup (+) (1, (2, ()))
3
> :t arrTup (+)
arrTup (+)
:: (Num a1, Arrow a, Zup b n, Fun n b c ~ (a1 -> a1 -> a1)) =>
a b c
> arrTup (+) (1, (2, ()))
3
If you want to define the specific variants, they're all just arrTup.
arr8
:: Arrow arr
=> (a -> b -> c -> d -> e -> f -> g -> h -> r)
-> arr (a, (b, (c, (d, (e, (f, (g, (h, ())))))))) r
arr8 = arrTup
It's finally worth noting that if we define a lazy uncurry
uncurryL :: (a -> b -> c) -> (a, b) -> c
uncurryL f ~(a, b) = f a b
then we can write the recursive branch of Zup in a way that is illustrative to what's going on here
instance Zup b => Zup (a, b) where
zup f = uncurryL (zup . f)
My approach would be writing
arr8 f = arr (uncurry8 f)
I don't know if we can write a generic uncurryN n f function (probably not), but I can offer you a pointfree uncurry_n for each n in a systematic manner like so:
uncurry3 f = uncurry ($) . cross (uncurry . f) id
uncurry4 f = uncurry ($) . cross (uncurry3 . f) id
...
uncurry8 f = uncurry ($) . cross (uncurry7 . f) id
where
cross f g = pair (f . fst) (g . snd)
pair f g x = (f x, g x)
Some of the functions for working with Arrows are quite handy to use on pairs. But I can't understand how the types of these functions unify with a pair. In general, I find the types of the Arrow related functions to be quite confusing.
For example, we have first :: a b c -> a (b, d) (c, d), which means little to me. But it can be used to, say, increment the first number in a pair:
Prelude Control.Arrow> :t first (+1)
first (+1) :: (Num b) => (b, d) -> (b, d)
And
Prelude Control.Arrow> :t (&&&)
(&&&) :: (Arrow a) => a b c -> a b c' -> a b (c, c')
Prelude Control.Arrow> :t (pred &&& succ)
(pred &&& succ) :: (Enum b) => b -> (b, b)
Could someone please explain how this works?
There is an instance for Arrow (->). So
(&&&) :: (Arrow a) => a b c -> a b c' -> a b (c,c')
has the instantiation
(&&&) :: (->) b c -> (->) b c' -> (->) b (c,c')
or, written in more conventional notation,
(&&&) :: (b -> c) -> (b -> c') -> (b -> (c,c'))
The rest should follow from that.
I use the arrow functions (especially (***) and (&&&)) all the time on the (->) instance. My usage of those combinators for any other instance of Arrow is very rare. So whenever you see a b c, think "(generalized) function from b to c", which works for regular functions too.
The first arrow takes a normal arrow, and changes it to perform its operation on the first element in a tuple and outputs the result as an arrow
a b c -> a (b, d) (c, d)
a b c -- is the input arrow, an operation that maps type b to c
a (b, d) (c, d) -- is the output arrow, an operation that maps a tuple (b, d) to (c, d)
it uses d as a dummy for the unknown second type in the tuple
&&& takes two arrows that take the same input and creates an arrow that takes that input, duplicates it into a tuple and runs one of the arrows on each part of the tuple, returning the altered tuple.
for some solid tutorial, check out:
http://www.vex.net/~trebla/haskell/hxt-arrow/lesson-0.xhtml
I did this blog post not long ago about how to use Arrow functions on pure functions
http://blog.romanandreg.com/post/2755301358/on-how-haskells-are-just-might-just-be-function
I try to cover all the basic Arrow methods in a really simple and detailed fashion.
Cheers.