参考透明度与Haskell中的多态性

nul*_*nge 17 polymorphism haskell referential-transparency higher-rank-types

说我有一个功能:

f :: Int -> (Rational, Integer)
f b = ((toRational b)+1,(toInteger b)+1)
Run Code Online (Sandbox Code Playgroud)

我想像这样抽象出(+1):

f :: Int -> (Rational, Integer)
f b = (h (toRational b)
      ,h (toInteger b))
    where h = (+1)
Run Code Online (Sandbox Code Playgroud)

这显然不会起作用,但如果我指定类型签名,它将起作用:

f :: Int -> (Rational, Integer)
f b = (h (toRational b)
      ,h (toInteger b))
    where h :: Num a => a -> a
          h = (+1)
Run Code Online (Sandbox Code Playgroud)

假设我现在想通过传递h作为参数来进一步抽象函数:

f :: Num a => Int -> (a -> a) -> (Rational, Integer)
f b g = (h (toRational b)
        ,h (toInteger b))
    where h :: Num a => a -> a
          h = g
Run Code Online (Sandbox Code Playgroud)

我得到一个错误,内部a与外部a不同.

有谁知道如何正确编写此功能?我想传递一个多态函数并以多态g方式f使用它.

我现在在非常不同的项目中多次遇到过这种情况,我找不到一个好的解决方案.

nul*_*nge 18

我找到了解决方案:使用forall量词如下:

{-# LANGUAGE RankNTypes #-}
f :: Int -> (forall a. Num a=> a -> a) -> (Rational, Integer)
f b g = (h (toRational b)
        ,h (toInteger b))
    where h :: Num a => a -> a
          h = g
Run Code Online (Sandbox Code Playgroud)

当然可以变成:

f :: Int -> (forall a. Num a=>a -> a) -> (Rational, Integer)
f b g = (g (toRational b)
        ,g (toInteger b))
Run Code Online (Sandbox Code Playgroud)

  • 这是完全正确的.如果没有forall,Haskell将尝试在函数体内专门化函数参数.这导致了无法解决的系统`(Rational~a)&&(Integer~a)`.forall绑定说你将传入一个非专业的功能,并仅在呼叫站点专门化它.缺点是你不能传递另一个`a`类型,并确保它与函数匹配 - 而不是你在这种情况下会这样做. (11认同)