在具有约束的多态元组上无法匹配 [英] cannot match on polymorphic tuples with constraints

查看:66
本文介绍了在具有约束的多态元组上无法匹配的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对Haskell还是比较陌生,如果真的很明显,请原谅我.

I'm relatively new to haskell so forgive me if this is really obvious.

基本上我有两个Bool,我想根据它们选择3种不同功能的实现.在两个布尔值相等的情况下(例如都为True或均为False),这些功能应该什么都不做.如果一个或另一个Bool为True,则存在不同的实现方式.

Basically I have two Bool and based on them I want to choose the implementation of 3 different functions. In the case that both bools are equal (e.g. both True or both False) the functions should do nothing. Then there are different implementation if one or the other Bool is True.

这些函数涉及约束,例如,第一个函数对参数具有Ord或Bounded约束.第二个函数对参数有Num约束.

These function involve constraints so for instance the first function has an Ord or Bounded constraint on the parameters. The second function has a Num constraint on the parameters.

我遇到的问题是我不知道如何使用这种结构来检查类型检查器.参见下面的一个最小示例,当我对结果进行模式匹配时会抱怨:

The problem I am having is that I have no clue how to make the type checker oke with this construct. See below for a minimal example that complains when I pattern match on the result:

f :: (Ord a, Bounded a) => a -> a -> a
f a b = if a > b then maxBound else minBound

g :: (Ord a, Bounded a) => a -> a -> a
g a b = if a > b then minBound else maxBound

a = True
b = False

test
  | a == b = (const, const, const)
  | a      = (f, (-), (+))
  | b      = (g, (+), (-))

(resF, _, _) = test

(_, resG, _) = test -- error  Could not deduce (Ord b0) arising from a use of ‘test’
                    -- from the context: Num b
                    -- Same error occurs for the last value in the tuple.

我不确定约束最大的函数resF是否完全可以分配给变量,但是resG抱怨...

I'm not sure how the function with the most constraints resF is completely fine with being assigned to a variable but resG complains...

感谢您的帮助!

推荐答案

这里的问题是您有歧义类型.首先,让我们检查GHC推断的test的类型签名.我不久前发现的一个巧妙技巧是将test :: _添加到您的程序中,并让GHC在错误消息中为我们提供其推断的类型:

The problem here is that you have an ambiguous type. Firstly, let’s check the type signature of test as inferred by GHC. A neat trick I discovered a while ago is to add test :: _ to your program and let GHC give us its inferred type in an error message:

so.hs:13:9: error:
    • Found type wildcard ‘_’
        standing for ‘(b0 -> b0 -> b0, Integer -> Integer -> Integer,
                       Integer -> Integer -> Integer)’
      Where: ‘b0’ is an ambiguous type variable
      To use the inferred type, enable PartialTypeSignatures
    • In the type signature: test :: _
   |
13 | test :: _
   |         ^

因此,GHC推断的test类型为(b0 -> b0 -> b0, Integer -> Integer -> Integer, Integer -> Integer -> Integer)(尽管应该存在GHC出于某种原因而忽略的其他(Ord b0, Bounded b0)约束).现在,让我们看一下resFresG:

So the type of test as inferred by GHC is (b0 -> b0 -> b0, Integer -> Integer -> Integer, Integer -> Integer -> Integer) (although there should be an additional (Ord b0, Bounded b0) constraint which GHC leaves out for some reason). Now, let’s look at resF and resG:

(resF, _, _) = test
(_, resG, _) = test

resF的定义中,b0类型参数最终也将在该表达式之外使用(在resF :: b0 -> b0 -> b0类型中),因此它没有歧义.但是,在resG的定义中,b0仅在该表达式内使用,因此它可以是任何!由于GHC绝对无法确定该声明中的b0是什么,因此将其标记为模糊的,从而产生此错误.

In the definition of resF, the b0 type parameter ends up being used outside that expression as well (in the type of resF :: b0 -> b0 -> b0), so it isn’t ambiguous. However, in the definition of resG, b0 is only ever used inside that expression, so it could be anything! Since GHC has absolutely no way to determine what b0 is in that declaration, it is marked as ambiguous, producing this error.

(如果这还不够清楚,那么我们的想法是,如果您的表达式中的类型变量含糊不清,并且您在=的左侧引用了该变量,则该变量将变得毫无歧义,因为变量在表达式外使用.我知道这不是很好的解释;我对Haskell的这一方面不太满意,因此,如果有人有更好的解释,请发表评论!)

(If that wasn’t clear enough, the idea is that if you have an expression with an ambiguous type variable, and you refer to this variable on the left side of the =, then it becomes disambiguated, as the variable is being used outside the expression. I know this isn’t a very good explanation; I’m not too good with this area of Haskell myself, so if anyone else has a better explanation please comment!)

那么如何解决这个问题?一种方法是简单地组合resFresG,因此b0最终会在test之外使用:

So how can this problem be solved? One way is simply to combine resF and resG, so b0 does end up being used outside test:

(resF, resG, _) = test

另一种方法是添加类型签名 restricting b0:

Another way is to add a type signature restricting b0:

(_, resG, _) = test :: (() -> () -> (), Integer -> Integer -> Integer, Integer -> Integer -> Integer)

这是解决模棱两可的类型错误的最常见方法,因为它可以在所有情况下使用.在这种情况下,它的时间可能会更长,但是您应该可以在比上述技术更多的情况下使用它,该技术实际上仅在此处起作用.

This is the most common way of getting around ambiguous type errors, as it will work in all circumstances. In this case it happens to be much longer, but you should be able to use it in more situations than the above technique, which really only works here.

但是,这里仍然有一些细微之处.首先,为什么GHC报告第二和第三字段使用Integer,而不允许任何类型?这是由于同态限制导致的,在某些情况下,该限制会自动专门化类型变量.您可以通过添加类型签名来解决此问题:

However, there’s still a few subtle points here. Firstly, why does GHC report that the second and third fields use Integer, instead of allowing any type? This is due to the monomorphism restriction, which in certain situations specialises type variables automatically. You can get around this by adding a type signature:

test :: (Ord a, Bounded a, Num b, Num c) => (a -> a -> a, b -> b -> b, c -> c -> c)

这就是为什么在所有函数中添加类型签名被认为是一种好习惯的原因!

This is why it is considered good practise to add type signatures to all functions!

当然,这具有使第二和第三字段也使用类型变量的缺点.因此,它们也容易变得模棱两可.您可以通过绑定所有三个字段以允许这些类型变量在声明之外的某种意义上传播"来解决此问题:

Of course, this has the disadvantage of making the second and third fields use type variables as well; hence, they become prone to ambiguous types as well. You can get around this by binding all three fields to allow these type variables to ‘propagate’ in a sense outside that declaration:

(resF, resG, resH) = test

(请注意,传播"是我自己的术语,而不是公认的Haskell术语!)

(Note that ‘propagate’ is my own term, not a recognised Haskell term!)

编辑:因此,该策略无效.该答案的末尾给出了更多详细信息,因为它有点详细.

So, it turns out this strategy doesn’t work. More details are given at the end of this answer, since it’s a bit detailed.

或者您可以再次添加类型签名以限制bc:

Or you can add a type signature again to restrict b and c:

(resF, _, _) = test :: (Ord a, Bounded a) => (a -> a -> a, Int -> Int -> Int, Int -> Int -> Int)


我想说明的另一点是test本身的定义.在Haskell中,像在这里一样使用全局变量是很不常见的.通常,您可以将它们作为参数添加到test,然后像这样从外部将它们传递进来:


The other point I wanted to make is with the definition of test itself. In Haskell, it is very uncommon to use global variables as you do here; usually you would add them as parameters to test, then pass them in from outside like this:

test :: (Ord a, Bounded a, Num b, Num c)
     => Bool
     -> Bool
     -> (a -> a -> a, b -> b -> b, c -> c -> c)
test a b =
  | a == b = (const, const, const)
  | a      = (f, (-), (+))
  | b      = (g, (+), (-))

(resF, resG, resH) = test True False

以这种方式进行操作可以更大程度地重用代码,因为test现在可以在不同的布尔条件下多次使用.

Doing it this way allows for greater reuse of code, as test can now be used multiple times with different boolean conditions.

我不确定以上情况是否正确,但是我完全错过了一个重要因素.如果您具有类型(Constr1 a, Constr2 b) => (a, b),则整个元组都取决于Constr1 a Constr2 b!因此,您无法轻松地删除一个类型变量来隔离另一个类型变量. (有关更多详细信息,请参见个很好的答案.)

I’m not sure the above is incorrect as such, but there’s an important factor which I completely missed. If you have something of type (Constr1 a, Constr2 b) => (a, b), the entire tuple depends on both Constr1 a and Constr2 b! So you can’t easily remove one type variable to isolate the other. (More details in this excellent answer.)

但是,有解决方案!在test中,每个字段彼此独立.因此,从理论上讲,应该可以将类型更改为后续代码:

However, there is a solution! In test, each field is independent of each other. So it should theoretically be possible to change the type to the followign:

test :: Bool -> Bool
     -> ( forall a. (Ord a, Bouded a) => a -> a -> a
        , forall b. Num b => b -> b -> b
        , forall c. Num c => c -> c -> c
        )
test a b =
  | a == b = (const, const, const)
  | a      = (f, (-), (+))
  | b      = (g, (+), (-))

从某种意义上说,现在所有约束都被拉入"了元组,因此您现在可以隔离一个字段.

Now all the constraints have in a sense been ‘pulled into’ the tuple, so you can now isolate one field.

当然,没有比这更简单的事情了,如果您尝试执行上述操作,则会遇到有关强制性多态性"的错误.解决方案将字段包装为辅助数据类型:

Of course, nothing is ever quite as simple as that, and if you try running the above you run into an error about ‘impredicative polymorphism’. The solutions is wrapping the fields in auxilliary data types:

newtype Wrapper1 = Wrapper1 (forall a. (Ord a, Bounded a) => a -> a -> a)
newtype Wrapper2 = Wrapper2 (forall b. Num b => b -> b -> b)

test :: (Wrapper1, Wrapper2, Wrapper2)
test
  | a == b = (Wrapper1 const, Wrapper2 const, Wrapper2 const)
  | a      = (Wrapper1 f    , Wrapper2 (-)  , Wrapper2 (+))
  | b      = (Wrapper1 g    , Wrapper2 (+)  , Wrapper2 (-))

(Wrapper1 resF, Wrapper2 resG, Wrapper2 resH) = test

(您还需要在文件的开头添加{-# LANGUAGE RankNTypes #-}才能进行编译.)

(You’ll also need to add {-# LANGUAGE RankNTypes #-} to the beginning of the file to get this to compile.)

这-终于! —类型检查成功.

And this — finally! — typechecks successfully.


进一步的好处是,该方法甚至消除了模棱两可的类型错误.以下代码也可以成功进行类型检查:


As a further upside, it turns out that this method even gets rid of ambiguous type errors. The following code typechecks successfully as well:

test
  | a == b = (Wrapper1 const, Wrapper2 const, Wrapper2 const)
  | a      = (Wrapper1 f    , Wrapper2 (-)  , Wrapper2 (+))
  | b      = (Wrapper1 g    , Wrapper2 (+)  , Wrapper2 (-))

(Wrapper1 resF, _, _) = test

如上所述,我不太清楚模棱两可的类型,但其原因可能是 ,因为有关其他类型变量的所有信息已被拉入"元组,因此GHC知道现在可以安全地忽略它们.

As I mentioned above, I don’t understand ambiguous types too well, but the reason for this is probably because all the information about other type variables has been ‘pulled into’ the other fields of the tuple, so GHC knows it can safely ignore them now.

这篇关于在具有约束的多态元组上无法匹配的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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