【问题标题】:What does uncurry ($) do?uncurry ($) 有什么作用?
【发布时间】:2013-04-06 06:27:36
【问题描述】:

我正在做一些练习,我必须添加一个函数的类型并解释它的作用。我被这个困住了:

phy = uncurry ($)

根据 GHCi,类型是 phy :: (a -> b, a) -> b。我的 haskell 知识很基础,所以我真的不知道它的作用。

【问题讨论】:

  • 这里有个可爱的想法:uncurry ($)其实和uncurry id是一样的!发生这种情况是因为 ($) 实际上只是 id 专用于函数。试试:t uncurry id 看看我的意思。
  • 这段代码说明了类型思考的力量。弄清楚uncurry ($) 的作用确实需要一些思考,但是(a -> b, a) -> b 类型的函数的作用应该是显而易见的。事实上,这种类型的函数可以做的只有一件事。如果您在使用 Haskell 编程时注意类型,那么您应该自然而然地培养这种直觉。如果您以后想正式化您的直觉,请尝试 Philip Wadler 的论文“Theorems for free”[PDF]

标签: haskell functional-programming currying


【解决方案1】:

(确保您了解高阶函数和柯里化,阅读higher-order functions 上的Learn You a Haskell 一章,然后阅读difference between . (dot) and $ (dollar sign)function composition (.) and function application ($) idioms

($)只是一个函数应用,f $ x相当于f x。但这很好,因为我们可以使用显式函数应用,例如:

map ($2) $ map ($3) [(+), (-), (*), (**)] -- returns [5.0,1.0,6.0,9.0]

相当于:

map (($2) . ($3)) [(+), (-), (*), (**)] -- returns [5.0,1.0,6.0,9.0]

检查($)的类型:($) :: (a -> b) -> a -> b。你知道类型声明是右关联的,因此($)的类型也可以写成(a -> b) -> (a -> b)。等一下,那是什么?接收一元函数并返回相同类型的一元函数的函数?这看起来像是身份函数id :: a -> a 的特定版本。好的,先来一些类型:

($) :: (a -> b) -> a -> b
id :: a -> a
uncurry :: (a -> b -> c) -> (a, b) -> c
uncurry ($) :: (b -> c, b) -> c
uncurry id :: (b -> c, b) -> c

在编写 Haskell 代码时,请始终查看类型,在您查看代码之前,它们会为您提供大量信息。那么,($) 是什么?它是 2 个参数的函数。 uncurry 是什么?它也是 2 个参数的函数,第一个是 2 个参数的函数。所以uncurry ($) 应该进行类型检查,因为uncurry 的1st 参数应该是2 个参数的函数,($) 是。现在尝试猜测uncurry ($) 的类型。如果($) 的类型是(a -> b) -> a -> b,则将其替换为(a -> b -> c)a 变为(a -> b)b 变为ac 变为b,因此返回@98765435 ((a -> b), a) -> b 类型的函数。或者(b -> c, b) -> c,同上。那么这种类型告诉我们什么? uncurry ($) 接受一个元组 (function, value)。现在尝试仅从类型猜测它的作用。

现在,在回答之前,一个插曲。 Haskell 是如此strongly typed,它禁止返回一个具体类型的值,如果类型声明有一个类型变量作为返回值类型。所以如果你有一个类型为a -> b的函数,你就不能返回String。这是有道理的,因为如果你的函数的类型是a -> a 并且你总是返回String,那么用户如何能够传递任何其他类型的值?您应该有一个类型 String -> String 或有一个类型 a -> a 并返回一个仅取决于输入变量的值。但是这种限制也意味着不可能为某些类型编写函数。没有a -> b 类型的函数,因为没有人知道应该用什么具体类型来代替b。或者[a] -> a,你知道这个函数不能是total,因为用户可以传递一个空列表,那么这个函数会返回什么? a 类型应该依赖于列表内部的类型,但是列表没有“内部”,它是空的,所以你不知道空列表中的元素是什么类型。这种限制只允许在特定类型下为可能的函数留出非常狭窄的肘部空间,这就是为什么仅通过读取类型就可以获得有关函数可能行为的大量信息的原因。

uncurry ($) 返回 c 类型的东西,但它是一个类型变量,而不是具体类型,所以它的值取决于也是 c 类型的东西。我们从类型声明中看到元组中的函数返回c 类型的值。同样的函数要求b 类型的值,它只能在同一个元组中找到。没有具体的类型也没有类型类,所以uncurry ($) 唯一能做的就是把一个元组的snd 作为一个参数放在一个元组的fst 的函数中,返回它返回的任何东西:

uncurry ($) ((+2), 2) -- 4
uncurry ($) (head, [1,2,3]) -- 1
uncurry ($) (map (+1), [1,2,3]) -- [2,3,4]

有一个可爱的程序djinn 可以根据类型生成 Haskell 程序。试一试,看看我们对uncurry ($) 功能的类型猜测是否正确:

Djinn> f ? a -> a
f :: a -> a
f a = a
Djinn> f ? a -> b
-- f cannot be realized.
Djinn> f ? (b -> c, b) -> c
f :: (b -> c, b) -> c
f (a, b) = a b

这还表明,fstsnd 是唯一可以具有各自类型的函数:

Djinn> f ? (a, b) -> a
f :: (a, b) -> a
f (a, _) = a
Djinn> f ? (a, b) -> b
f :: (a, b) -> b
f (_, a) = a

【讨论】:

    【解决方案2】:

    让我们系统地拼出类型部分。我们将从uncurry($) 的类型开始:

    uncurry :: (a -> b -> c) -> (a, b) -> c
    ($)     :: (a -> b) -> a -> b
    

    由于目标表达式有 ($) 作为 uncurry 的参数,让我们排列它们的类型以反映这一点:

    uncurry :: (a       -> b -> c) -> (a, b) -> c
    ($)     :: (a -> b) -> a -> b
    

    ($) 的整个类型与uncurry 的第一个参数类型对齐,($) 的参数和结果类型与uncurry 的第一个参数类型对齐,如图所示。这是对应关系:

    uncurry's a  <==> ($)'s a -> b
    uncurry's b  <==> ($)'s a
    uncurry's c  <==> ($)'s b
    

    这有点令人困惑,因为一种类型中的ab 类型变量与另一种类型不同(就像plusTwo x = x + 2 中的xx 不同一样在timesTwo x = x * 2)。但是我们可以重写类型来帮助解释这一点。在像这样的简单 Haskell 类型签名中,任何时候你看到一个类型变量,你都可以用任何其他类型替换它的所有出现,也可以得到一个有效类型。如果你选择 fresh 类型变量(类型变量不会出现在原始的任何地方),你会得到一个 equivalent 类型(可以转换回原来的类型) ;如果您选择非新鲜类型,您会得到一个专业版本的原始版本,它适用于更窄范围的类型。

    但无论如何,让我们将它应用到uncurry:: 的类型上

    -- Substitute a ==> x, b ==> y, c ==> z:
    uncurry :: (x -> y -> z) -> (x, y) -> z
    

    让我们用重写后的类型重做“排队”:

    uncurry :: (x       -> y -> z) -> (x, y) -> z
    ($)     :: (a -> b) -> a -> b
    

    现在很明显:x &lt;==&gt; a -&gt; by &lt;==&gt; az &lt;==&gt; b。现在,将uncurry 的类型变量替换为($) 中的对应类型,我们得到:

    uncurry :: ((a -> b) -> a -> b) -> (a -> b, a) -> b
    ($)     ::  (a -> b) -> a -> b
    

    最后:

    uncurry ($) :: (a -> b, a) -> b
    

    这就是您确定类型的方式。它的作用如何?好吧,在这种情况下,最好的方法是查看类型并仔细考虑,弄清楚我们必须编写什么才能获得该类型的函数。让我们这样重写它,让它更神秘:

    mystery :: (a -> b, a) -> b
    mystery = ...
    

    既然我们知道mystery 是一个参数的函数,我们可以扩展这个定义来反映:

    mystery x = ...
    

    我们也知道它的参数是一对,所以我们可以再扩展一点:

    mystery (x, y) = ...
    

    既然我们知道x 是一个函数,而y :: a,我喜欢使用f 来表示“函数”并将变量命名为与其类型相同——这有助于我推理函数,所以让我们这样做:

    mystery (f, a) = ...
    

    现在,我们在右手边放什么?我们知道它必须是b 类型,但我们不知道b 是什么类型(它实际上是调用者选择的任何类型,所以我们无法知道)。所以我们必须以某种方式使用我们的函数f :: a -&gt; b 和值a :: a 创建一个b。啊哈!我们可以只调用带有值的函数:

    mystery (f, a) = f a
    

    我们在没有查看uncurry ($) 的情况下编写了这个函数,但事实证明它与uncurry ($) 做的事情相同,我们可以证明这一点。先从uncurry($)的定义说起:

    uncurry f (a, b) = f a b
    f $ a = f a
    

    现在,用equals代替equals:

    uncurry ($) (f, a) = ($) f a         -- definition of uncurry, left to right
                       = f $ a           -- Haskell syntax rule
                       = f a             -- definition of ($), left to right
                       = mystery (f, a)  -- definition of mystery, right to left
    

    因此,在 Haskell 中攻击您不理解的类型的一种方法是尝试编写一些具有该类型的代码。 Haskell 与其他语言的不同之处在于,这通常是比尝试阅读代码更好的策略。

    【讨论】:

      【解决方案3】:

      其他两个答案都很好。我只是对此略有不同。

      uncurry :: (a -> b -> c) -> (a, b) -> c
      ($)     :: (a -> b) -> a -> b
      

      由于类型签名中的“->”关联到右边,我可以将这两个类型签名等效地写成这样:

      uncurry :: (a -> b -> c) -> ((a, b) -> c)
      ($)     :: (a -> b) -> (a -> b)
      

      uncurry 接受两个输入的任意函数并将其更改为一个参数的函数,其中该参数是原始两个参数的元组。

      ($) 采用一个简单的单参数函数并将其转换为...自身。它的唯一作用是语法。 f $ 等价于 f

      【讨论】:

        【解决方案4】:
        uncurry :: (a -> b -> c) -> (a, b) -> c
        
        ($) :: (a -> b) -> a -> b
        
        uncurry ($) :: (a -> b, a) -> b
        

        如果您检查 uncurry$ 的类型及其描述:

        uncurry 将 curried 函数转换为对上的函数。

        它所做的只是接受一个函数(a -&gt; b -&gt; c) 并返回一个将参数作为元组的函数。

        所以phy$ 做同样的事情,但不是f $ x($) f x 你把它称为phy (f, x)

        【讨论】:

        • 我认为如果你在uncurry ($)的类型签名中使用相同的类型变量会更清楚。
        猜你喜欢
        • 2016-08-28
        • 1970-01-01
        • 2012-01-13
        • 1970-01-01
        • 2023-04-10
        • 2011-07-17
        • 2014-09-13
        • 2011-12-30
        • 1970-01-01
        相关资源
        最近更新 更多