可能具有多态参数的函数的类型签名



我能为函数写类型签名吗?如果可以,我该如何写:

g f x y = (f x, f y)

这样给定:

f1 :: a -> [a]
f1 x = [x]
x1 :: Int
x1 = 42
c1 :: Char
c1 = 'c'
f2 :: Int -> Int
f2 x = 3 * x
x2 :: Int
x2 = 5

这样:

g f1 x1 c1 == ([42], ['c']) :: ([Int], [Char])
g f2 x1 x2 == (126, 15) :: (Int, Int)

不,你不能。基本问题是Haskell的语法欺骗了您,使您认为f1f2的类型比实际情况更相似。一旦翻译成GHC核心,它们看起来就大不相同了:

f1 :: forall a . a -> [a]
f2 :: Int -> Int

不仅如此,相应的术语看起来也相当不同:

f1 = Λa -> λ(x :: a) -> [x]
f2 = λ(x :: Int) -> 3 * x

如您所见,f1f2实际上具有不同数量的参数,其中f1采用类型;而f2仅采用一个值。

在更常见的情况下,当您将f1放入期望类型为Int -> [Int]的函数的上下文中时,GHC会将f1应用于您所需的类型(即,将f1实例化为特定类型),一切都会好起来。例如,如果你有

g :: (Int -> [Int]) -> Bool

如果您将g应用于f1,GHC实际上会将其编译为

g (f1 @Int)

但在这里,你想要多态性来控制实例化是否发生,而GHC不支持这一点(我认为这将是对核心语言的一个相当激进和破坏性的改变)。

由于类实例、类型族模式和类型族结果是无法量化的,我很有信心没有任何方法可以得到你想要的。

如果您不介意添加Proxy参数,使用我对类似问题的回答的变体,这实际上是可能的。

我对这个答案的大部分解释在这里成立,但我们需要通过添加更多的助手类型类(我在这里称之为ListBoth)来稍微扩展一下:

{-# LANGUAGE RankNTypes            #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE ConstraintKinds       #-}
import           Data.Proxy
f1 :: a -> [a]
f1 x = [x]
x1 :: Int
x1 = 42
c1 :: Char
c1 = 'c'
f2 :: Int -> Int
f2 x = 3 * x
x2 :: Int
x2 = 5
class b ~ [a] => List a b
instance List a [a]
class (a ~ b, b ~ c) => Both a b c
instance Both a a a
g :: (c a r1, c b r2) =>
      Proxy c -> (forall x r. c x r => x -> r) -> a -> b -> (r1, r2)
g _ f x y = (f x, f y)

这使我们能够进行

ghci> g (Proxy :: Proxy List) f1 x1 c1
([42],"c")
ghci> g (Proxy :: Proxy (Both Int)) f2 x1 x2
(126,15)

ListBoth不是最好的名称(尤其是List),所以如果你确实使用了这个名称,你可能会想要更好的名称(尽管我不确定我是否建议在生产代码中使用这种类型的欺骗,除非你有一个真正的充分理由)。

最新更新