在Haskell中计算N-Ary(具有不同类型!!)笛卡尔积

时间:2015-03-03 13:45:42

标签: haskell template-haskell

我知道函数sequence可以处理[[1, 2], [3, 4]] -> [[1, 3], [1, 4], [2, 3], [2, 4]]问题。

但我认为真正的笛卡尔积应该处理([1, 2], ['a', 'b']) -> [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]问题,并且如果每个列表的类型不同,也不管外部元组的类型(& size),那么应该关心neigher。

因此,我想要的cartProd函数的类型如下:([a1], [a2], [a3] ...) -> [(a1, a2, a3 ...)]

我知道类型系统存在一些问题。但有没有办法实现这个cartProd的完美版本?

3 个答案:

答案 0 :(得分:4)

这里可以使用通常的异构列表:

{-# LANGUAGE
   UndecidableInstances, GADTs,
   TypeFamilies, MultiParamTypeClasses,
   FunctionalDependencies, DataKinds, TypeOperators,
   FlexibleInstances #-}

import Control.Applicative

data HList xs where
  Nil  :: HList '[]
  (:>) :: x -> HList xs -> HList (x ': xs)
infixr 5 :>

-- A Show instance, for illustrative purposes here. 
instance Show (HList '[]) where
  show _ = "Nil"

instance (Show x, Show (HList xs)) => Show (HList (x ': xs)) where
  show (x :> xs) = show x ++ " : " ++ show xs

我们通常使用类在HLists上编写函数,其中一个实例为Nil,另一个实例为:>。然而,仅仅针对一个用例(即笛卡尔积)这样的课程就不太可能了,所以让我们将问题归结为应用序列:

class Applicative f => HSequence f (xs :: [*]) (ys :: [*]) | xs -> ys, ys f -> xs where
  hsequence :: HList xs -> f (HList ys)

instance Applicative f => HSequence f '[] '[] where
  hsequence = pure

instance (Applicative g, HSequence f xs ys, y ~ x, f ~ g) =>
         HSequence g (f x ': xs) (y ': ys) where
  hsequence (fx :> fxs) = (:>) <$> fx <*> hsequence fxs

请注意在实例定义中使用~约束。它极大地帮助了类型推断(以及类声明中的函数依赖);一般的想法是尽可能多地从实例头移动到约束,因为这样可以让GHC延迟解决它们,直到有足够的上下文信息。

现在笛卡尔产品开箱即用:

> hsequence ([1, 2] :> "ab" :> Nil)
[1 : 'a' : Nil,1 : 'b' : Nil,2 : 'a' : Nil,2 : 'b' : Nil]

我们还可以将hsequence与任何Applicative

一起使用
> hsequence (Just "foo" :> Just () :> Just 10 :> Nil)
Just "foo" : () : 10 : Nil

编辑:我发现(感谢dfeuer)现有的hlist包提供了相同的功能:

import Data.HList.CommonMain

> hSequence ([3, 4] .*. "abc" .*. HNil)
[H[3, 'a'],H[3, 'b'],H[3, 'c'],H[4, 'a'],H[4, 'b'],H[4, 'c']]

答案 1 :(得分:2)

使用Template Haskell可以实现这一目标。

{-# LANGUAGE TemplateHaskell #-}
f :: ExpQ -> ExpQ
f ess = do es <- ess
           case es of
             (TupE e) -> return $ h e
             _ -> fail "f expects tuple of lists"
  where
    h ts = let ns = zipWith (\_ -> mkName . ('x':) . show) ts [0..]
           in CompE $ (zipWith (BindS . VarP) ns ts) ++
                      [NoBindS $ TupE $ map VarE ns]

然后使用可能有点尴尬,但这是支持任何元组的代价:

Prelude> take 7 $ $(f [| ([1..], [1..2], "ab") |] )
[(1,1,'a'),(1,1,'b'),(1,2,'a'),(1,2,'b'),(2,1,'a'),(2,1,'b'),(2,2,'a')]

答案 2 :(得分:0)

我自己找到了一个更好的解决方案,这个解决方案非常适合用户,但它的实现有点难看(必须创建每个元组的实例,就像zip一样):

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies #-}

class CartProd a b | a -> b where
    cartProd :: a -> b

instance CartProd ([a], [b]) [(a, b)] where
    cartProd (as, bs) = [(a, b) | a <- as, b <- bs]

instance CartProd ([a], [b], [c]) [(a, b, c)] where
    cartProd (as, bs, cs) = [(a, b, c) | a <- as, b <- bs, c <- cs]

c = cartProd (['a'..'c'], [0..2])
d = cartProd (['a'..'c'], [0..2], ['x'..'z'])

我们也可以通过这种方式提供更好的zip版本,这样我们就可以使用单个函数名zip'而不是zipzip3zip4 .. :

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies #-}

class Zip a b | a -> b where
    zip' :: a -> b

instance Zip ([a], [b]) [(a, b)] where
    zip' (as, bs) = zip as bs

instance Zip ([a], [b], [c]) [(a, b, c)] where
    zip' (as, bs, cs) = zip3 as bs cs

a = zip' (['a'..'z'], [0..])
b = zip' (['a'..'z'], [0..], ['x'..'z'])