Обычный гетерогенный список может быть использован здесь:
{-# 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
EDIT: Я узнал (спасибо 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']]
@chi на самом деле? В моей интерпретации он просит комбинации, а не молнии; то есть 'liftA2 (,)' и т. д. (для списков). – phg
@phb Вы правы.Тем не менее, если бы мы могли использовать вложенные пары вместо кортежей, мы могли бы решить это через класс типов и 'liftA2 (,)', как вы предлагаете. С кортежами это сложнее, поскольку нет удобного способа перехода от n-кортежа к n + 1-кортежу. – chi
Забавный факт: GHC не поддерживает кортежи длиной более 62 записей: https://downloads.haskell.org/~ghc/latest/docs/html/libraries/ghc-prim-0.3.1.0/src/GHC-Tuple. HTML. Таким образом, есть определенно способ реализовать все варианты cartFordN до 62 вручную;). Сказано ли вам, что вам нужны произвольные декартовы продукты? Вероятно, есть причина, по которой 'zipWith *' и ее варианты останавливаются на '7' ... – Zeta