【问题标题】:Transforming functions of type `a -> b` into those of type `String -> String` in Haskell在 Haskell 中将 `a -> b` 类型的函数转换为 `String -> String` 类型的函数
【发布时间】:2013-08-07 14:31:25
【问题描述】:

我的意图很简单。我想将a -> b 类型的函数包装到String -> String 中(以便可以将一堆异构函数放入列表中)。所以我写:

wrap :: (Read a, Show b) => (a -> b) -> (String -> String)
wrap f = \s -> show $ f (read s :: a)

但是,ghc 投诉:

Could not deduce (Read a1) arising from a use of `read'
from the context (Read a, Show b)
  bound by the type signature for
             wrap :: (Read a, Show b) => (a -> b) -> String -> String

我想知道为什么我的一段代码不能工作,以及需要什么样的 hack 来实现我的目标?

谢谢。

【问题讨论】:

  • 将一堆异构函数放在一个列表中不是好的haskell,并且将它们全部包装在字符串转换中绝对是不好的haskell。从更大的意义上说,你想做什么?
  • 这是一个错误。不要这样做。您正在抛弃 Haskell 强大的类型系统,以及与之相伴的所有出色的编译时检查。一个机械师告诉你你的车很好,然后它在高速公路上抛锚了,不像一个机械师告诉你花 50 美元修理一些会导致你坏掉的东西(现在,当它在车库里时)下来(后来,在造成更多损坏后,需要支付维修费,而修理它的人没有他需要的零件)。静态类型是一种很好的机制,动态类型是一种很好的机制。
  • @NovaDenizen 我绝对喜欢静态类型。最近我一直在写一个简单的服务器。 a -> IO b 代表已实现的服务。一个组件是将[(String, a -> IO b)] 转换为Map String (a -> IO b)。但是类型系统不允许这样做。我确实有更复杂的类型设计来强制类型安全(因此客户端必须将a 类型的输入提供给a -> IO b 类型的服务,否则整个程序将不会进行类型检查)。但问题是服务器可能根本没有为用 Haskell 编写的客户端提供服务。所以我的机制在这种情况下不起作用。
  • @AndrewC 另外我很好奇是否有一种方法可以在 Haskell 和其他静态类型的 FP 语言(比如 OCaml)之间强制执行类型安全?

标签: haskell functional-programming


【解决方案1】:

您的代码将无法工作,因为 Haskell 不会重用或作用域类型变量; wrap :: (Read a, Show b) => (a -> b) -> (String -> String) 中的 aread s :: a 中的 a 完全不同(并且它们都是普遍量化的)。这是错误消息中a1的来源; GHC 正在将程序 alpha 转换为

wrap :: (Read a, Show b) => (a -> b) -> (String -> String)
wrap f = \s -> show $ f (read s :: a1)

但是,f 的参数类型在wrap 中是固定的,因此只需删除类型注释即可。你的函数变成了

wrap :: (Read a, Show b) => (a -> b) -> (String -> String)
wrap f = \s -> show $ f (read s)
  -- Or wrap f = show . f . read

你可以使用它:

ghci> map ($ "42") [wrap (+ (7 :: Integer)), wrap (* (2.0 :: Double))]
["49","84.0"]

请注意,这意味着read s 具有您无法写下的类型。在 Haskell 2010(或 98)中,解决这个问题的唯一方法是使用像 asTypeOf :: a -> a -> a 这样的函数; asTypeOf 只是 const,但由于它的类型签名,它将第一个返回的参数限制为与第二个相同的类型。然后,当然,你必须想出一个a 类型的变量。以下将适用于:

wrap :: (Read a, Show b) => (a -> b) -> (String -> String)
wrap f = \s -> show $ f (read s `asTypeOf` fInput)
  where fInput = undefined
        fOutput = f fInput -- I still can't give this a type signature

在 GHC 中,为避免这种情况,您可以打开 the ScopedTypeVariables extension;有了它,如果你用forall 明确地限定所有类型变量,它们的作用域就像值级名称一样。那么你的代码就会变成

{-# LANGUAGE ScopedTypeVariables #-}
wrap :: forall a b. (Read a, Show b) => (a -> b) -> (String -> String)
wrap f = \s -> show $ f (read s :: a)

但请记住,对于这个简单的示例,您根本不需要任何类型注释。

【讨论】:

    【解决方案2】:

    要明确指定read s 的类型,您需要类似ScopedTypeVariables

    {-# LANGUAGE ScopedTypeVariables #-}
    ...
    wrap :: forall a b. (Read a, Show b) => (a -> b) -> (String -> String)
    wrap f = \s -> show $ f (read s :: a)
    

    否则,函数内的:: a 注释与类型签名中的a 引用不同的类型(它隐含表示:: forall a. a)。但请注意,您也可以完全删除类型注释:

    wrap :: (Read a, Show b) => (a -> b) -> (String -> String)
    wrap f = \s -> show $ f (read s)
    

    因为read s的类型可以推断出来。这也可以让您将主体简化为

    wrap f = show . f . read
    

    【讨论】:

      猜你喜欢
      • 2016-09-25
      • 2017-01-29
      • 2012-11-09
      • 2016-01-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多