2015-03-03 14 views
6

Wiem, że funkcja sequence może obsłużyć problem [[1, 2], [3, 4]] -> [[1, 3], [1, 4], [2, 3], [2, 4]].Oblicz N-Ary (z różnymi typami !!) Produkt kartezjański w Haskell

Ale myślę, że prawdziwy produkt kartezjański powinien poradzić sobie z problemem ([1, 2], ['a', 'b']) -> [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')] i powinien zadbać o neighera, jeśli typ każdej listy jest inny, ani typu zewnętrznej krotki (rozmiar &).

Więc funkcja cartProd Chcę ma typ takiego: ([a1], [a2], [a3] ...) -> [(a1, a2, a3 ...)]

wiem, że jest tu z systemem typu jakiś problem. Ale czy jest jakiś sposób na wdrożenie idealnej wersji tego cartProd?

+0

@chi naprawdę? W mojej interpretacji prosi o kombinacje, a nie zamki; tj. 'liftA2 (,)' i tak dalej (dla list). – phg

+0

@phb Masz rację.Mimo to, gdybyśmy mogli użyć par zagnieżdżonych zamiast krotek, bylibyśmy w stanie rozwiązać to poprzez klasę typów i 'liftA2 (,)', jak proponujesz. Z krotkami jest to trudniejsze, ponieważ nie ma wygodnego sposobu na przejście z n-krotki na n + 1-krotkę. – chi

+1

Zabawny fakt: GHC nie obsługuje krotek dłuższych niż 62 wpisy: https://downloads.haskell.org/~ghc/latest/docs/html/libraries/ghc-prim-0.3.1.0/src/GHC-Tuple. html. Więc zdecydowanie istnieje sposób na wdrożenie wszystkich wariantów 'cartProdN' do 62 ręcznie;). Biorąc to pod uwagę, czy naprawdę potrzebujesz arbitralnych produktów kartezjańskich? Prawdopodobnie istnieje powód, dla którego 'zipWith *' i jego warianty zatrzymują się na '7' ... – Zeta

Odpowiedz

4

Zwykła lista heterogeniczne można stosować tutaj:

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

Zwykle napisać funkcje na HLists korzystających z zajęć, z jednej instancji do Nil i drugiego w przypadku :>. Jednak nie byłoby to dość mieć klasę tylko dla pojedynczego przypadku użycia (czyli iloczyn kartezjański tutaj), więc niech generalizować problemu do aplikacyjnej sekwencjonowania:

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 

Uwaga wykorzystanie ~ ograniczeń w instancji definicja. Bardzo pomaga w typowaniu wnioskowania (wraz z zależnościami funkcjonalnymi w deklaracji klasy); ogólną ideą jest przeniesienie jak największej ilości informacji z głowy instancji do ograniczeń, ponieważ to pozwala GHC opóźniać ich rozwiązywanie, dopóki nie będzie wystarczającej informacji kontekstowej.

produkty Teraz kartezjańskie pracy po wyjęciu z pudełka:

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

I możemy również użyć hsequence z dowolnym Applicative:

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

EDIT: znalazłem się (dzięki dfeuer), że taką samą funkcjonalność jest dostępny z istniejącego pakietu 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

Myślę, że pakiet' HList' może już oferować coś takiego, ale nie jestem pewien, czy to coś takiego. Mógłby również mieć sens napisanie (lub wspomnienie, jeśli już istnieje) klasy do konwersji 'HList's na krotki, z powiązanym typem krotki. – dfeuer

+0

@dfeuer: Znalazłem go w 'hlist' i jest bardzo podobny (patrz edycja). –

2

Za pomocą szablonu Haskella można to osiągnąć.

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

Potem może trochę niewygodne w użyciu, ale to cena wspierania wszelkich krotki:

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

znalazłem lepsze rozwiązanie siebie, jest to rozwiązanie idealne dla użytkowników, ale realizacja jest porządek brzydki (musi utworzyć instancję każdej krotki, podobnie jak zamek):

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

możemy również zapewnić lepszą wersję zip w ten sposób, tak, że możemy korzystać z jednego nazwę funkcji zip' zamiast 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']) 
Powiązane problemy