[英]Calculate N-Ary (with different types !!) Cartesian Product in 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')]
問題,如果每個列表的類型不同,或者外部元組的類型(& 大小)不同,則應該關心 neigher。
所以,我想要的cartProd
函數有這樣的類型: ([a1], [a2], [a3] ...) -> [(a1, a2, a3 ...)]
我知道這里的類型系統存在一些問題。 但是有沒有辦法實現這個cartProd
的完美版本?
通常的異構列表可以在這里使用:
{-# 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']]
使用 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')]
我自己找到了一個更好的解決方案,這個解決方案非常適合用戶,但它的實現有點丑陋(必須創建每個元組的實例,就像 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'])
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.