【问题标题】:Composing functions with a intermediate polymorphic type in Haskell在 Haskell 中组合具有中间多态类型的函数
【发布时间】:2020-12-19 21:01:15
【问题描述】:

我有以下文件:

module SimpleComposition where

class Intermediate a where
    f :: a -> Int
    g :: Char -> a

h :: Char -> Int
h = f . g

尝试在 ghci 中加载它时,出现错误:

main.hs:8:5: error:
    * No instance for (Intermediate a0) arising from a use of `f'
    * In the first argument of `(.)', namely `f'
      In the expression: f . g
      In an equation for `h': h = f . g
  |
8 | h = f . g
  |     ^

我认为问题在于有人可能会使用 2 种不同的类型,它们是此组合中 Intermediate 的实例。导出这个模块时如何保证是一样的?

PS:与我之前提出的问题 (How to compose polymorphic functions in Haskell?) 相比,这是我遇到的问题的一个更好的最小示例。

【问题讨论】:

  • 问题是 Haskell 不知道用什么a 作为中间体。
  • 答案给出了错误的解决方案,但我认为不要解释错误所暗示的问题。因此,让我尝试一下作为修复的补充。假设我写instance Intermediate () where {f _ = 0; g _ = ();}; instance Intermediate Bool where {f _ = 1; g _ = False;}。您希望h 有什么行为?

标签: haskell polymorphism function-composition


【解决方案1】:

问题不在于无法推断出实例,而是编译器确实无法知道您可能想要的类型。 g 可以产生任何向它询问的类型(前提是它有一个 Intermediate 实例),f 可以使用任何这样的类型...但没有人指定哪个

但这很容易解决:现在只需选择一种类型。当然,它必须是有实例的;例如如果你有

instance Intermediate Char where
  f = fromEnum
  g = id

那么你可以使用

h :: Char -> Int
h = (f :: Char -> Int) . g

固定类型选择的更简洁的方法是使用语法扩展:

{-# LANGUAGE TypeApplications #-}

h = f @Char . g

...或者,为了强调您只是在中间固定类型,

h = f . id @Char . g

【讨论】:

    【解决方案2】:

    我认为问题在于有人可能使用了 2 种不同类型,它们是此组合中的中间体实例。

    没有问题,Haskell 不能再从签名派生出要使用的a。假设有两种Intermediate 类型:

    instance Intermediate Char where
        # …
    
    instance Intermediate Bool where
        # …

    现在h 有两种实现方式:

    h :: Char -> Int
    h = f . (g :: Char -> Char)

    或:

    h :: Char -> Int
    h = f . (g :: Char -> Bool)

    可以使用无限数量的Intermediate 类型。问题是 Haskell 无法根据类型签名判断使用什么类型。

    我们可以给它一个类型提示,但这当然意味着中间类型是固定的。

    解决此问题的一种简单方法是使用asTypeOf :: a -> a -> a。这基本上是一个const 函数,但是两个参数具有相同的类型。因此,这用于添加提示使用什么类型,例如:

    h :: Intermediate a => a -> Char -> Int
    h a x = f (g x `asTypeOf` a)

    这里 value a 参数因此并不重要,这是一种“注入”将用作g 和参数结果类型的类型的方法f.

    如果您以后使用h,您可以使用:

    h <b>(undefined :: Char)</b> 'a'

    指定f 的类型应为Char -&gt; Char,而g 的类型应为Char -&gt; Int

    就像@leftroundabout@DanielWagner 说的那样,不使用这种虚拟变量 的更干净的解决方案是在签名中添加类型变量:

    {-# LANGUAGE AllowAmbiguousTypes, ScopedTypeVariables, TypeApplications #-}
    
    h :: forall a. Intermediate a => Char -> Int
    h = f . g @ a

    然后我们可以将h 与类型变量一起使用:

    h <b>@ Char</b> 'a'

    【讨论】:

    • 我不喜欢传入虚拟参数的建议。至少也要提一下ScopedTypeVariables / AllowAmbiguousTypes 版本(或者如果您愿意,也可以使用带有代理的版本,尽管 IMO 代理也已过时)。
    • @leftaroundabout:我不确定AllowAmbiguousTypes这里能解决问题吗?这里的想法是在我们 使用 h 作为函数时启用“访问”中间类型,而不是在我们定义它时。这样一个人以后可以决定数据是“通过”Char 还是 Bool,而不是决定我们在哪里定义 h 函数。
    • @WillemVanOnsem 是的,AllowAmbiguousTypes 解决了这个问题。 (事实上​​,这正是它旨在解决的问题。)编写h :: Intermediate a =&gt; Char -&gt; Int 允许调用者说h @String(或其他)。
    • @DanielWagner:好吧,我试过了,得到了Could not deduce (Intermediate a0) arising from a use of ‘f’ from the context: Intermediate a,我用h = f . id @a . g 试过,用ScopedTypeVariablesTypeApplications,但可能是由于某种大脑冻结,我不要让它工作:repl.it/@willemvo/ambiguous#main.hs
    • 虚拟参数方法的更好版本是代理参数方法。 asProxyTypeOf :: a -&gt; proxy a -&gt; a; asProxyTypeOf a _ = a。导入Data.Proxy,可以写a `asProxyTypeOf` (Proxy :: Proxy Int)或者类似的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-11
    • 1970-01-01
    • 1970-01-01
    • 2016-09-25
    • 1970-01-01
    相关资源
    最近更新 更多