简体   繁体   English

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

[英]Calculate N-Ary (with different types !!) Cartesian Product in Haskell

I know that the function sequence can handle the [[1, 2], [3, 4]] -> [[1, 3], [1, 4], [2, 3], [2, 4]] problem.我知道函数sequence可以处理[[1, 2], [3, 4]] -> [[1, 3], [1, 4], [2, 3], [2, 4]]问题.

But I think the real cartesian product should handle the ([1, 2], ['a', 'b']) -> [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')] problem, and should care about neigher if the type of each list is different nor the outer tuple's type( & size).但我认为真正的笛卡尔积应该处理([1, 2], ['a', 'b']) -> [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]问题,如果每个列表的类型不同,或者外部元组的类型(& 大小)不同,则应该关心 neigher。

So, the cartProd function I want has a type like this: ([a1], [a2], [a3] ...) -> [(a1, a2, a3 ...)]所以,我想要的cartProd函数有这样的类型: ([a1], [a2], [a3] ...) -> [(a1, a2, a3 ...)]

I know there is some problem here with the type system.我知道这里的类型系统存在一些问题。 But is there any way to implement a perfect version of this cartProd ?但是有没有办法实现这个cartProd的完美版本?

The usual heterogeneous list can be used here:通常的异构列表可以在这里使用:

{-# 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

We usually write functions on HLists using classes, with one instance for Nil and another for the :> case.我们通常使用类在HLists上编写函数,一个实例用于Nil ,另一个用于:>情况。 However, it wouldn't be pretty to have a class for just a single use case (namely cartesian products here), so let's generalize the problem to applicative sequencing:然而,仅仅为一个用例(即这里的笛卡尔积)创建一个类并不是很好,所以让我们将问题概括为应用排序:

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

Note the use of ~ constraints in the instance definition.注意实例定义中~约束的使用。 It greatly helps type inference (along with the functional dependencies in the class declaration);它极大地帮助了类型推断(以及类声明中的函数依赖); the general idea is to move as much information as possible from the instance head to the constraints, because that lets GHC delay solving them until there is sufficient contextual information.总体思路是将尽可能多的信息从实例头部移动到约束,因为这会让 GHC 延迟解决它们,直到有足够的上下文信息。

Now cartesian products work out of the box:现在笛卡尔积开箱即用:

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

And we can also use hsequence with any Applicative :我们还可以将hsequence与任何Applicative一起使用:

> hsequence (Just "foo" :> Just () :> Just 10 :> Nil)
Just "foo" : () : 10 : Nil

EDIT: I found out (thanks dfeuer) that the same functionality is available from the existing hlist package:编辑:我发现(感谢 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']]

Using Template Haskell it is possible to achieve this.使用 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]

Then perhaps a little awkward to use, but that's the price of supporting any tuples:那么使用起来可能有点尴尬,但这是支持任何元组的代价:

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')]

I found a better solution myself, this solution is perfect for user, but it's implementation is sort of ugly (must create instance of every tuple, just like zip):我自己找到了一个更好的解决方案,这个解决方案非常适合用户,但它的实现有点丑陋(必须创建每个元组的实例,就像 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'])

We can also provide a better version of zip this way, so that we can use a single function name zip' instead of zip , zip3 , zip4 ...:我们还可以通过这种方式提供更好的 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