在Haskell类型族中输入歧义 [英] Type ambiguity in Haskell type families

查看:109
本文介绍了在Haskell类型族中输入歧义的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图将以下类放在一起及其实例 TrivialDomain

  { - #LANGUAGE TypeFamilies# - } 

data Transition = Transition

class Domain d其中
类型设置d
类型引擎d :: * - > *

top :: Engine d(Set d)

- ...
complement :: Set d - >引擎d(Set d)
exclude :: Set d - >设置d - >引擎d(设置d)
- ...

数据TrivialDomain = TrivialDomain

实例域TrivialDomain其中
类型设置TrivialDomain = [Int]
类型引擎TrivialDomain = IO

top = return [0..10]

- ...
补充a =顶部>> ; =(flip exclude)a
exclude ab = return $ filter(not。(`elem` b))a
- ...

但我不断收到以下错误,我无法理解

  test3.hs:25:21:
无法将类型'Engine d0'与'IO'匹配
类型变量'd0'不明确
期望类型:IO(Set d0 )
实际类型:Engine d0(Set d0)
在'(> =)'的第一个参数中,即'top'
在表达式:top>> =(flip exclude)a
test3.hs:25:35:
无法将类型'Set d1'与'[Int]'相匹配
类型变量'd1'不明确
Exp受影响类型:设置d0 - > [Int] - > IO [Int]
实际类型:设置d1 - >设置d1 - >引擎d1(Set d1)
在'flip'的第一个参数中,即'exclude'
在'(> =)'的第二个参数中,即'(flip exclude)

我期望引擎d(Set d)在实例声明中解析为 IO [Int] ,但似乎并非如此。至少GHC不这么认为。我缺少什么?

解决方案

在您的情况下,关联类型不足以推断方法的类型。

您有类 Domain d Set 以及引擎 d 相关联。这意味着只要我们的程序中有一个已知的 d 实例的已知 d ,GHC就可以解析设置d 引擎d 。但是这不会倒退。 GHC无法解决 d Domain 实例存在 Set d 或者 Engine d ,因为完全可能有不同的 Domain 设置引擎类型。由于您的类方法只提及 Set 引擎,所以 $ b Domain d 永远不能从方法使用中推断出来。



您可以根据自己的目标做几件事情。首先,您可以使 d 取决于集合引擎

  class域设置引擎,其中
类型DomainOf设置引擎:: *
- ...

更一般地说, FunctionalDependencies 为您提供更大的灵活性来强化类型间的依赖关系。例如,你可以特别声明每个 Set 只有一个 d ,这足以恢复好类型推理:

  class Domain d set engine | d  - >设置引擎,设置 - > d其中

top ::引擎集
complement :: set - >引擎设置
exclude :: set - >设置 - >引擎集

数据TrivialDomain = TrivialDomain

实例Domain TrivialDomain [Int] IO其中

top = return [0..10]

complement a = top>> =(flip exclude)a

exclude ab = return $ filter(not。(`elem` b))a

最后,如果您想使用原始类,则必须添加 Proxy d 参数传递给你的方法,以便使实例和相关类型可解析:

  import Data.Proxy 

数据转换=转换

类域d其中
类型设置d
类型引擎d :: * - > *

top :: Proxy d - > Engine d(Set d)
complement :: Proxy d - >设置d - > Engine d(Set d)
exclude :: Proxy d - >设置d - >设置d - >引擎d(集合d)

数据TrivialDomain = TrivialDomain

实例域TrivialDomain其中
类型设置TrivialDomain = [Int]
类型引擎TrivialDomain = IO

顶部_ =返回[0..10]

补全da =顶部d>> =(翻转(不包括d))a
排除轻触= return $ filter(not。(`elem` b))a

这里, code>代理服务器d 是指定你想要使用哪个实例。然而,这意味着我们必须在每种方法用法上编写 top(Proxy :: Proxy d)(类似与其他方法),这是相当繁重。使用GHC 8,我们可以省略代理 s并使用 TypeApplications 代替:

  { - #language TypeApplications,TypeFamilies# } 

- ...

实例域TrivialDomain其中
类型设置TrivialDomain = [Int]
类型引擎TrivialDomain = IO

top = return [0..10]

complement a = top @TrivialDomain>> =(flip(排除@TrivialDomain))a
exclude ab = return $过滤器(不是。(`elem` b))a


I am trying put together the following class Domain and its instance TrivialDomain

{-# LANGUAGE TypeFamilies #-}

data Transition = Transition

class Domain d where
    type Set d
    type Engine d :: * -> *

    top :: Engine d (Set d)

    -- ...
    complement :: Set d -> Engine d (Set d)
    exclude    :: Set d -> Set d -> Engine d (Set d)
    -- ...

data TrivialDomain = TrivialDomain

instance Domain TrivialDomain where
    type Set TrivialDomain = [Int]
    type Engine TrivialDomain = IO

    top = return [0..10]

    -- ...
    complement a = top >>= (flip exclude) a
    exclude a b  = return $ filter (not . (`elem` b)) a
    -- ...

but I keep getting the following error which I fail to understand

test3.hs:25:21:
    Couldn't match type ‘Engine d0’ with ‘IO’
    The type variable ‘d0’ is ambiguous
    Expected type: IO (Set d0)
      Actual type: Engine d0 (Set d0)
    In the first argument of ‘(>>=)’, namely ‘top’
    In the expression: top >>= (flip exclude) a
test3.hs:25:35:
    Couldn't match type ‘Set d1’ with ‘[Int]’
    The type variable ‘d1’ is ambiguous
    Expected type: Set d0 -> [Int] -> IO [Int]
      Actual type: Set d1 -> Set d1 -> Engine d1 (Set d1)
    In the first argument of ‘flip’, namely ‘exclude’
    In the second argument of ‘(>>=)’, namely ‘(flip exclude) a’

I would expect Engine d (Set d) to resolve to IO [Int] in the instance declaration, which does not seem to be the case. At least GHC does not think so. What am I missing?

解决方案

In your case, associated types aren't enough to infer the types of the methods.

You have class Domain d, and Set and Engine are associated to d. This means that whenever there is a known d in our program with a known Domain d instance, GHC can resolve Set d and Engine d. But this doesn't work backwards. GHC can't resolve d or a Domain instance from the presence of a Set d or an Engine d, since it's entirely possible that there are different Domain instances with the same Set and Engine types.

Since your class methods only mention Set and Engine, Domain d can never be inferred from method use.

You could do a couple of things depending on your goals.

First, you could make d depend on Set and Engine:

class Domain set engine where
  type DomainOf set engine :: *
  -- ...

More generally, FunctionalDependencies gives you much more flexibility to enforce dependencies between types. For example, you can specifically declare that there is only one d for each Set, which is enough to recover good type inference:

class Domain d set engine | d -> set engine, set -> d where

    top        :: engine set
    complement :: set -> engine set
    exclude    :: set -> set -> engine set

data TrivialDomain = TrivialDomain

instance Domain TrivialDomain [Int] IO where

    top = return [0..10]

    complement a = top >>= (flip exclude) a

    exclude a b  = return $ filter (not . (`elem` b)) a

Finally, if you want to use your original class, you have to add Proxy d parameters to your methods, in order to make the instance and the associated types resolvable:

import Data.Proxy

data Transition = Transition

class Domain d where
    type Set d
    type Engine d :: * -> *

    top        :: Proxy d -> Engine d (Set d)
    complement :: Proxy d -> Set d -> Engine d (Set d)
    exclude    :: Proxy d -> Set d -> Set d -> Engine d (Set d)

data TrivialDomain = TrivialDomain

instance Domain TrivialDomain where
    type Set TrivialDomain = [Int]
    type Engine TrivialDomain = IO

    top _ = return [0..10]

    complement d a = top d >>= (flip (exclude d)) a
    exclude d a b  = return $ filter (not . (`elem` b)) a

Here, the purpose of Proxy d is to specify exactly which instance you want to use.

However, this means we have to write top (Proxy :: Proxy d) on each method usage (similarly with other methods), which is rather onerous. With GHC 8 we can omit Proxys and use TypeApplications instead:

{-# language TypeApplications, TypeFamilies #-}

-- ...

instance Domain TrivialDomain where
    type Set TrivialDomain = [Int]
    type Engine TrivialDomain = IO

    top = return [0..10]

    complement a = top @TrivialDomain >>= (flip (exclude @TrivialDomain)) a
    exclude a b = return $ filter (not . (`elem` b)) a

这篇关于在Haskell类型族中输入歧义的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆