多态参数类型的模式匹配 - 备选方案

Pattern match on type of polymorphic parameter - alternatives

假设我需要不同的输出,具体取决于函数的多态参数类型。我的初始尝试失败并出现一些神秘的错误消息:

choice :: a -> Int
choice (_ :: Int) = 0
choice (_ :: String) = 1
choice _ = 2

但是,我们可以通过将所需类型包装在不同的数据构造函数中并在模式匹配中使用它们来轻松解决此问题:

data Choice a = IntChoice Int | StringChoice String | OtherChoice a

choice :: Choice a -> Int
choice (IntChoice _) = 0
choice (StringChoice _) = 1
choice (OtherChoice _) = 2

问题:你知道规避这个问题的方法吗? Haskell2010、GHC 或任何扩展中是否有允许我使用第一个变体(或类似的东西)的功能?

这混淆了两种不同的多态性。你想要的是 ad-hoc 多态性,这是通过类型 classes 完成的。 a -> Int类型函数的多态性是参数多态性。使用参数多态性,choice 的一个函数定义必须适用于 any 可能的类型 a。在这种情况下,这意味着它实际上不能使用类型 a 的值,因为它对此一无所知,因此 choice 必须是一个常量函数,例如 choice _ = 3.这实际上为您提供了关于函数可以做什么的非常有力的保证,只需查看它的类型(这个 属性 称为 parametricity)。

使用类型 class,您可以将示例实现为:

class ChoiceClass a where
  choice :: a -> Int

instance ChoiceClass Int where
  choice _ = 0

instance ChoiceClass String where
  choice _ = 1

instance ChoiceClass a where
  choice _ = 2

现在,我应该指出这种 class 方法通常是错误的,尤其是当刚开始使用它的人想要使用它时。您 肯定 不想这样做以避免在您的问题中使用像 Choice 这样的简单类型。它会增加很多复杂性,并且实例解析一开始可能会令人困惑。请注意,为了使类型 class 解决方案起作用,需要打开两个扩展:FlexibleInstancesTypeSynonymInstances 因为 String 是 [=21= 的同义词]. OverlappingInstances 也是必需的,因为类型 class 是基于 "open world" 假设工作的(这意味着任何人以后都可以出现并为新类型添加实例,必须考虑到这一点) .这不一定 是件坏事,但这是使用类型 class 解决方案而不是更简单的数据类型解决方案所导致的复杂性逐渐增加的迹象。 OverlappingInstances 尤其会使事情更难思考和处理。

Question: Do you know of a way to circumvent this? Is there a feature in Haskell2010, GHC or any of the extensions that allows me to use the first variant (or something similar)?

否,Haskell 2010 中或任何 GHC 扩展都没有提供可让您编写

类型函数的功能
choice :: a -> Int

其 return 值取决于其参数的类型。您可以指望将来也不会存在这样的功能。

即使使用 hack 在运行时检查 GHC 的内部数据表示,也无法区分类型 Int 的值和类型为 Int 的新类型的值:这些类型具有相同的运行时陈述。

你的函数得到的Int return是一个运行时存在的值,所以需要由另一个运行时存在的值来确定。这可能是

  1. 普通值,如您的 data Choice a = IntChoice Int | StringChoice String | OtherChoice a; choice :: Choice a -> Int 示例,或

  2. 类型 class 字典,使用 David Young 的回答中的自定义 class 或内置 Typeable class:

    choice :: Typeable a => a -> Int
    choice a
      | typeOf a == typeOf (undefined :: Int)    = 0
      | typeOf a == typeOf (undefined :: String) = 1
      | otherwise                                = 2