Haskell:鼓励GHC推断出正确的中间类型

时间:2012-02-19 17:28:44

标签: haskell ghc type-inference

我认为在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 = (<=)

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满足Booly a a0, ChainedOrd a0 a,因为这是它知道的唯一实例?

如果不这样做,还有另一种方法可以进行任意比较链接工作吗?

4 个答案:

答案 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比其他解决方案更具可读性。必要的实例声明的数量当然不是很好,但它是一劳永逸的,所以我认为这不是太糟糕。