Skip to content

dev: 调整章节 #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
153 changes: 153 additions & 0 deletions src/basic/scripting-access.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#import "mod.typ": *

#show: book.page.with(title: "成员与方法")

// == 复合字面量

// ,它们是:
// + #term("array literal", postfix: "。")
// + #term("dictionary literal", postfix: "。")

Typst提供了一系列「成员」和「方法」访问字面量、变量与函数中存储的“信息”。

其实在上一节(甚至是第二节),你就已经见过了「成员」语法。<grammar-member-exp>你可以通过「点号」获得代码块的“text”(文本内容):

#code(```typ
#repr(`OvO`.text), #type(`OvO`), #type(`OvO`.text)
```)

每个类型有哪些「成员」是由Typst决定的。你需要逐渐积累经验以知晓这些「成员」的分布,才能更快地通过访问成员快速编写出收集和处理信息的脚本。(todo: 建议阅读《参考:XXX》)

当然,为防你不知道,大家不都是死记硬背的:有软件手段帮助你使用这些「成员」。许多编辑器都支持LSP(Language Server Protocol,语言服务),例如VSCode安装Tinymist LSP。当你对某个对象后接一个点号时,编辑器会自动为你做代码补全。

#figure(image("./IDE-autocomplete.png", width: 120pt), caption: [作者使用编辑器作代码补全的精彩瞬间。])

从图中可以看出来,该代码片段「对象」上有七个「成员」。特别是“text”成员赫然立于其中,就是它了。除了「成员」列表,编辑器还会告诉你每个「成员」的作用,以及如何使用。这时候只需要选择一个「成员」作为补全结果即可。

== 方法 <grammar-method-exp>

「方法」是一种特殊的「成员」。准确来说,如果一个「成员」是一个对象的函数,那么它就被称为该对象的「方法」。

来看以下代码,它们输出了相同的内容,事实上,它们是*同一*「函数调用」的不同写法:

#code(```typ
#let x = "Hello World"
#let str-split = str.split
#str-split(x, " ") \
#str.split(x, " ") \
#x.split(" ")
```)

第三行脚本含义对照如下。之前已经学过,这正是「函数调用」的语法:

```typ
#( str-split( x, " " ))
// 调用 字符串拆分函数,参数为 变量x和空格
```

与第三行脚本相比,第四行脚本仍然是在做「函数调用」,只不过在语法上更为紧凑。

第五行脚本则更加简明,此即「方法调用」。约定`str.split(x, y)`可以简写为`x.split(y)`,如果:
+ 对象`x`是`str`类型,且方法`split`是`str`类型的「成员」。
+ 对象`x`用作`str.split`调用的第一个参数。

「方法调用」即一种特殊的「函数调用」规则(语法糖),在各编程语言中广泛存在。其大大简化了脚本。但你也可以选择不用,毕竟「函数调用」一样可以完成所有任务。

#pro-tip[
这里有一个问题:为什么Typst要引入「方法」的概念呢?主要有以下几点考量。

其一,为了引入「方法调用」的语法,这种语法相对要更为方便和易读。对比以下两行,它们都完成了获取`"Hello World"`字符串的第二个单词的第一个字母的功能:

#code(
```typ
#"Hello World".split(" ").at(1).split("").at(1)
#array.at(str.split(array.at(str.split("Hello World", " "), 1), ""), 1)
```,
al: top,
)

可以明显看见,第二行语句的参数已经散落在括号的里里外外,很难理解到底做了什么事情。

其二,相比「函数调用」,「方法调用」更有利于现代IDE补全脚本。你可以通过`.split`很快定位到“字符串拆分”这个函数。

其三,方便用户管理相似功能的函数。不仅仅是字符串可以拆分,似乎内容及其他许多类型也可以拆分。如果一一为它们取不同的名字,那可就太头疼了。相比,`str.split`就简单多了。要知道,很多程序员都非常头痛为不同的变量和函数取名。
]

== 数组和字典的成员访问

为了访问数组,你可以使用`at`方法。“at”在中文里是“在”的意思,它表示对「数组」使用「索引」操作。`at(0)`索引到第1个值,`at(n)`索引到第 $n + 1$ 个值,以此类推。如下所示:

#code(```typ
#let x = (1, "OvO", [一段内容])
#x.at(0), #x.at(1), #x.at(2)
```)

至于「索引」从零开始的原因,这只是约定俗成。等你习惯了,你也会变成计数从零开始的好程序员。

为了访问字典,你可以使用`at`方法。但由于「键」都是字符串,你需要使用字符串作为字典的「索引」。

#code(```typ
#let cat = (attribute: [kawaii\~])
#cat.at("attribute")
```)

为了方便,Typst允许你直接通过成员方法访问字典对应「键」的值: <grammar-dict-member-exp>

#code(```typ
#let cat = ("attribute": [kawaii\~])
#cat.attribute
```)

== 数组和字典的「存在谓词」

为了访问数组,你可以使用`contains`方法。“contain”在中文里是“包含”的意思,如下所示:

#code(```typ
#let x = (1, "OvO", [一段内容])
#x.contains[一段内容]
```)

因为这太常用了,typst专门提供了`in`语法,表示判断`x`是否*存在于*某个数组中:<grammar-array-in>

#code(```typ
#([一段内容] in (1, "OvO", [一段内容])) \
#([另一段内容] in (1, "OvO", [一段内容]))
```)

字典也可以使用此语法,表示判断`x`是否是字典的一个「键」。特别地,你还可以前置一个`not`判断`x`是否*不在*某个数组或字典中:<grammar-dict-in>

#code(```typ
#let cat = (neko-mimi: 2)
#("neko-kiki" not in cat)
```)

注意:`x in (...)`与`"x" in (...)`是不同的。例如`neko-mimi in cat`将检查`neko-mimi`变量的内容是否是字典变量`cat`的一个「键」,而`"neko-mimi"`检查对应字符串是否在其中。

== 总结

// Typst如何保证一个简单函数甚至是一个闭包是“纯函数”?

// 答:1. 禁止修改外部变量,则捕获的变量的值是“纯的”或不可变的;2. 折叠的对象是纯的,且「折叠」操作是纯的。

// Typst的多文件特性从何而来?

// 答:1. import函数产生一个模块对象,而模块其实是文件顶层的scope。2. include函数即执行该文件,获得该文件对应的内容块。

// 基于以上两个特性,Typst为什么快?

// + Typst支持增量解析文件。
// + Typst所有由*用户声明*的函数都是纯的,在其上的调用都是纯的。例如Typst天生支持快速计算递归实现的fibnacci函数:

// #code(```typ
// #let fib(n) = if n <= 1 { n } else { fib(n - 1) + fib(n - 2) }
// #fib(42)
// ```)
// + Typst使用`include`导入其他文件的顶层「内容块」。当其他文件内容未改变时,内容块一定不变,而所有使用到对应内容块的函数的结果也一定不会因此改变。

// 这意味着,如果你发现了Typst中与一般语言的不同之处,可以思考以上种种优势对用户脚本的增强或限制。

#todo-box[总结]

== 习题

#todo-box[习题]
Loading