类型变量条件下的Haskell实例

时间:2018-12-11 22:03:03

标签: haskell typeclass

从我的问题的具体实例开始,我们都知道(并且喜欢)Monad类型的类:

class ... => Monad m where
  return :: a -> m a
  (>>=)  :: m a -> (a -> m b) -> mb
  ...

请考虑以下可能的实例,其中我们使用nub修改标准列表/“不确定性”实例,以仅保留每个“结果”的一个副本:

type DistinctList a = DL { dL :: [a] }
instance Monad DistinctList where
  return = DL . return
  x >>= f = DL . nub $ (dL x) >>= (dL . f)

...发现错误了吗?问题是nub :: Eq a => [a] -> [a]以及x >>= f仅在条件f :: Eq b => a -> DistinctList b下定义,而编译器要求f :: a -> DistinctList b。我有什么办法可以继续吗?

后退一步,假设我有一个仅在某些条件下在参数类型的变量上定义的准实例。我知道这通常是不允许的,因为不能保证使用类型类编写的其他代码提供遵守条件的参数值。但是在某些情况下仍然可以执行此操作吗?如果可以,怎么办?

2 个答案:

答案 0 :(得分:6)

这是set-monad中应用的技术针对您的情况的改编。

请注意,必须存在一些“作弊”。该结构包括表示“返回”和“绑定”的额外值构造函数。这些充当需要运行的暂挂计算。 bcd 实例是Eq函数的一部分,而创建“ suspension”的构造函数是run免费的。

Eq

这是一个肮脏的技巧,可以提高效率,解决了下面有关绑定评估的问题。

{-# LANGUAGE GADTs #-}

import qualified Data.List            as L
import qualified Data.Functor         as F
import qualified Control.Applicative  as A
import Control.Monad

-- for reference, the bind operation to be implemented
-- bind operation requires Eq
dlbind :: Eq b => [a] -> (a -> [b]) -> [b] 
dlbind xs f = L.nub $ xs >>= f

-- data structure comes with incorporated return and bind 
-- `Prim xs` wraps a list into a DL   
data DL a where
  Prim   :: [a] -> DL a
  Return :: a -> DL a
  Bind   :: DL a -> (a -> DL b) -> DL b

-- converts a DL to a list 
run :: Eq a => DL a -> [a]
run (Prim xs)             = xs
run (Return x)            = [x]
run (Bind (Prim xs) f)    = L.nub $ concatMap (run . f) xs
run (Bind (Return x) f)   = run (f x)
run (Bind (Bind ma f) g)  = run (Bind ma (\a -> Bind (f a) g))

-- lifting of Eq and Show instance
-- Note: you probably should provide a different instance
--       one where eq doesn't depend on the position of the elements
--       otherwise you break functor laws (and everything else)
instance (Eq a) => Eq (DL a) where
  dxs == dys = run dxs == run dys

-- this "cheats", i.e. it will convert to lists in order to show. 
-- executing returns and binds in the process        
instance (Show a, Eq a) => Show (DL a) where
  show = show . run

-- uses the monad instance
instance F.Functor DL where
  fmap  = liftM 

-- uses the monad instance
instance A.Applicative DL where
  pure  = return
  (<*>) = ap

-- builds the DL using Return and Bind constructors
instance Monad DL where
  return = Return
  (>>=)  = Bind

-- examples with bind for a "normal list" and a "distinct list"
list  =  [1,2,3,4] >>= (\x ->  [x `mod` 2, x `mod` 3])   
dlist = (Prim [1,2,3,4]) >>= (\x -> Prim [x `mod` 2, x `mod` 3]) 

答案 1 :(得分:2)

如果您的类型可以是Monad,则它将需要在所有Monad或所有应用程序中参数化的函数中工作。但这不能,因为人们在他们的monad中存储了各种奇怪的东西。最值得注意的是,函数经常在应用上下文中作为值存储。例如,考虑:

pairs :: Applicative f => f a -> f b -> f (a, b)
pairs xs ys = (,) <$> xs <*> ys

即使ab都是Eq,为了将它们组合成(a, b)对,我们需要先将函数映射到{{1} },简要产生类型为xs的值。如果我们将f (b -> (a, b))设为您的DL monad,我们会发现这是行不通的,因为此函数类型没有f实例。

由于Eq被保证适用于所有Applicatives,并且不适用于您的类型,因此我们可以确保您的类型不是Applicative。而且由于所有Monad也是适用的,因此我们可以得出结论,您的类型不可能成为Monad的实例:它将违反法律。