2017-02-24 47 views
5

oczywiście wytwarzania iloczyn kartezjański heterogenicznych listach mogą być wykonane w różnych sposób w Haskell, takich jak:iloczyn kartezjański heterogenicznych list

[(x,y) | x <- [1,2,3], y <- [4,5,6]] 

lub

(,) <$> [1,2,3] <*> [4,5,6] 

ale co ja chce się funkcję tak:

heteroCartesian :: 
    (a1, a2, ... , an) -> 
    (b1, b2, ... , bn) -> 
    ((a1,b1), (a1,b2), ... , (a1,bn), (a2,b1), (a2,b2), ... , (a2,bn), (an,b1), ... ,(an,b2), ... , (an,bn)) 

Więc mogę zrobić coś takiego:

Nie mam nic przeciwko używaniu krotek lub czegoś innego, ale muszę zachować informacje o typie, takie jak powyżej.

Powodem, dla którego tego chcę, jest tworzenie przypadków testowych. Mam wiele funkcji powiedzieć n i wartości m. Ostatecznie zamapuję funkcję nad tymi, która redukuje je wszystkie do tego samego typu (Test), ale do tego momentu istnieje kilka różnych typów testów testowych, które chcę wykonać (nie jest to tak proste, ponieważ niektóre funkcje mogą tylko podjąć ograniczone podzbiory wartości).

Tak więc naturalnie dobrze byłoby mieć inne funkcje na tych heterogenicznych listach, na przykład na przykład w rodzaju map.

Spojrzałem na HList, ale nie został zaktualizowany w zeszłym roku i nieco, i nie byłem pewien, czy to było najbardziej odpowiednie narzędzie w każdym razie.

Odpowiedz

2

Sposób, aby rozwiązać ten problem, jest za pomocą szablon Haskell za to:

import Control.Monad(replicateM) 
import Language.Haskell.TH.Syntax(newName,Pat(TupP,VarP),Exp(LamE,TupE,VarE)) 

heteroCartesian m n = do 
    as <- replicateM m $ newName "a" 
    bs <- replicateM n $ newName "b" 
    return $ LamE [TupP (map VarP as),TupP (map VarP bs)] $ TupE $ [TupE [VarE ai,VarE bi] | ai <- as, bi <- bs] 

Teraz w innym pliku, można użyć funkcji:

{-# LANGUAGE TemplateHaskell #-} 

heteroCartesian23 = $(heteroCartesian 2 3) 

W takim przypadku heteroCartesian23 będzie mieć typ heteroCartesian23 :: (a1,a2) -> (b1,b2,b3) -> ((a1,b1),(a1,b2),(a1,b3),(a2,b1),(a2,b2),(a2,b3)).

Albo można go używać w ghci:

$ ghci -XTemplateHaskell library.hs 
GHCi, version 7.10.3: http://www.haskell.org/ghc/ :? for help 
[1 of 1] Compiling Main    (ha.hs, interpreted) 
Ok, modules loaded: Main. 
*Main> :t $(heteroCartesian 3 4) 
$(heteroCartesian 3 4) 
    :: (t, t1, t5) 
    -> (t2, t3, t4, t6) 
    -> ((t, t2), 
     (t, t3), 
     (t, t4), 
     (t, t6), 
     (t1, t2), 
     (t1, t3), 
     (t1, t4), 
     (t1, t6), 
     (t5, t2), 
     (t5, t3), 
     (t5, t4), 
     (t5, t6)) 
+0

Zauważ, że to dno dość szybko - Krotki GHC są ograniczone do [62 elementów] (https://github.com/ghc/ghc/blob/master/libraries/ghc-prim/GHC/Tuple.hs # L167-L168) (jeśli policzę poprawnie). – Alec

+0

@Alec: tak, chciałem tylko dostarczyć rozwiązanie, które używa wbudowanych typów Haskella. –

4

Wydaje HList rzeczywiście nieco zgniły kawałek. Nic jednak nie powstrzymuje nas przed przetoczeniem naszego własnego HList! W rzeczywistości możemy również w dużym stopniu polegać na singletons dla naszych operacji na poziomie listy typów. Po pierwsze, niektóre import:

{-# LANGUAGE DataKinds, TypeOperators, GADTs,TypeFamilies, UndecidableInstances, PolyKinds, FlexibleInstances #-} 

import Data.Singletons 
import Data.Promotion.Prelude.List 

Wtedy rzeczywista definicja HList (prostszy niż jeden pakiet używa tej nazwy ze względów opisanych here i here).

data HList (l :: [*]) where 
    HNil :: HList '[] 
    HCons :: x -> HList xs -> HList (x ': xs) 

-- Notice we are using `:++` from singletons 
append :: HList xs -> HList ys -> HList (xs :++ ys) 
append HNil xs = xs 
append (x `HCons` xs) ys = x `HCons` (xs `append` ys) 

-- Notice we are using `Map` and `TyCon1` from singletons. Bow before the magic 
-- of type level HOFs. ;) 
addTuple :: z -> HList xs -> HList (Map (TyCon1 ((,) z)) xs) 
addTuple _ HNil = HNil 
addTuple x (y `HCons` ys) = (x,y) `HCons` addTuple x ys 

-- These instances aren't needed, but they let us check the output of our code 
instance (Show x, Show (HList xs)) => Show (HList (x ': xs)) where 
    show (x `HCons` xs) = show x ++ " " ++ show xs 
instance Show (HList '[]) where 
    show HNil = "" 

Wreszcie docieramy do samego produktu kartezjańskiego:

type family Cartesian (ys :: [*]) (xs :: [*]) :: [*] where 
    Cartesian '[] xs = '[] 
    Cartesian (y ': ys) xs = Map (TyCon1 ((,) y)) xs :++ Cartesian ys xs 

cartesian :: HList xs -> HList ys -> HList (xs `Cartesian` ys) 
cartesian HNil _ = HNil 
cartesian (y `HCons` ys) xs = addTuple y xs `append` cartesian ys xs 

które możemy testować działa:

ghci> h1 = HCons True $ HCons LT $ HCons() $ HCons (1 :: Int) HNil 
ghci> h2 = HCons() $ HCons "hello" $ HCons 'a' HNil 
ghci> h1 `cartesian` h2 
(True,()) (True,"hello") (True,'a') (LT,()) (LT,"hello") (LT,'a') ((),()) ((),"hello") ((),'a') (1,()) (1,"hello") (1,'a') 

Ze wszystkim, co powiedział, nie jestem pewien, to jest naprawdę warty dla testów. Zasadniczo oczekuję, że testy będą prostsze i łatwiejsze do odczytania niż testowany przeze mnie kod. I HList nie jest moim pomysłem na prosty test. Ale każdemu jego własnemu. :)