我知道函数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
的完美版本?
答案 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'
而不是zip
,zip3
,zip4
.. :
{-# 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'])