【问题标题】:How to cast `forall a. a -> a` back to `a -> a`?如何投射`forall a. a -> a` 回到`a -> a`?
【发布时间】:2020-01-17 00:52:35
【问题描述】:

在我的实际问题中,我有一个函数f,作为参数传递,它会更改列表中的顺序,但对类型没有要求,也不会更改类型。我想在[Int][Bool] 上应用该函数,所以我必须解决两个上下文,试图将f 类型转换为[Int] -> [Int][Bool] -> [Bool]。我用Rank2Types 解决了这个问题。 但后来我在fany 等函数列表上使用any 要求函数为[a] -> [a] 而不是forall a. [a] -> [a]

下面的代码虽然荒谬,但完美地重现了错误:

{-# LANGUAGE Rank2Types #-}

--debug :: (forall a. [a] -> [a]) -> Bool 
debug swap = any combine [swap]
  where
    combine :: (forall a. [a] -> [a]) -> Bool
    combine f =  usefonBool f && usefonInt f
    --usefonBool :: (forall a. [a] -> [a]) -> Bool
    usefonBool f = f [True,True] == [False]

    --usefonInt :: (forall a. [a] -> [a]) -> Bool
    usefonInt f = (f [1,2]) == [2,1]

错误信息是:

• Couldn't match type ‘a’ with ‘forall a1. [a1] -> [a1]’
  ‘a’ is a rigid type variable bound by
    the inferred type of debug :: a -> Bool
    at /path/debug.hs:(4,1)-(12,36)
  Expected type: a -> Bool
    Actual type: (forall a. [a] -> [a]) -> Bool
• In the first argument of ‘any’, namely ‘combine’
  In the expression: any combine [swap]
  In an equation for ‘debug’:
      debug swap
        = any combine [swap]
        where
            combine :: (forall a. [a] -> [a]) -> Bool
            combine f = usefonBool f && usefonInt f
            usefonBool f = f [True, ....] == [False]
            usefonInt f = (f [1, ....]) == [2, ....]
• Relevant bindings include
    swap :: a (bound at /path/debug.hs:4:7)
    debug :: a -> Bool (bound at /path/debug.hs:4:1)
|

我的目标是找到一个注释,让我可以在不同的类型上使用 f,然后在此类通用函数的列表中应用任何类型。

如果我取消注释我所有的类型注释(或只是顶部的注释),错误将变为

• Couldn't match type ‘[a0] -> [a0]’ with ‘forall a. [a] -> [a]’
  Expected type: ([a0] -> [a0]) -> Bool
    Actual type: (forall a. [a] -> [a]) -> Bool
• In the first argument of ‘any’, namely ‘combine’
  In the expression: any combine [swap]
  In an equation for ‘debug’:
      debug swap
        = any combine [swap]
        where
            combine :: (forall a. [a] -> [a]) -> Bool
            combine f = usefonBool f && usefonInt f
            usefonBool :: (forall a. [a] -> [a]) -> Bool
            usefonBool f = f [True, ....] == [False]
            ....
  |

【问题讨论】:

  • 你为什么要注释掉类型签名?
  • @leftaroundabout 我将它们注释掉以展示一个尽可能少假设的版本。
  • 这样不行。 GHC 只能推断 Rank-1 类型,因此您必须使用显式签名。在这种情况下,顶级的应该足够了。 – 但无论如何,省略签名通常是个坏主意。
  • @leftaroundabout 取消注释 my 顶级签名尚未解决

标签: haskell types type-inference


【解决方案1】:

首先,请注意 Rank2Types 是一个已弃用的名称。它相当于现代 GHC 中的 RankNTypes,这是扩展的首选名称。

这是根本问题。 “此类通用函数的列表”可能具有以下类型:

[forall a. [a] -> [a]]

遗憾的是,这不是有效的 Haskell 类型,因为 Haskell 不支持“指示性多态性”。具体如下:

{-# LANGUAGE RankNTypes #-}
myFunctions :: [forall a. [a] -> [a]]
myFunctions = [f1, f2]
   where f1 (x:y:rest) = y:x:rest
         f2 = reverse

产生错误信息:

DebugRank.hs:2:16: error:
    • Illegal polymorphic type: forall a. [a] -> [a]
      GHC doesn't yet support impredicative polymorphism
    • In the type signature: myFunctions :: [forall a. [a] -> [a]]

有一个扩展名,ImpredicativeTypes。它是易碎且不完整的,但它允许编译以下内容:

{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ImpredicativeTypes #-}

myFunctions :: [forall a. [a] -> [a]]
myFunctions = [f1, f2]
   where f1 (x:y:rest) = y:x:rest
         f2 = reverse

debug :: [forall a. [a] -> [a]] -> Bool
debug = any combine
  where
    combine :: (forall a. [a] -> [a]) -> Bool
    combine f = usefonBool f && usefonInt f
    usefonBool f = f [True,True] == [False]
    usefonInt  f = f [1,2] == [2,1]

main = print (debug myFunctions)

不过,我仍然建议不要使用它。

通常的替代方法是为多态函数使用newtype 包装器:

newtype ListFunction = ListFunction (forall a. [a] -> [a])

这需要一些样板文件,但除了 RankNTypes 之外没有其他扩展:

myFunctions :: [ListFunction]
myFunctions = [ListFunction f1, ListFunction f2]
   where f1 (x:y:rest) = y:x:rest
         f2 = reverse

debug :: [ListFunction] -> Bool
debug = any combine
  where
    combine :: ListFunction -> Bool
    combine (ListFunction f) = usefonBool f && usefonInt f
    usefonBool f = f [True,True] == [False]
    usefonInt  f = f [1,2] == [2,1]

完整代码:

{-# LANGUAGE RankNTypes #-}

newtype ListFunction = ListFunction (forall a. [a] -> [a])

myFunctions :: [ListFunction]
myFunctions = [ListFunction f1, ListFunction f2]
   where f1 (x:y:rest) = y:x:rest
         f2 = reverse

debug :: [ListFunction] -> Bool
debug = any combine
  where
    combine :: ListFunction -> Bool
    combine (ListFunction f) = usefonBool f && usefonInt f
    usefonBool f = f [True,True] == [False]
    usefonInt  f = f [1,2] == [2,1]

main = print $ debug myFunctions

【讨论】:

  • 我真的不明白为什么Rank2Types弃用。许多应用程序(包括这个)实际上只需要 rank-2,没有更高的要求,实际上有一个合理的理由可以将其限制为:rank-2 多态性仍然允许类型推断,而 rank-N 不允许.当然,GHC 没有实现这一点,但可以想象它可以做到。同时,指定Rank2 也同样有效。 OTOH,ImpredicativeTypes 应该被弃用——它不能正常工作,从来没有正常工作过,可能不会很快。
猜你喜欢
  • 2017-11-21
  • 2020-05-01
  • 1970-01-01
  • 2018-09-10
  • 2011-03-20
  • 1970-01-01
  • 1970-01-01
  • 2019-05-04
  • 2017-01-30
相关资源
最近更新 更多