Haskell中的功能依赖

gnu*_*nce 22 haskell typeclass

我试图绕过功能依赖,但我自己并没有得到任何结果.在"Monad Transformers Step by Step"一文中,作者给出了这两个类型定义:

class (Monad m) => MonadError e m | m -> e where
    throwError :: e -> m a
    catchError :: m a -> (e -> m a) -> m a

class (Monad m) => MonadReader r m | m -> r where
    ask :: m r
    local :: (r -> r) -> m a -> m a
Run Code Online (Sandbox Code Playgroud)

根据我对在网上发现的一些材料的理解,这意味着类型变量e由确定m.我只是不明白这意味着什么.它是如何确定的?任何人最初都可以用最少的理论解决问题,然后将更重的理论内容联系起来吗?

谢谢

Dan*_*zer 40

如果您有多参数类型类,默认情况下,类型变量是独立考虑的.所以当类型推断器试图找出哪个实例时

class Foo a b
Run Code Online (Sandbox Code Playgroud)

要选择,它必须确定ab独立,然后去查看是否存在实例.通过功能依赖,我们可以减少此搜索.当我们做类似的事情

class Foo a b | a -> b
Run Code Online (Sandbox Code Playgroud)

我们说"你看,如果你确定什么a是,那么有一个独特的b,这样Foo a b存在的,所以不要费事去推断b,只是去查找实例和类型检查,认为".这让类型推理器更加有效,并有助于在许多地方进行推理.

例如,这对返回类型多态性特别有用

class Foo a b c where
  bar :: a -> b -> c
Run Code Online (Sandbox Code Playgroud)

现在没有办法推断出来

  bar (bar "foo" 'c') 1
Run Code Online (Sandbox Code Playgroud)

因为我们无法确定c.即使我们只为String和编写了一个实例Char,我们也必须假设某人可能/将会出现并稍后添加另一个实例.如果没有fundeps,我们必须实际指定返回类型,这很烦人.但是我们可以写

class Foo a b c | a b -> c where
  bar :: a -> b -> c
Run Code Online (Sandbox Code Playgroud)

现在很容易看出返回类型bar "foo" 'c'是独特的,因此可推断.


Cir*_*dec 27

这意味着类型系统始终能够从类型中确定类型(er)m.

让我们自己做一个例子:

class KnowsA a b | b -> a where
    known :: b -> a
Run Code Online (Sandbox Code Playgroud)

我们要始终能够确定ab

data Example1 = Example1 deriving (Show)

instance KnowsA Int Example1 where
    known = const 1 
Run Code Online (Sandbox Code Playgroud)

该型系统知道这个instnace,只要它有一个Example1,它的类型是aInt.怎么知道的?它不允许我们使用另一种类型的另一个实例.

如果我们补充

instance KnowsA [Char] Example1 where
    known = const "one"
Run Code Online (Sandbox Code Playgroud)

我们收到一个错误:

    Functional dependencies conflict between instance declarations:
      instance KnowsA Int Example1
      instance KnowsA [Char] Example1
Run Code Online (Sandbox Code Playgroud)

但是a如果我们有不同的类型,我们可以添加另一个不同类型的实例b

data Example2 = Example2 deriving (Show)

instance KnowsA [Char] Example2 where
    known = const "two"

main = do
    print . known $ Example1
    print . known $ Example2
Run Code Online (Sandbox Code Playgroud)

这可预测地输出

1
"two"
Run Code Online (Sandbox Code Playgroud)


J. *_*son 23

当Haskell试图解决MonadError e m它时,默认情况下,将两个em参数一起搜索,以寻找碰巧有实例的任何一对.如果我们没有e在约束本身之外的类型签名中的任何地方显示,则这尤其困难

unitError :: MonadError e m => m ()
unitError = return ()
Run Code Online (Sandbox Code Playgroud)

功能依赖说,一旦我们解决了m,就只有一个e有效.这让上面的片段编译,因为它向Haskell保证,那里有足够的信息让它具有非模糊类型.

如果没有功能依赖,Haskell会抱怨它unitError含糊不清,因为它可能对任何类型都有效,e而且我们无法知道这种类型是什么 - 信息以某种方式蒸发到空气中.


对于MonadError函数依赖,通常意味着monad本身由错误类型参数化.例如,这是一个实例

instance MonadError e (Either e) where
  thowError = Left
  catchError (Left e)  f = f e
  catchError (Right a) _ = Right a
Run Code Online (Sandbox Code Playgroud)

在哪里e ~ em ~ Either e我们看到m确实唯一地确定了一个e有效的单一.


功能依赖性也几乎等同于类型族.类型家庭有时更容易消化.例如,这是一个MonadError类,TypeFamilies样式

{-# LANGUAGE TypeFamilies #-}

class MonadError m where
  type Err m
  throwError :: Err m -> m a
  catchError :: m a -> (Err m -> m a) -> m a

instance MonadError (Either e) where
  type Err (Either e) = e
  throwError = Left
  catchError (Left e) f  = f e
  catchError (Right a) _ = Right a
Run Code Online (Sandbox Code Playgroud)

在这里,Err是一个类型函数,它采用m其特定的错误类型e和概念,如果有任何一个e等于Err m任何等于任何m来自我们对函数的理解.

  • +1类型系列.我认为功能依赖更受欢迎,不仅因为它们更老,而且因为我们可以继续使用小写字母来表示我们想象的类型变量. (2认同)