簡體   English   中英

在 Haskell 中計算 N-Ary(不同類型!!)笛卡爾積

[英]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'而不是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'])

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM