当前位置: 首页 > 知识库问答 >
问题:

Haskell Let表达式求值

孙星鹏
2023-03-14

我正在练习计算let表达式的问题,但我不理解这个表达式的输出。

下面是一个表达:

let a = 2
    b = 1:[i * 2 | i <- b]
    f a = 1:[i * a | i <- (f a)]
in take (a+2) (f (head (tail b) ))

输出应该是[1,2,4,8]。有人能一步一步地解释为什么这是输出吗

共有2个答案

曹建华
2023-03-14

为了了解发生了什么,我们在每个实体形成时仔细命名:

let a   = 2
    b   = 1 : [i * 2 | i <- b]
    f a = 1 : [i * a | i <- f a]
in  take (a+2) (f (head (tail b)))
==
let b        = (b1:bs1)
    (b1:bs1) = 1 : [i * 2 | i <- b]
in  take 4 (f (head (tail b)))
==
let b1  = 1
    bs1 = [i * 2 | i <- (b1:bs1)]
in  take 4 (f (head bs1))
==
let b1  = 1
    bs1 = [i * 2 | i <- [b1]] ++ [i * 2 | i <- bs1]
in  take 4 (f (head bs1))
==
let bs1 = [i * 2 | i <- [1]] ++ [i * 2 | i <- bs1]
in  take 4 (f (head bs1))
==
let bs1      = (b2:bs2)
    (b2:bs2) = [1 * 2] ++ [i * 2 | i <- bs1]
in  take 4 (f b2)
==
let (b2:bs2) = 2 : [i * 2 | i <- (b2:bs2)]
in  take 4 (f b2)
==
let bs2      =     [i * 2 | i <- (2:bs2)]
    f a      = 1 : [i * a | i <- f a]     -- same as before
in  take 4 (f 2)
==
let xs       = f 2
    f 2      = 1 : [i * 2 | i <- f 2] 
in  take 4 xs
==
let (x1:xs1) = 1 : [i * 2 | i <- f 2]
in  take 4 (x1:xs1)
==
let xs1      =     [i * 2 | i <- f 2]
in  take 4 (1:xs1)
==
let xs1      =     [i * 2 | i <- f 2]
in  1 : take 3 xs1
==
let (x2:xs2) =     [i * 2 | i <- (y1:ys1)]
    (y1:ys1) = 1 : [i * 2 | i <- f 2]
in  1 : take 3 (x2:xs2)
==
let (x2:xs2) =     [i * 2 | i <- (1:ys1)]
    ys1      =     [i * 2 | i <- f 2]
in  1 : take 3 (x2:xs2)
==
let (x2:xs2) = 2 : [i * 2 | i <- ys1]
    ys1      =     [i * 2 | i <- f 2]
in  1 : take 3 (x2:xs2)
==
let xs2      =     [i * 2 | i <- ys1]
    ys1      =     [i * 2 | i <- f 2]
in  1 : take 3 (2:xs2)
==
let xs2      =     [i * 2 | i <- ys1]
    ys1      =     [i * 2 | i <- f 2]
in  1 : 2 : take 2 xs2
==
let (x3:xs3) =     [i * 2 | i <- (y2:ys2)]
    (y2:ys2) =     [i * 2 | i <- (z1:zs1)]
    (z1:zs1) = 1 : [i * 2 | i <- f 2]
in  1 : 2 : take 2 (x3:xs3)
==
let (x3:xs3) =     [i * 2 | i <- (y2:ys2)]
    (y2:ys2) = 2 : [i * 2 | i <- zs1]
    zs1      =     [i * 2 | i <- f 2]
in  1 : 2 : take 2 (x3:xs3)
==
let (x3:xs3) = 4 : [i * 2 | i <- ys2]
    ys2      =     [i * 2 | i <- zs1]
    zs1      =     [i * 2 | i <- f 2]
in  1 : 2 : take 2 (x3:xs3)
==
let xs3      =     [i * 2 | i <- ys2]
    ys2      =     [i * 2 | i <- zs1]
    zs1      =     [i * 2 | i <- f 2]
in  1 : 2 : 4 : take 1 xs3
==
let (x4:xs4) =     [i * 2 | i <- (y3:ys3)]
    (y3:ys3) =     [i * 2 | i <- (z2:zs2)]
    (z2:zs2) =     [i * 2 | i <- (w1:ws1)]
    (w1:ws1) = 1 : [i * 2 | i <- f 2]
in  1 : 2 : 4 : take 1 (x4:xs4)
==
let (x4:xs4) =     [i * 2 | i <- (y3:ys3)]
    (y3:ys3) =     [i * 2 | i <- (z2:zs2)]
    (z2:zs2) = 2 : [i * 2 | i <- ws1]
    ws1      =     [i * 2 | i <- f 2]
in  1 : 2 : 4 : take 1 (x4:xs4)
==
let (x4:xs4) =     [i * 2 | i <- (y3:ys3)]
    (y3:ys3) = 4 : [i * 2 | i <- zs2]
    zs2      =     [i * 2 | i <- ws1]
    ws1      =     [i * 2 | i <- f 2]
in  1 : 2 : 4 : take 1 (x4:xs4)
==
let (x4:xs4) = 8 : [i * 2 | i <- ys3]
    ys3      =     [i * 2 | i <- zs2]
    zs2      =     [i * 2 | i <- ws1]
    ws1      =     [i * 2 | i <- f 2]
in  1 : 2 : 4 : take 1 (x4:xs4)
==
    1 : 2 : 4 : 8 : take 0 xs4
==
    1 : 2 : 4 : 8 : []

在上面的推导中,我们使用了列表理解的性质,其中

  [ ... | ... <- (xs   ++   ys)] 
=== 
  [ ... | ... <- xs ] ++ [ ... | ... <- ys]

所以说

  [ ... | ... <- (x : ys)] 
=== 
  [ ... | ... <- [x] ] ++ [ ... | ... <- ys]

fa产生的结果与iterate(*a)1相同,但在操作上有很大不同。后者是线性的,前者是二次函数,其时间复杂度为w.r.t。

要了解它在实践中的意义,请比较以下时间:

> f 1.01 !! 4000
1.9297236994732192e17
(1.28 secs, 1614556912 bytes)

> iterate (* 1.01) 1 !! 4000
1.9297236994732192e17
(0.00 secs, 12990984 bytes)
陆高峰
2023-03-14

下面是一个逐步的解释:

let a = 2
    b = 1:[i * 2 | i <- b]
    f a = 1:[i * a | i <- (f a)]
in take (a+2) (f (head (tail b) ))

其中有两个不同的变量称为a,一个与另一个相形见绌,因此首先让我们重命名其中一个,以避免意外地混淆它们:

let outer_a = 2
    b = 1:[i * 2 | i <- b]
    f a = 1:[i * a | i <- (f a)]
in take (outer_a+2) (f (head (tail b) ))

现在我们可以在中替换外部_a并评估

let b = 1:[i * 2 | i <- b]
    f a = 1:[i * a | i <- (f a)]
in take 4 (f (head (tail b) ))

根据map重写列表理解:

let b = 1:map (* 2) b
    f a = 1:map (* a) (f a)
in take 4 (f (head (tail b) ))

使用iterate代替显式递归:

let b = iterate (* 2) 1
    f a = iterate (* a) 1
in take 4 (f (head (tail b) ))

评估b的前两个步骤:

let b = 1:2:iterate (* 2) 4
    f a = iterate (* a) 1
in take 4 (f (head (tail b) ))

b中替换:

let f a = iterate (* a) 1
in take 4 (f (head (tail (1:2:iterate (* 2) 4)) ))

评估tail

let f a = iterate (* a) 1
in take 4 (f (head (2:iterate (* 2) 4) ))

评估head

let f a = iterate (* a) 1
in take 4 (f 2)

f a中替换:

take 4 (iterate (* 2) 1)

评估迭代几次:

take 4 (1:2:4:8:iterate (* 2) 16)

评估采取

[1,2,4,8]

我们结束了。

 类似资料:
  • 7.9. 示例: 表达式求值 在本节中,我们会构建一个简单算术表达式的求值器。我们将使用一个接口Expr来表示Go语言中任意的表达式。现在这个接口不需要有方法,但是我们后面会为它增加一些。 // An Expr is an arithmetic expression. type Expr interface{} 我们的表达式语言由浮点数符号(小数点);二元操作符+,-,*, 和/;一元操作符-x

  • 问题内容: 我有以下代码片段: 我知道在C ++中,您被教导不要依赖子表达式的求值顺序,因为它不能保证完全是任何顺序。因此,此代码将是错误的,并且不能保证条件中表达式所产生的布尔值是真实的(例如,可以在第一次等式测试中对y进行递增运算)。由于我是在Java认证书中阅读此代码的,因此我认为Java并非如此。.我的意思是,我是否保证Java的评估顺序始终是从左到右?因此,以上表达式应始终为true。

  • 我有以下代码片段: 我知道在C++中,你被教导不要依赖子表达式的求值顺序,因为它不能保证是任何顺序。因此这段代码是错误的,并且条件中的表达式所产生的布尔值不能保证为真(例如,在第一个相等性测试中计算y之前,可以增加y)。因为我是在Java认证书上读到这段代码的,所以我假设Java不是这样的。我的意思是,我能保证Java的计算顺序总是从左到右吗?所以上面的表达式应该总是产生true。

  • 8 表达式计算/求值 在计算表达式时,ffmpeg通过libavutil/eval.h接口调用内部计算器进行计算。 表达式可以包含一元运算符、运算符、常数和函数 两个表达式expr1和expr2可以组合起来成为”expr1;expr2” ,两个表达式都会被计算,但是新表达式(组合起来的)值实为表达式expr2的值。 表达式支持的二元运算符有:+,-,*,/,^ 一元运算符:+,- 以及下面的函数:

  • 本文向大家介绍OCaml 布尔表达式的求值,包括了OCaml 布尔表达式的求值的使用技巧和注意事项,需要的朋友参考一下 示例 我们定义布尔表达式的类型,其原子由字符串标识为 并可以通过oracle : string -> bool给出原子的值来评估这些表达式,如下所示: 了解该功能如何清晰易读。由于正确使用了模式匹配,因此读取此功能的程序员只需很少的时间即可确保其正确实现。

  • 我目前正在学习Haskell,突然遇到了这个表达式。 我的直觉是,由于用0除法,这将导致运行时错误,但从测试来看,情况并非如此。 我的结论是,这一定是由于Haskell内部的懒惰评估。因为匹配任何东西,所以它不会检查它与什么比较? 所以,如果有人能告诉我这个评估是否正确,如果不正确,为什么。另外,请详细说明在不实际查看表达式的情况下,案例行可以匹配的前提。