【问题标题】:Can't understand result when calling applyTwice multiple times多次调用 applyTwice 时无法理解结果
【发布时间】:2021-07-11 08:22:20
【问题描述】:

有很多基于applyTwice 的问题,但没有一个与我的问题有关。我了解applyTwice 函数是这样定义的:

applyTwice :: (a -> a) -> a -> a
applyTwice f a = f (f a)

两次应用一个函数。所以如果我有一个增量函数:

increment x = x + 1

然后做

applyTwice increment 0

我得到 2。但我不明白这些结果:

applyTwice applyTwice applyTwice increment 0 -- gives 16
applyTwice applyTwice applyTwice applyTwice increment 0 -- gives 65536
applyTwice applyTwice applyTwice applyTwice applyTwice increment 0 -- stack overflow

我也知道

twice = applyTwice applyTwice increment
applyTwice twice 0 -- gives 8

我根本无法理解这些结果,如果有人可以解释,我会很高兴。如果这是基本的东西,我很抱歉,因为我刚刚学习 Haskell。

【问题讨论】:

  • 你需要了解函数应用是左关联的,所以applyTwice applyTwice increment(applyTwice applyTwice) increment是一样的。所以为了了解(applyTwice applyTwice) increment做了什么,你需要了解(applyTwice applyTwice)一般做了什么,然后弄清楚它对increment做了什么。
  • 如果您将applyTwice applyTwice 视为2^2,那么applyTwice . applyTwice 就是2*2,因此给出相同的结果:) 查看Church encoding。与求幂 applyTwice @(Int->Int) $ applyTwice @Int 不同,类型不会爆炸 applyTwice @Int . applyTwice @Int

标签: haskell functional-programming higher-order-functions currying


【解决方案1】:

让我们使用非正式的表示法

iter n f = f . f . f . ....  -- n times

您的applyTwice 就是iter 2

从定义,我们立即得到:

(iter n . iter m) f 
= iter n (iter m f)
= (f.f. ...) . ... . (f.f. ...)   -- n times (m times f)
= iter (n*m) f

因此,eta 合同,

iter n . iter m = iter (n*m)    -- [law 1]

我们也有

iter n (iter m) 
=  -- definition
iter m . iter m . .... . iter m    -- n times
= -- law 1
iter (m*m* ... *m)                 -- n times
= -- power
iter (m^n)                         -- [law 2]

然后我们将t 写成applyTwice

t = iter 2

t t 
= -- previous equation
iter 2 (iter 2)
= -- law 2
iter (2^2)

t t t
= -- left associativity of application
(t t) t
= -- previous equation
iter (2^2) (iter 2)
= -- law 2
iter (2^(2^2))

t t t t
= -- left associativity of application
(t t t) t
= -- previous equation
iter (2^(2^2)) (iter 2)
= -- law 2
iter (2^(2^(2^2)))

等等。

【讨论】:

    【解决方案2】:

    隐藏在幕后的复杂性以不可见的类型参数的形式存在。如果我们用-XTypeApplications 写出这些论点会发生什么。我已经缩短了一些名称。

    下面的twicetwice @Int实例化,其类型是二阶的。

    one :: Int
    one = twice (+ 1) 0
          ^^^^^
          | 
          twice @Int
            :: (Int -> Int) 
            -> (Int -> Int)
    

    当您将twice (order-3) 应用于twice (order-2) 时,类型签名变得更加复杂。

    two :: Int
    two = twice twice (+ 1) 0
          ^^^^^ ^^^^^
          |     |
          |     twice @Int
          |       :: (Int -> Int)
          |       -> (Int -> Int)
          |
          twice @(Int->Int)
            :: ((Int->Int) -> (Int->Int))
            -> ((Int->Int) -> (Int->Int))
    

    以此类推,当你有 twice twice twice 时,它们分别是 order-4、order-3 和 order-2:

    three :: Int
    three = twice twice twice (+ 1) 0
            ^^^^^ ^^^^^ ^^^^^
            |     |     | 
            |     |     twice @Int
            |     |       :: (Int -> Int)
            |     |       -> (Int -> Int)
            |     |
            |     twice @(Int->Int)
            |       :: ((Int->Int) -> (Int->Int))
            |       -> ((Int->Int) -> (Int->Int))
            |
            twice @((Int->Int)->(Int->Int))
              :: (((Int->Int)->(Int->Int)) -> ((Int->Int)->(Int->Int)))
              -> (((Int->Int)->(Int->Int)) -> ((Int->Int)->(Int->Int)))
    

    你给出的最后一个例子变成了这个怪物,分别有 order-6、order-5、order-4、order-3、order-2...

    {-# Language TypeApplications #-}
    
    five :: Int
    five = twice @((((Int->Int)->(Int->Int))->((Int->Int)->(Int->Int)))->(((Int->Int)->(Int->Int))->((Int->Int)->(Int->Int))))
             (twice @(((Int->Int)->(Int->Int))->((Int->Int)->(Int->Int))))
             (twice @((Int->Int)->(Int->Int)))
             (twice @(Int->Int))
             (twice @Int)
             (+ 1) 0
    

    所以这是第一个twice的类型!!

    twice @((((Int->Int)->(Int->Int))->((Int->Int)->(Int->Int)))->(((Int->Int)->(Int->Int))->((Int->Int)->(Int->Int))))
      :: (((((Int -> Int) -> Int -> Int) -> (Int -> Int) -> Int -> Int)
           -> ((Int -> Int) -> Int -> Int) -> (Int -> Int) -> Int -> Int)
          -> (((Int -> Int) -> Int -> Int) -> (Int -> Int) -> Int -> Int)
          -> ((Int -> Int) -> Int -> Int)
          -> (Int -> Int)
          -> Int
          -> Int)
         -> ((((Int -> Int) -> Int -> Int) -> (Int -> Int) -> Int -> Int)
             -> ((Int -> Int) -> Int -> Int) -> (Int -> Int) -> Int -> Int)
         -> (((Int -> Int) -> Int -> Int) -> (Int -> Int) -> Int -> Int)
         -> ((Int -> Int) -> Int -> Int)
         -> (Int -> Int)
         -> Int
         -> Int
    

    【讨论】:

    • 我真的很喜欢这个解释!有没有一种方便的方法可以在 GHCi 中显示这些类型参数和推断的子表达式类型?我还想指出,这里常用的术语是“顺序”(函数箭头的嵌套),而不是“等级”(forall 量词的嵌套)。这些类型都是 0 级的,因为它们不是多态的。虽然我觉得奇怪的是,我们很少提及特定的序数,除了“一阶”(1)与“更高阶”(≥2),“更高等级”和“更高种类”也是如此。
    • @JonPurdy 要知道id Trueid 的类型,可以写(asTypeOf _ id) True 并得到错误Found hole: _ :: Bool -> Bool(括号在这种特定情况下是多余的)。 IIRC,一些像 intero 这样的 IDE 可以在鼠标悬停时显示推断的类型。
    • 我写了twice @() 并得到了建议的修复。我建议 IDE 集成自动插入不可见的参数,我认为这对于很多人来说是一个秘密的绊脚石,当涉及到多态性时。感谢您的排名/订单捕获,已修复。
    猜你喜欢
    • 1970-01-01
    • 2020-04-17
    • 2019-07-08
    • 1970-01-01
    • 2012-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多