2012-02-19 21 views
7

Myślałam być schludny, aby umożliwić dowolną przykuty porównania w Haskell, więc można zrobić kilka prostych czynności sprawdzających zakres jak:Haskell: Zachęcanie GHC wywnioskować prawidłowy typ pośredni

x <= y < z 

bardziej skomplikowane rzeczy, jak

x /= y < z == a 

Jeżeli powyższe dwa są semantycznie równoważne

x <= y && y < z 
x /= y && y < z && z == a 

Wystarczy gdyż ja f Mogę uzyskać składnię do działania.

Więc mam większość drogi tam przy użyciu kilku klas typu:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-} 
module ChainedOrd where 

import Prelude hiding ((<), (<=), (>), (>=), (==), (/=)) 

class Booly v a where 
    truthy :: v -> a 
    falsy :: v -> a 

instance Booly a Bool where 
    truthy = const True 
    falsy = const False 

instance Booly a (Maybe a) where 
    truthy = Just 
    falsy = const Nothing 

class ChainedOrd a b where 
    (<),(>),(<=),(>=),(==),(/=) :: (Booly b c) => a -> b -> c 

infixl 4 < 
infixl 4 > 
infixl 4 <= 
infixl 4 >= 
infixl 4 == 
infixl 4 /= 

instance Ord a => ChainedOrd a a where 
    x < y  = case compare x y of LT -> truthy y ; _ -> falsy y 
    x > y  = case compare x y of GT -> truthy y ; _ -> falsy y 
    x <= y = case compare x y of GT -> falsy y ; _ -> truthy y 
    x >= y = case compare x y of LT -> falsy y ; _ -> truthy y 
    x == y = case compare x y of EQ -> truthy y ; _ -> falsy y 
    x /= y = case compare x y of EQ -> falsy y ; _ -> truthy y 

instance Ord a => ChainedOrd (Maybe a) a where 
    Just x < y  = case compare x y of LT -> truthy y ; _ -> falsy y 
    Nothing < y = falsy y 
    Just x > y  = case compare x y of GT -> truthy y ; _ -> falsy y 
    Nothing > y = falsy y 
    Just x <= y = case compare x y of GT -> falsy y ; _ -> truthy y 
    Nothing <= y = falsy y 
    Just x >= y = case compare x y of LT -> falsy y ; _ -> truthy y 
    Nothing >= y = falsy y 
    Just x == y = case compare x y of EQ -> truthy y ; _ -> falsy y 
    Nothing == y = falsy y 
    Just x /= y = case compare x y of EQ -> falsy y ; _ -> truthy y 
    Nothing /= y = falsy y 

które zestawia w porządku, ale nie dość wydaje się dopuszczać łańcuchowym, ze względu na problem typów pośrednich. ponieważ zarówno umieścić ograniczenie typu pośredniego (albo wyniku pierwszego porównania, lub jako drugi argument)

-- works 
checkRange1 :: Ord a => a -> a -> a -> Bool 
checkRange1 x y z = x `lem` y <= z 
    where lem :: Ord a => a -> a -> Maybe a 
     lem = (<=) 

-- works 
checkRange2 :: Ord a => a -> a -> a -> Bool 
checkRange2 x y z = (x <= y) `leb` z 
    where leb :: Ord a => Maybe a -> a -> Bool 
     leb = (<=) 

checkRange1 i checkRange2 pracy porządku.

-- error 
checkRange3 :: Ord a => a -> a -> a -> Bool 
checkRange3 x y z = (x <= y) <= z 

Kiedy próbuję pozwolić kompilatorowi wywnioskować typ pośredni, szczeka na mnie.

ChainedOrd.hs:64:30: 
    Ambiguous type variable `a0' in the constraints: 
     (ChainedOrd a0 a) arising from a use of `<=' 
         at ChainedOrd.hs:64:30-31 
     (Booly a a0) arising from a use of `<=' at ChainedOrd.hs:64:24-25 
    Probable fix: add a type signature that fixes these type variable(s) 
    In the expression: (x <= y) <= z 
    In an equation for `checkRange3': checkRange3 x y z = (x <= y) <= z 

Czy jest jakiś sposób mogę przekonać kompilator, że powinien on używać Maybe a jak typu pośredniego a0 satisifying Booly a a0, ChainedOrd a0 a, ponieważ jest to jedyny przypadek, że wie o?

Jeśli to nie możliwe, czy istnieje inny sposób, w jaki mogę wykonać dowolne łańcuchowe porównywanie?

+1

Czy wpadłeś na pomysł, czytając [to pytanie] (http://stackoverflow.com/questions/9284350/why-does-1-in-1-0-true-e-valuate-to-false)? Kiedy go przeczytałem, pomyślałem, jak użyteczna jest ta funkcja, ale niebezpieczna w nieobecności ładnego silnego pisania statycznego, jakiego Python nie może zaoferować. Szkoda, że ​​nie działa tak dobrze w Haskell z jego systemem typu, który zapewnia to bezpieczeństwo. – leftaroundabout

+0

@leftaroundabout faktycznie, mam to z [tego podręcznika dla języka Julii] (http://julialang.org/manual/mathematical-operations/#Numeric+Comparisons) – rampion

Odpowiedz

5
infixl 4 ==? 

class ChainedEq a b where 
    (==?) :: a -> b -> Maybe b 

instance (Eq a) => ChainedEq (Maybe a) a where 
    x ==? y = if x == Just y 
    then x 
    else Nothing 

instance (Eq a) => ChainedEq a a where 
    x ==? y = if x == y 
    then Just x 
    else Nothing 

unChain :: Maybe a -> Bool 
unChain Nothing = False 
unChain (Just _) = True 

test :: Int -> Int -> Int -> Bool 
test x y z = unChain $ x ==? y ==? z 
+0

'unChain' =' isJust' – rampion

+0

Podoba mi się to podejście – rampion

+0

Wierzę, że ten wzorzec mógłby być łatwo rozszerzony na "ChainedOrd" –

4

Istnieją sposoby, aby poinformować kompilator, który do korzystania typ,

checkRange4 x y z = ((x <= y) `asTypeOf` Just x) <= z 

lub użyć ScopedTypeVariables doprowadzić typ zmiennej w zakresie i umieścić podpis typu na x <= y. Ale nie można powiedzieć kompilatorowi, aby korzystał z jedynych wystąpień, o których wie. Kompilator działa na założeniu otwartego świata, inne instancje mogą być zdefiniowane, a kod musi działać, jeśli są i wchodzą w zakres. Więc cokolwiek zrobisz będzie bardziej niezgrabne niż

checkRange5 x y z = x <= y && y <= z 
3

Oto w jaki sposób to zrobić:

{-# LANGUAGE NoMonomorphismRestriction #-} 

data Chain v e = Link { evaluation :: e 
         , val :: v 
         , next :: Chain v e 
         } 
       | Start { val :: v } 


liftChain :: (a -> a -> b) -> Chain a b -> a -> Chain a b 
liftChain f ch x = Link { evaluation = val ch `f` x, val = x, next = ch } 

(.<) = liftChain (<) 
(.>) = liftChain (>) 
(.<=) = liftChain (<=) 
(.>=) = liftChain (>=) 
(.==) = liftChain (==) 

toList :: Chain v e -> [v] 
toList (Start v) = [v] 
toList (Link _ v n) = v : toList n 

toList' :: Chain v e -> [e] 
toList' (Start _) = [] 
toList' (Link e _ n) = e : toList' n 

and' :: Chain v Bool -> Bool 
and' = and . toList' 

Zastosowanie:

ghci> and' $ Start 3 .< 4 .< 7 .== 7 .< 9 .>= 0 .== (2-2) 
True 
+0

Warto porównać kompromisy między złożonymi funkcjami językowymi i dodatkowa moc syntaktyczna. Moja wersja nie wymaga niczego poza Haskell 98, ale przychodzi za cenę obowiązkowej składni "Start". Rozwiązanie Trinithis wymaga kilku rozszerzeń językowych, co pozwala mu wyeliminować 'Start', ale nadal wymaga' unChain' (porównywalnego z moim 'i''). Być może istnieje sposób, aby to wyeliminować, ale nie jestem tego świadomy i na pewno wymagałoby to potężnych rozszerzeń i być może jeszcze bardziej natrętnych zamienników Prelude. –

+1

Możesz pozbyć się 'Start' w niektórych sytuacjach (np. Powyższy przykład), jeśli sprawisz, że' Num a => Chain a Bool' będzie instancją 'Num'. – rampion

1

on nie dał mi resztę, że to mogłem Wydaje się, że można je wyrazić bez kłopotliwych funkcji kończenia/rozpakowywania.Co wymyśliłem w celu umożliwienia wyrażenia czysto Infix łańcuchu:

{-# LANGUAGE MultiParamTypeClasses  #-} 
{-# LANGUAGE FlexibleInstances   #-} 
{-# LANGUAGE FlexibleContexts   #-} 
{-# LANGUAGE TypeFamilies    #-} 

module ChainedComp where 

infixl 4 ==.. , .==. , ==? 

data Comp1Chain a = Sofar1OK a | Already1Failed 
data Comp2Chain a = Sofar2OK a | Already2Failed 
data Comp3Chain a = Sofar3OK a | Already3Failed 
-- ... 

(==..) :: (Eq a) => a -> a -> Comp1Chain a 
x==..y | x==y  = Sofar1OK y 
     | otherwise = Already1Failed 

class ChainableComp c where 
    type AppendElem c :: * 
    type ChainAfterAppend c :: * 
    (.==.) :: c -> AppendElem c -> ChainAfterAppend c 
    (==?) :: c -> AppendElem c -> Bool 


instance (Eq a) => ChainableComp (Comp1Chain a) where 
    type AppendElem (Comp1Chain a) = a 
    type ChainAfterAppend (Comp1Chain a) = Comp2Chain a 
    chn.==.y | (Sofar1OK x)<-chn, x==y = Sofar2OK x 
      | otherwise    = Already2Failed 
    chn==?y | (Sofar1OK x)<-chn, x==y = True 
      | otherwise    = False 
instance (Eq a) => ChainableComp (Comp2Chain a) where 
    type AppendElem (Comp2Chain a) = a 
    type ChainAfterAppend (Comp2Chain a) = Comp3Chain a 
    chn.==.y | (Sofar2OK x)<-chn, x==y = Sofar3OK x 
      | otherwise    = Already3Failed 
    chn==?y | (Sofar2OK x)<-chn, x==y = True 
      | otherwise    = False 
-- ... 

A z tym, można napisać

*ChainedComp> 7 ==..7.==.7==? 7 
True 
*ChainedComp> 7 ==..7.==.6==? 7 
False 
*ChainedComp> 5 ==..5.==.5.==.4.==.5.==.5==? 5 
False 

Niezupełnie piękny, albo, ale IMO lepiej czytelne niż innych rozwiązań. Ilość niezbędnych deklaracji instancji nie jest oczywiście taka przyjemna, ale jest raz na zawsze, więc sądzę, że nie jest tak źle.

+0

W tym momencie, z różnymi operatorami otwierającymi, kontynuującymi i zamykającymi, nie widzę potrzeby korzystania z czcionek typowych - można to zrobić za pomocą 'Maybe' i zwykłych operatorów: https://gist.github.com/1871093 – rampion

+0

Och, mój, masz całkowitą rację. Pierwotnie miał działać tylko z dwoma różnymi operatorami i nie mogłem sprawić, by działało to bez rodzin tego typu - ani z nimi, jak się wtedy okazało ... czy mogę? czekaj ... – leftaroundabout

+0

Nie ... problem polega na tym, że synonimy skojarzonego typu nie mogą działać w nakładających się instancjach. – leftaroundabout

Powiązane problemy