2015-03-03 14 views
6

So che la funzione sequence può gestire il problema [[1, 2], [3, 4]] -> [[1, 3], [1, 4], [2, 3], [2, 4]].Calcola N-Ary (con tipi diversi !!) Prodotto cartesiano in Haskell

ma penso che il vero prodotto cartesiano deve gestire il problema ([1, 2], ['a', 'b']) -> [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')], e dovrebbe preoccuparsi Neigher se il tipo di ciascuna lista è diversa, né il tipo di tupla esterno (& dimensioni).

Quindi, la funzione cartProd voglio ha un tipo come questo: ([a1], [a2], [a3] ...) -> [(a1, a2, a3 ...)]

so che c'è qualche problema qui con il sistema di tipi. Ma c'è un modo per implementare una versione perfetta di questo cartProd?

+0

@chi davvero? Nella mia interpretazione, chiede combinazioni, non cerniere; Ad esempio, 'liftA2 (,)' e così via (per le liste). – phg

+0

@phb Hai ragione.Tuttavia, se potessimo usare coppie nidificate invece di tuple, saremo in grado di risolverlo attraverso una classe di caratteri e 'liftA2 (,)', come proponete. Con le tuple è più difficile poiché non c'è un modo pratico per passare da una tupla n a una tupla + 1. – chi

+1

Un fatto divertente: GHC non supporta tuple più lunghe di 62 voci: https://downloads.haskell.org/~ghc/latest/docs/html/libraries/ghc-prim-0.3.1.0/src/GHC-Tuple. html. Quindi c'è sicuramente un modo per implementare tutte le varianti di 'cartProdN' fino a 62 a mano;). Detto questo, hai davvero bisogno di prodotti cartesiani arbitrari? C'è probabilmente un motivo per cui 'zipWith *' e le sue varianti si fermano a '7' ... – Zeta

risposta

4

Il solito elenco eterogeneo può essere utilizzato qui:

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

solitamente scrivono le funzioni di HLists utilizzando classi, con un'istanza per Nil e un altro per il caso :>. Tuttavia, non sarebbe abbastanza per avere una classe solo per un caso d'uso singola (cioè i prodotti cartesiani qui), quindi cerchiamo di generalizzare il problema al sequenziamento applicativa:

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 

Nota l'uso di ~ vincoli nell'istanza definizione. Aiuta molto tipo l'inferenza (insieme alle dipendenze funzionali nella dichiarazione di classe); l'idea generale è di spostare quante più informazioni possibili dalla testa dell'istanza ai vincoli, perché ciò consente a GHC di risolverli fino a quando non ci sono sufficienti informazioni contestuali.

prodotti Ora cartesiani lavorare fuori dalla scatola:

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

e possiamo anche utilizzare hsequence con qualsiasi Applicative:

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

EDIT: ho scoperto (grazie dfeuer) che la stessa funzionalità è disponibile dal pacchetto hlist esistente:

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

Penso che il pacchetto' HList' possa già offrire qualcosa del genere, ma non sono sicuro che sia lo stesso. Potrebbe anche avere senso scrivere (o menzionare, se esiste già) una classe per convertire 'HList's in tuple, con un tipo di tupla associato. – dfeuer

+0

@dfeuer: l'ho trovato in 'hlist' ed è praticamente lo stesso (vedi modifica). –

2

Utilizzando Template Haskell è possibile ottenere questo risultato.

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

Allora forse un po 'scomodo da usare, ma questo è il prezzo di sostenere eventuali tuple:

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

mi sono trovato una soluzione migliore, questa soluzione è perfetta per l'utente, ma è l'attuazione è sorta di brutto (deve creare un'istanza di ogni tupla, proprio come 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']) 

possiamo anche fornire una versione migliore di zip in questo modo, in modo da poter utilizzare un unico nome di funzione zip' invece di 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'])