2015-03-03 45 views
6

我知道函數sequence可以處理[[1, 2], [3, 4]] -> [[1, 3], [1, 4], [2, 3], [2, 4]]問題。計算N-Ary(帶不同類型!!)Haskell中的笛卡爾積

但我認爲真正的笛卡兒產品應該處理([1, 2], ['a', 'b']) -> [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]問題,如果每個列表的類型不同,也不應該考慮外部元組的類型(&大小),那麼應該關心它。

所以,cartProd功能我想有一個類型是這樣的:([a1], [a2], [a3] ...) -> [(a1, a2, a3 ...)]

我知道有這裏的類型系統的一些問題。但有什麼辦法來實現這個cartProd的完美版本?

+0

@chi真的嗎?在我的解釋中,他要求組合,而不是拉鍊;即「liftA2(,)」等(用於列表)。 – phg 2015-03-03 13:56:21

+0

@phb你說得對。不過,如果我們可以使用嵌套對而不是元組,我們可以通過類型類和'liftA2(,)'來解決這個問題。使用元組很難,因爲沒有方便的方法從n元組移動到n + 1元組。 – chi 2015-03-03 14:11:06

+1

有趣的事實:GHC不支持超過62個元素的元組:https://downloads.haskell.org/~ghc/latest/docs/html/libraries/ghc-prim-0.3.1.0/src/GHC-Tuple。 HTML。所以肯定有一種方法可以手動實現所有'cartProdN'變體,最多62個;)。這就是說,你是否真的需要任意的笛卡爾產品? ''zipWith *'及其變種可能會停在'7'處,這可能是有原因的...... – Zeta 2015-03-03 14:18:35

回答

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']] 
+0

我認爲'HList'包可能已經提供了類似的東西,但我不確定它是否完全相同。編寫(或者提及,如果它已經存在)一個用於將'HList's轉換爲元組並且具有關聯的元組類型的類也是有意義的。 – dfeuer 2015-03-03 16:00:10

+0

@dfeuer:我在'hlist'中找到它,並且它非常相似(請參閱編輯)。 – 2015-03-03 16:25:32

2

使用模板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')] 
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'代替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'])