Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic variant of bi f a b = (f a, f b)

Is there any type-safe way to write a function

bi f a b = (f a, f b)

such that it would be possible to use it like this:

x1 :: (Integer, Char)
x1 = bi head [2,3] "45"

x2 :: (Integer, Char)
x2 = bi fst (2,'3') ('4',5)

x3 :: (Integer, Double)
x3 = bi (1+) 2 3.45

? In rank-n-types examples there are always something much simpler like

g :: (forall a. a -> a) -> a -> a -> (a, a)
g f a b = (f a, f b)
like image 678
aemxdp Avatar asked May 27 '12 06:05

aemxdp


2 Answers

{-# LANGUAGE TemplateHaskell #-}

bi f = [| \a b -> ($f a, $f b)|]

 

ghci> :set -XTemplateHaskell 
ghci> $(bi [|head|]) [2,3] "45" 
(2,'4')

;)

like image 198
FunctorSalad Avatar answered Sep 30 '22 22:09

FunctorSalad


Yes, though not in Haskell. But the higher-order polymorphic lambda calculus (aka System F-omega) is more general:

bi : forall m n a b. (forall a. m a -> n a) -> m a -> m b -> (n a, n b)
bi {m} {n} {a} {b} f x y = (f {a} x, f {b} y)

x1 : (Integer, Char)
x1 = bi {\a. List a} {\a. a} {Integer} {Char} head [2,3] "45"

x2 : (Integer, Char)
x2 = bi {\a . exists b. (a, b)} {\a. a} {Integer} {Char} (\{a}. \p. unpack<b,x>=p in fst {a} {b} x) (pack<Char, (2,'3')>) (pack<Integer, ('4',5)>)

x3 : (Integer, Double)
x3 = bi {\a. a} {\a. a} {Integer} {Double} (1+) 2 3.45

Here, I write f {T} for explicit type application and assume a library typed respectively. Something like \a. a is a type-level lambda. The x2 example is more intricate, because it also needs existential types to locally "forget" the other bit of polymorphism in the arguments.

You can actually simulate this in Haskell by defining a newtype or datatype for every different m or n you instantiate with, and pass appropriately wrapped functions f that add and remove constructors accordingly. But obviously, that's no fun at all.

Edit: I should point out that this still isn't a fully general solution. For example, I can't see how you could type

swap (x,y) = (y,x)
x4 = bi swap (3, "hi") (True, 3.1)

even in System F-omega. The problem is that the swap function is more polymorphic than bi allows, and unlike with x2, the other polymorphic dimension is not forgotten in the result, so the existential trick does not work. It seems that you would need kind polymorphism to allow that one (so that the argument to bi can be polymorphic over a varying number of types).

like image 22
Andreas Rossberg Avatar answered Sep 30 '22 21:09

Andreas Rossberg