我认为在Haskell中允许任意链式比较是很好的,所以你可以进行简单的范围检查,如:
x <= y < z
更复杂的东西,比如
x /= y < z == a
上述两个在语义上等同于
x <= y && y < z
x /= y && y < z && z == a
看看我是否可以使用该语法。
所以我在那里使用了几个类型类的大部分方式:
{-# 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 = (<=)
checkRange1
和checkRange2
工作正常,因为它们都对中间类型设置了约束(或者
作为第一次比较的结果,或作为第二次比较的参数。
-- 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
满足Booly a a0, ChainedOrd a0 a
,因为这是它知道的唯一实例?
如果不这样做,还有另一种方法可以进行任意比较链接工作吗?
答案 0 :(得分: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
答案 1 :(得分:4)
有一些方法可以告诉编译器使用哪种类型,
checkRange4 x y z = ((x <= y) `asTypeOf` Just x) <= z
或者您可以使用ScopedTypeVariables
,将类型变量放入范围并在x <= y
上添加类型签名。但是你不能告诉编译器使用它知道的唯一实例。编译器在开放世界的假设下运行,可以定义其他实例,并且如果代码已经进入范围,则代码必须工作。所以无论你做什么都会比
checkRange5 x y z = x <= y && y <= z
答案 2 :(得分: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
答案 3 :(得分:1)
如果没有笨拙的终止/解包功能,这似乎无法表达。我想出了什么允许纯粹的中缀链式表达式:
{-# 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比其他解决方案更具可读性。必要的实例声明的数量当然不是很好,但它是一劳永逸的,所以我认为这不是太糟糕。