2011-06-25 12 views
38

powiedzieć, że chcemy zdefiniować rekord Atrybut tak:Czy istnieje typ "Any" w haskell?

data Attribute = Attribute {name :: String, value :: Any}

To nie jest poprawny kod Haskell oczywiście. Ale czy istnieje typ "Any", który w zasadzie mówi, że każdy typ będzie robił? Czy może użyć zmiennej typu tylko w taki sposób?

data Attribute a = Attribute {name :: String, value :: a}

+5

Sprawdź w "GHC.Prim. Any". –

+0

Jeśli chcesz, aby 'name' miał naprawdę dowolny typ, powinieneś użyć zmiennej typu. Ale: czy jesteś pewien, że "imię" osoby powinno mieć jakikolwiek rodzaj? Prawdopodobnie (na przykład) wartość 'Int' jako' name' lub 'Bool'-value jako nazwa nie brzmi !? – phynfo

+0

@phynfo, próbka, której użyłem była hipotetyczna. Zmieniłem to po prostu na bardziej ogólny. – edwardw

Odpowiedz

65

Ogólnie mówiąc, typy Any nie są zbyt użyteczne. Zastanów się: Jeśli utworzysz listę polimorficzną, która może pomieścić cokolwiek, co możesz zrobić z typami na liście? Odpowiedź jest oczywiście niczym - nie macie gwarancji, że istnieje jakakolwiek operacja wspólna dla tych elementów.

Co jeden zwykle zrobić jest:

  1. Zastosowanie GADTs zrobić listę, która może zawierać elementy konkretnego typeclass, jak w:

    data FooWrap where 
        FooWrap :: Foo a => a -> FooWrap 
    type FooList = [FooWrap] 
    

    Z takim podejściem, to don znasz konkretny typ elementów, ale wiesz, że można nimi manipulować przy użyciu elementów typograficznych z serii Foo.

  2. Utwórz typ przełączania pomiędzy poszczególnych rodzajów betonu zawartych w liście:

    data FooElem = ElemFoo Foo | ElemBar Bar 
    type FooList = [FooElem] 
    

    ten można łączyć z podejściem 1 utworzyć listę, która może pomieścić elementy, które są jednym z ustalonym zestawem typeklasses.

  3. W niektórych przypadkach może to być pomocne, aby zbudować listę funkcji manipulacji:

    type FooList = [Int -> IO()] 
    

    Jest to przydatne dla rzeczy takich jak systemy powiadamiania o zdarzeniach. W momencie dodawania elementu do listy, wiążesz go w funkcji, która wykonuje wszelkie operacje, które później będziesz chciał zrobić.

  4. Użyj Data.Dynamic (niezalecane!) Jako cheat. Nie gwarantuje to jednak, że można w ogóle manipulować konkretnym elementem, dlatego preferowane powinny być powyższe podejścia.

+2

+1 za sugerowanie "właściwych" podejść. –

+0

Dzięki za dokładną odpowiedź @bdonlan. Tego właśnie szukam. – edwardw

+16

N.B. - Zauważ, że typy 'Any' są bardzo użyteczne w * języku z podtypem * - typowym przykładem jest klasa' Object' na szczycie hierarchii dziedziczenia OOP. Haskell nie ma prawdziwego pojęcia podtypy, więc pojęcie to jest w większości bezużyteczne. –

12

Jest typem Dynamic z Data.Dynamic który może pomieścić niczego (no, nic Typeable). Ale rzadko jest to właściwy sposób. Jaki problem próbujesz rozwiązać?

+0

Witam @augustss, jeden jest to, że szukam odpowiednika typu any() Erlanga w celu przetłumaczenia niektórych kodu. Drugim jest to, że zastanawiam się, jak zadeklarować listę polimorficzną, która może pomieścić cokolwiek. Następnie '[Data.Dynamic.Dynamic]'? – edwardw

+0

Polecam, aby nie używać 'Dynamic', zamiast tego należy utworzyć klasę typu o właściwościach wymaganych przez elementy, a następnie użyć typu egzystencjalnego, aby ukryć rzeczywisty typ elementów. Mogę rozwinąć, jeśli chcesz. – augustss

11

To brzmi jak dość podstawowe pytanie, więc udzielę jeszcze bardziej podstawowej odpowiedzi niż ktokolwiek inny. Oto, co prawie zawsze jest dobrym rozwiązaniem:

data Attribute a = Attribute { name :: String, value :: a } 

Następnie, jeśli chcesz atrybut, który otacza się Int, że atrybut musiałby typ Attribute Int lub atrybut, który owija Bool musiałby typ Attribute Bool, etc.Możesz utworzyć te atrybuty z wartościami dowolnego typu; na przykład możemy napisać:

testAttr = Attribute { name = "this is only a test", value = Node 3 [] } 

, aby utworzyć wartość typu Attribute (Tree Int).

20

Dodawanie do odpowiedzi bdonlan za: Zamiast GADTs, można również użyć existential types:

{-# LANGUAGE ExistentialQuantification #-} 

class Foo a where 
    foo :: a -> a 

data AnyFoo = forall a. Foo a => AnyFoo a 

instance Foo AnyFoo where 
    foo (AnyFoo a) = AnyFoo $ foo a 

mapFoo :: [AnyFoo] -> [AnyFoo] 
mapFoo = map foo 

To jest w zasadzie równoważne rozwiązania GADT bdonlan, ale nie narzuca wybór struktury danych na ciebie - możesz używać Map zamiast listy, na przykład:

import qualified Data.Map as M 

mFoo :: M.Map String AnyFoo 
mFoo = M.fromList [("a", AnyFoo SomeFoo), ("b", AnyFoo SomeBar)] 

data AnyFoo = forall a. Foo a => AnyFoo a bit może być także napisany w notacji GADT jak:

data AnyFoo where 
    AnyFoo :: Foo a => a -> AnyFoo 
+1

Tak, to jest sposób. – augustss

2

Jeśli dane muszą w końcu być konkretnym typem, można użyć opcji Zamienny z GADT. Ponieważ jako konsument, interesuje Cię tylko typ danych, który musisz zużyć.

{-# LANGUAGE GADTs #-} 
import Data.Convertible 

data Conv b where 
    Conv :: a -> (a -> b) -> Conv b 
    Chain :: Conv b -> (b -> c) -> Conv c 

unconv :: (Conv b) -> b 
unconv (Conv a f) = f a 
unconv (Chain c f) = f $ unconv c 

conv :: Convertible a b => a -> Conv b 
conv a = (Conv a convert) 

totype :: Convertible b c => Conv b -> Conv c 
totype a = Chain a convert 

W tym celu nie jest bardzo trudno wyprowadzać instancje funktora, komonady i monady. Mogę je opublikować, jeśli jesteś zainteresowany.

Powiązane problemy