【问题标题】:Haskell function type clarificationHaskell 函数类型说明
【发布时间】:2017-02-15 08:15:14
【问题描述】:

我正在尝试了解函数的 Haskell 类型变量。我写了一个函数:

applyTwice f x = f(f x)

我试图了解这个函数的类型变量,所以我做了一个:t applyTwice。 Haskell 是这样解释类型的:

applyTwice :: (t -> t) -> t -> t

然后我创建了另一个函数:

applyOnce f x = f x

这次:tHaskell 回归

applyOnce :: (t1 -> t) -> t1 -> t

我的问题是

  1. 我们如何阅读/理解这些函数的取值和返回值?

  2. 这是给applyTwice的。如果他们说-> 的左侧是函数所需要的,而右侧是它返回的,那么它不应该是 applyTwice :: ((t -> t) -> t) -> t? (t -> t) 对应于(f x)((t -> t) -> t) 对应于f (f x),返回类型为t

  3. 这是给applyOnce的。为什么函数的类型被解释为applyOnce :: (t1 -> t) -> t1 -> t?因为我们只接受一个函数并返回它的值。不应该是applyOnce :: (t1 -> t) -> t1吗?

作为 Haskell 的初学者,我希望得到任何建议。

【问题讨论】:

    标签: haskell


    【解决方案1】:

    我们有:

    applyTwice f x = f(f x)
    

    这只有在xf x 的类型是等价的情况下才有意义(否则你怎么能将f x 传递给一个接受x 的函数?)。因此,您可以说 applyTwice 接受 2 个输入参数:

    • 接受类型 t 并返回相同类型 t 的函数
    • 某种类型的输入

    现在这个输入可以是任何类型,比如 t1。但是您还必须能够将函数应用于此输入,因此输入的类型也必须是 t。放在一起,我们得到了签名:

    applyTwice :: (t -> t) -> t -> t
    

    请记住,只有最后一项是返回类型,所有其他项都是输入的类型。

    现在考虑:

    applyOnce f x = f x
    

    简单地说,它对函数 f 所说的只是它应该能够接受任何类型的输入 x。没有关于函数应该返回什么类型 except applyOnce 也应该返回相同的类型。因此,我们最终得到您看到的签名:

    applyOnce :: (t1 -> t) -> t1 -> t
    

    其中 t1 可以是任意类型,t 可以是任意类型(与 t1 相同或不同),但要求 f 的返回类型与 applyOnce 的返回类型匹配,此处由 t 表示。

    TL;DR:您对具有 n 个输入的函数的函数签名将始终具有 (n+1) 项,最后一项将是函数的返回类型。

    但是,您应该注意,这实际上并不是事情的幕后运作方式。 Haskell 中的所有函数实际上都有一个参数,即它们是 curried。你可以阅读更多关于这个here的信息。

    【讨论】:

    • 您可以深入了解applyTwice (\x -> [x]) 以及那里需要哪些语言扩展...
    • @Franky:我不明白这个评论,但我很感兴趣。这是什么意思?
    【解决方案2】:

    返回类型

    函数接受什么参数以及函数返回什么在 Haskell 中是一个相当棘手的问题,因为偏应用之类的特殊问题。

    map 函数看起来需要一个f :: a -> b、转换函数和一个列表xs :: [a],并返回一个ys :: [b] 列表,其中xs 的每个元素都根据f 进行了转换。但是map 也可以看作是一个函数,它接受一个简单的函数f 并返回另一个 函数,该函数作用于列表并返回列表。

    为了帮助我推理参数,我首先将由顶级->s 分隔的所有类型分开。例如map:

    map :: (a -> b) -> [a] -> [b]
    -- gives us
    -- (a -> b) and [a] and [b]
    

    当调用 map 并提供所有参数时,最右边的类型是传统上称为“返回类型”的类型。其他类型是我必须使用的参数,例如在函数定义中。

    对此持保留态度,因为您会看到大量函数定义示例,这些示例并未明确使用所有参数来定义函数。


    谈到第二个问题,您显然混淆了参数和函数体。以applyTwice

    applyTwice f x = f (f x)
    

    fxapplyTwice 的参数,f (f x) 是函数体。现在f 必须是一个函数,因为它应用于参数xx 的类型可以是任何类型,我们称之为t。现在 f 必须从 tx 的类型)转到别的东西,称之为 s。但是现在f x,其类型为s,又是f 的一个参数,所以编译器可以推断出s ~ t。现在f (f x) 又是s 类型,它必须与t 相同,这意味着你最终会得到:

    applyTwice :: (t -> t) -> t -> t
    

    类似地,您可以尝试对applyOnce 进行此操作,并查看其类型为(t -> s) -> t -> s,即$ 来自Prelude

    【讨论】:

    • 感谢您的解释。在 (applyTwice :: (t1 -> t) -> t1 -> t) 中,第三个参数 (t1) 提到了中间值的输出类型。但是在 (applyOnce :: (t1 -> t) -> t1 -> t) 中,第三个参数只是意味着 (t1) 与传递给函数的输入的类型相同。之所以混淆,是因为在 applyOnce 中只有 3 方 1.Function,2.Input 类型和 3Output 类型。为什么我们在类型声明中有 4 种类型?
    • @Srinivas f 的类型声明只能表示为两种类型:输入类型和输出类型。所以你需要2种类型来指定f,一种类型指定输入,一种类型指定输出,等于四种类型。您认为t1applyOnce 的第二个参数的类型是正确的以及 f 的第一个参数。
    • 感谢您的澄清
    【解决方案3】:

    让我们将 Haskell 翻译成英文。

    applyTwice :: (t -> t) -> t -> t 表示 applyTwice 接受从 tt 的函数和值 t 并返回 t。事实上,如果我们查看定义,我们会看到它将函数 f 应用于值 x 并将结果再次输入 f。这意味着 - 如类型签名所示 - ff x 必须具有相同的类型 t(因为 f 只接受 t 作为参数)。

    相比之下,applyOnce :: (t1 -> t) -> t1 -> t 允许f xx 具有不同的类型(分别为tt1),因为此函数不提供f 的输出(@ 类型) 987654340@) 回f

    我认为混淆源于您无法区分f(函数)和f x(函数的结果)。

    最后,您为applyOnce(即(t1 -> t) -> t)建议的类型意味着(不,肯定)您可以在不提供参数的情况下获得函数的结果;请记住,(t1 -> t) 是输入类型(一个函数),t 是输出类型。没有x,你如何计算f(x)

    【讨论】:

    • 感谢您的解释,在
    • 在(applyTwice :: (t1 -> t) -> t1 -> t)中,第三个参数(t1)提到了中间值的输出类型。但是在 (applyOnce :: (t1 -> t) -> t1 -> t) 中,第三个参数是否仅仅意味着 (t1) 与传递给函数的输入类型相同?之所以混淆,是因为在 applyOnce 中只有 3 方 1. Function,2. Input type 和 3 Output type。为什么我们在类型声明中有 4 种类型?
    • @Srinivas 因为 1. 不仅仅是“函数”(->),而是“从输入类型到输出类型的函数”(t1 -> t)。如果要添加多余的括号,可以在(t1 -> t) -> (t1) -> (t) 中看到三方——现在每一方都有自己的括号。
    • 感谢您的解释
    • 修正了第一种类型的解释中的错误(应该是applyTwice
    猜你喜欢
    • 2017-07-08
    • 1970-01-01
    • 1970-01-01
    • 2021-12-31
    • 1970-01-01
    • 2011-08-28
    • 1970-01-01
    • 2022-08-12
    • 1970-01-01
    相关资源
    最近更新 更多