2012-02-08 9 views
14

W Haskell, Czy istnieje "standardowa" biblioteka/pakiet do generowania enumeracji Random/Arbitrary?Haskell: "instancja (Enum a, Bounded a) => Losowo a" i "= Arbitralnie a"

Napisałem poniższy kod, ale nie mogę uwierzyć, że jestem pierwszą osobą, która ma tę potrzebę lub rozwiązała ją (i nie jestem pewien, czy moje rozwiązanie jest całkowicie poprawne). Mam również nadzieję, że istniejące rozwiązanie ma przy nim inne miłe funkcje.

Oto parę funkcji wybrać losową wartość od typu ENUM:

enumRandomR :: (RandomGen g, Enum e) => (e, e) -> g -> (e, g) 
enumRandomR (lo,hi) gen = 
    let (int, gen') = randomR (fromEnum lo, fromEnum hi) gen in (toEnum int, gen') 

enumRandom :: (RandomGen g, Enum e) => g -> (e, g) 
enumRandom gen = 
    let (int, gen') = random gen in (toEnum int, gen') 

i oto przypadki dla System.Random.Random i Test.QuickCheck.Arbitrary

{-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances #-} 

instance (Enum a, Bounded a) => Random a where 
    random = enumRandom 
    randomR = enumRandomR 

instance (Enum a, Bounded a) => Arbitrary a where 
    arbitrary = choose (minBound, maxBound) 

Oto przykład Bounded, Enum typ

data Dir = N | E | S | W 
    deriving (Show, Enum, Bounded) 

A oto test losowe/metody arbitralne

> import Test.QuickCheck 
> sample (arbitrary:: Gen Dir) 
N 
E 
N 
S 
N 
E 
W 
N 
N 
W 
W 

Nie jestem zachwycony, że moje rozwiązanie opiera się na tych rozszerzeniach:

{-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances #-}" 

ponieważ:

- Constraint is no smaller than the instance head 
    in the constraint: Enum a 
(Use -XUndecidableInstances to permit this) 

,

- Overlapping instances for Random Int 
    arising from a use of `randomR' 
Matching instances: 
    instance Random Int -- Defined in System.Random 
    instance (Enum a, Bounded a) => Random a 

, oraz

- Illegal instance declaration for `Random a' 
    (All instance types must be of the form (T a1 ... an) 
    where a1 ... an are *distinct type variables*, 
    and each type variable appears at most once in the instance head. 
    Use -XFlexibleInstances if you want to disable this.) 

Czy istnieje lepszy sposób? Czy moje rozwiązanie zawodzi w przypadku niektórych (bardziej "egzotycznych") typów ograniczonej enum niż mój prosty przykład?

+0

Przyjęto odpowiedź hammara na udzielenie obejścia. Wznowiono wszystkie trzy odpowiedzi, aby uzyskać dobrą radę. Dziękuję Ci! – misterbee

+0

Henning miał podobny pomysł na enumRandom: http://www.haskell.org/pipermail/libraries/2007-December/008725.html – misterbee

+0

Używanie 'FlexibleInstances', takich jak' FlexibleContexts' i 'MultiParamTypeClasses', nie jest czymś w rodzaju być niezadowolonym z. –

Odpowiedz

9

Standardowym sposobem obejścia tego problemu jest utworzenie opakowania typu newtype i dostarczenie w zamian jego wystąpień.

{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- avoid some boilerplate 

newtype Enum' a = Enum' a 
    deriving (Bounded, Enum, Show) 

instance (Enum a, Bounded a) => Random (Enum' a) where 
    random = enumRandom 
    randomR = enumRandomR 

instance (Enum a, Bounded a) => Arbitrary (Enum' a) where 
    arbitrary = choose (minBound, maxBound) 

Oczywiście, takie podejście wymaga dodatkowego opakowania i rozpakowaniu podczas korzystania z nowego rodzaju, ale do użytku z QuickCheck, że nie powinno być tak źle, jak to zwykle wystarczy wzór meczu raz na własność:

prop_foo (Enum' x) = ... -- use x as before here 
+0

Czy odnosisz się do mojego 'enumRandom' lub do biblioteki, której przeoczyłem? A nowy typ działa na niepożądanych rozszerzeniach GHC? Czy korzystanie z rozszerzeń może prowadzić do kłopotów? Imiona są przerażające, ale użycie wydaje się łagodne. – misterbee

+2

@misterbee: Tak, używam definicji z pytania. Żadne rozszerzenia nie są potrzebne dla tej techniki (używam 'GeneralizedNewtypeDeriving' do generowania instancji dla mnie, zawsze możesz po prostu napisać je samemu). Jeśli chodzi o rozszerzenia, "FlexibleInstances" jest bezpieczne, a najgorszą rzeczą, jaką może zrobić "UndecidableInstances", jest utworzenie pętli sprawdzania typu w nieskończoność, jeśli napiszesz coś głupio, na przykład 'instance Arbitrary a => Enum a'. 'OverlappingInstances' jest nieco gorszy, ale znowu tylko w czasie kompilacji. Dopiero kiedy dojdziesz do 'IncoherentInstances', możesz mieć kłopoty w czasie wykonywania. – hammar

7

Nie jest bezpiecznie zadeklarować wystąpienia tego typu dla dowolnego typu Enum. Powodem tego jest to, że nie można zagwarantować, że toEnum . fromEnum zachowuje się jak id. Weź przykład Enum dla przykładu: Double; funkcja fromEnum po prostu zwraca "skróconą" wartość całkowitą podwójnego. Te "bardziej egzotyczne" typy (jak je nazywacie) nie będą działać z twoim rozwiązaniem.

Dlatego generalnie warto utworzyć instancje Random dla konkretnych typów i całkowicie uniknąć takich ogólnych deklaracji.

Wymienione rozszerzenia są koniecznie wymagane, jeśli naprawdę chcesz zadeklarować wystąpienie, które zadeklarowałeś, ponieważ jest to podpis samej deklaracji instancji, która ich wymaga.

+0

Tak, tak jak dzisiaj patrzyłem na Enuma, byłem lekko zaskoczony, gdy dowiedziałem się, że nie zawsze Wyrzucane są Enums, ale to miało sens, ponieważ Haskell jest nieskończenie przyjazny z lenistwem i wszystkim. Byłem bardziej zaskoczony, że Double jest instancją, a potem obrażony, gdy zobaczyłem definicję. To nie są wasze wyliczenia Javy! – misterbee

6

Kolejny powód, dla którego nie należy sprawiać, by instancje były "uniwersalne": osoba, która chce częściej odzwierciedlać wartości "rzeczywistego świata", a zatem chce niestandardowej instancji Arbitrary z różnymi wagami.

(Powiedział, że użyłem i zdefiniowane funkcję pomocnika na piśmie, że Arbitrary wystąpienie w moim kodu tylko w celu uniknięcia konieczności powtarzania go dla każdego małego typu.)

+0

Czytam podobny komentarz do innego pytania (może jednego z twoich) o różnych użytkownikach o różnych preferencjach dla Arbitrary. Jednak preferencje te mogą się różnić nawet w konkretnym typie, więc Państwa obawy dotyczą ogólnie eksportu ogólnego, a nie tylko wersji "uniwersalnej". (Ale myślę, że wersja uniwersalna to o wiele szersza sieć do obsady). Rozumiem, dlaczego biblioteka ogólnego przeznaczenia nie powinna wykorzystywać tej techniki, ale mamy także OverlappingInstances do ratowania specjalizacji ... Myślę. – misterbee

+2

@misterbee Nie jestem fanem rzeczy, które wymagają użycia 'OverlappingInstances', ponieważ wydaje mi się to trochę delikatne; lepiej nie polegać na takiej funkcjonalności IMHO. – ivanm

8

QuickCheck eksportuje funkcję

arbitraryBoundedEnum :: (Bounded a, Enum a) => Gen a 

Ta funkcja może być rozumiana jako "standardowa".

Powiązane problemy