在 R 中评估期间会发生什么?
What happens during evaluation in R?
我对最基本的东西(求值)在 R 中的工作方式很感兴趣。
我是作为一名生物学家来到 R 的,但对与代码相关的一切都感兴趣,它仍然有点神秘。
我觉得我理解正确:
- 发生的一切都是函数调用(相同的参考)
- 什么是环境以及惰性求值的工作原理
- (或多或少)编译语言在编译过程中发生了什么。
但从技术上讲,当我们在 R 中求值时,当我们在一行(或多行)代码后按回车键时,幕后会发生什么?
我在核心团队的 R language definition 中找到了这个:
When a user types a command at the prompt (or when an expression is read from a file) the first thing that happens to it is that the command is transformed by the parser into an internal representation. The evaluator executes parsed R expressions and returns the value of the expression. All expressions have a value. This is the core of the language.
但它对我来说很深奥(尤其是粗体部分),小节也帮不了我解开这个问题。
我是否必须打开一本关于信息学的基础书籍才能理解这一点,或者是否有其他方法从技术上理解我每天 8 小时在做什么?
这将是一个不完整的答案,但您的问题似乎是关于 "internal representation." 本质上,R 的解析器采用任意 R 代码,删除不相关的内容(如多余的空格)并创建要评估的一组嵌套表达式。我们可以使用 pryr::call_tree()
来查看发生了什么。
采用仅使用数学运算符的简单表达式:
> 1 + 2 - 3 * 4 / 5
[1] 0.6
在这一系列操作中,会出现符合 R 优先级规则的输出。但实际上发生了什么?首先,解析器将输入的内容转换为 "expression":
> parse(text = "1 + 2 - 3 * 4 / 5")
expression(1 + 2 - 3 * 4 / 5)
这个表达式掩盖了更深层次的复杂性:
> library("pryr")
> call_tree(parse(text = "1 + 2 - 3 * 4 / 5"))
\- ()
\- `-
\- ()
\- `+
\- 1
\- 2
\- ()
\- `/
\- ()
\- `*
\- 3
\- 4
\- 5
这个表达式是四个函数的顺序求值,首先是"*"()
,然后是"/"()
,然后是"+"()
,然后是"-"()
。因此,这实际上可以重写为一个深层嵌套的表达式:
> "-"("+"(1,2), "/"("*"(3,4), 5))
[1] 0.6
> call_tree(parse(text = '"-"("+"(1,2), "/"("*"(3,4), 5))'))
\- ()
\- `-
\- ()
\- `+
\- 1
\- 2
\- ()
\- `/
\- ()
\- `*
\- 3
\- 4
\- 5
多行表达式也被解析为单独的表达式:
> parse(text = "1; 2; 3")
expression(1, 2, 3)
> parse(text = "1\n2\n3")
expression(1, 2, 3)
> call_tree(parse(text = "1; 2; 3"))
\- 1
\- 2
\- 3
然后评估这些调用树。
因此,当 R 的 read-eval-print 循环执行时,它会将在解释器中输入的代码或从文件中获取的代码解析到此调用树结构中,然后依次评估每个函数调用,然后打印结果,除非出现错误发生)。当无法完全评估可解析的代码行时会发生错误:
> call_tree(parse(text = "2 + 'A'"))
\- ()
\- `+
\- 2
\- "A"
并且当可键入的代码行无法解析为调用树时发生解析失败:
> parse(text = "2 + +")
Error in parse(text = "2 + +") : <text>:2:0: unexpected end of input
1: 2 + +
^
这不是一个完整的故事,但也许它能让你理解。
我对最基本的东西(求值)在 R 中的工作方式很感兴趣。
我是作为一名生物学家来到 R 的,但对与代码相关的一切都感兴趣,它仍然有点神秘。
我觉得我理解正确:
- 发生的一切都是函数调用(相同的参考)
- 什么是环境以及惰性求值的工作原理
- (或多或少)编译语言在编译过程中发生了什么。
但从技术上讲,当我们在 R 中求值时,当我们在一行(或多行)代码后按回车键时,幕后会发生什么?
我在核心团队的 R language definition 中找到了这个:
When a user types a command at the prompt (or when an expression is read from a file) the first thing that happens to it is that the command is transformed by the parser into an internal representation. The evaluator executes parsed R expressions and returns the value of the expression. All expressions have a value. This is the core of the language.
但它对我来说很深奥(尤其是粗体部分),小节也帮不了我解开这个问题。
我是否必须打开一本关于信息学的基础书籍才能理解这一点,或者是否有其他方法从技术上理解我每天 8 小时在做什么?
这将是一个不完整的答案,但您的问题似乎是关于 "internal representation." 本质上,R 的解析器采用任意 R 代码,删除不相关的内容(如多余的空格)并创建要评估的一组嵌套表达式。我们可以使用 pryr::call_tree()
来查看发生了什么。
采用仅使用数学运算符的简单表达式:
> 1 + 2 - 3 * 4 / 5
[1] 0.6
在这一系列操作中,会出现符合 R 优先级规则的输出。但实际上发生了什么?首先,解析器将输入的内容转换为 "expression":
> parse(text = "1 + 2 - 3 * 4 / 5")
expression(1 + 2 - 3 * 4 / 5)
这个表达式掩盖了更深层次的复杂性:
> library("pryr")
> call_tree(parse(text = "1 + 2 - 3 * 4 / 5"))
\- ()
\- `-
\- ()
\- `+
\- 1
\- 2
\- ()
\- `/
\- ()
\- `*
\- 3
\- 4
\- 5
这个表达式是四个函数的顺序求值,首先是"*"()
,然后是"/"()
,然后是"+"()
,然后是"-"()
。因此,这实际上可以重写为一个深层嵌套的表达式:
> "-"("+"(1,2), "/"("*"(3,4), 5))
[1] 0.6
> call_tree(parse(text = '"-"("+"(1,2), "/"("*"(3,4), 5))'))
\- ()
\- `-
\- ()
\- `+
\- 1
\- 2
\- ()
\- `/
\- ()
\- `*
\- 3
\- 4
\- 5
多行表达式也被解析为单独的表达式:
> parse(text = "1; 2; 3")
expression(1, 2, 3)
> parse(text = "1\n2\n3")
expression(1, 2, 3)
> call_tree(parse(text = "1; 2; 3"))
\- 1
\- 2
\- 3
然后评估这些调用树。
因此,当 R 的 read-eval-print 循环执行时,它会将在解释器中输入的代码或从文件中获取的代码解析到此调用树结构中,然后依次评估每个函数调用,然后打印结果,除非出现错误发生)。当无法完全评估可解析的代码行时会发生错误:
> call_tree(parse(text = "2 + 'A'"))
\- ()
\- `+
\- 2
\- "A"
并且当可键入的代码行无法解析为调用树时发生解析失败:
> parse(text = "2 + +")
Error in parse(text = "2 + +") : <text>:2:0: unexpected end of input
1: 2 + +
^
这不是一个完整的故事,但也许它能让你理解。