2012-02-19 15 views
7

我覺得這一定是整潔,允許在Haskell任意鏈接比較,所以你可以做一些簡單的檢查範圍,如:哈斯克爾:鼓勵GHC來推斷正確的中間類型

x <= y < z 

而像

更復雜的東西
x /= y < z == a 

當上述兩者是語義上等同於

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

只是看到我f我可以使用語法。

所以我大部分的使用有幾個類型類的方式:

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

哪個編譯罰款,但並不完全似乎允許鏈接,由於中間類型的問題。

-- 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 = (<=) 

checkRange1checkRange2工作精細的,因爲它們既把一個約束在中間類型( 作爲第一比較,或者作爲一個參數到所述第二的結果)。

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

當我試圖讓編譯器推斷中間類型,但是,它又喊又叫。

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 

有沒有什麼辦法可以說服它應該使用Maybe a作爲 中間型a0 satisifying Booly a a0, ChainedOrd a0 a編譯器,因爲這是它知道的唯一實例?

失敗了,有沒有另一種方法可以讓任意比較鏈接工作?

+1

當你閱讀[這個問題]你有沒有想法(http://stackoverflow.com/questions/9284350/why-does-1-in-1-0-true-evaluate-to-false)?當我閱讀它時,我認爲這個功能是多麼有用,但是在Python無法提供的漂亮的靜態類型輸入中卻很危險。可惜在Haskell中它的工作方式並不像它的類型系統那麼好,它確實提供了這種安全性。 – leftaroundabout 2012-02-20 02:39:37

+0

@leftaroundabout其實,我從[本手冊中爲Julia語言]中得到了它(http://julialang.org/manual/mathematical-operations/#Numeric+Comparisons) – rampion 2012-02-20 04:19:42

回答

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 2012-02-19 19:28:03

+0

我喜歡這種方法 – rampion 2012-02-19 20:06:59

+0

我相信這種模式可以很容易地擴展到「ChainedOrd」 – 2012-02-19 20:11:06

4

有辦法告訴編譯器使用哪種類型,

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

,或者您可以使用ScopedTypeVariables,把類型變量進入活動範圍,把一個類型簽名上x <= y。但是你不能告訴編譯器使用它知道的唯一實例。編譯器在開放世界的假設下運行,其他實例可能被定義,並且代碼必須工作,如果它們已經進入範圍。所以,無論你做什麼會比

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

這是我會怎麼做更笨重:

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

用法:

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

比較複雜語言特性和額外的語法權力。我的版本除了Haskell 98以外不需要任何東西,但是必須以強制性的「開始」語法爲代價。 trinithis的解決方案需要一些語言擴展,它可以消除'Start',但它仍然需要'unChain'(與我的'和''相媲美)。可能有一種方法可以消除這種情況,但我並不知道這樣的事情,它肯定需要一些強大的擴展,甚至可能更具侵入性的Prelude替代品。 – 2012-02-19 20:47:25

+1

如果你使'Num a => Chain Bool'成爲'Num'的一個實例,你可以在某些情況下(比如你上面的例子)擺脫'Start'。 – rampion 2012-02-19 22:19:54

1

它給了我沒有休息,這couldn似乎沒有尷尬的終止/解包功能。我想出了允許純綴鏈表達式:

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

有了這樣的,你可以寫

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

不完全是美麗的,要麼,但比其他解決方案IMO更好的可讀性。必要的實例聲明的數量當然不是很好,但它是一勞永逸的,所以我認爲這並不算太壞。

+0

在這一點上,對於不同的開放,連續和關閉操作符,我沒有看到任何類型類的需要 - 這可以用'Maybe'和普通操作符完成:https://gist.github.com/1871093 – rampion 2012-02-20 20:08:46

+0

哦,我的,你完全正確。它最初應該只與兩個不同的操作員一起工作,如果沒有這些類型的家庭,我似乎無法得到這個工作 - 儘管如此,因爲它然後變成......或者我能嗎?等等...... – leftaroundabout 2012-02-20 20:45:32

+0

不......麻煩的是,關聯類型同義詞似乎無法在重疊實例中工作。 – leftaroundabout 2012-02-20 20:58:28