LV02-vimscript-06-函数.md
本文主要是vimscript一些基础知识和操作的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows | windows11 |
Ubuntu | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
点击查看本文参考资料
参考方向 | 参考原文 |
Learn vimscript the Hard Way | https://learnvimscriptthehardway.stevelosh.com/ |
vim在线文档 | http://vimcdoc.sourceforge.net/ |
VIM 中文帮助 | https://vimcdoc.sourceforge.net/doc/help.html |
vim教程 | https://www.w3cschool.cn/vim/ |
一、函数
接下来,就该了解一下函数了。官方文档关于函数说明在这里:VIM: eval - user-functions
在此之前需要先了解函数名称的规范:函数名须以大写字母开始,以免和内建函数引起混淆。要避免在不同脚本使用相同的名字,避免显见的或者过短的名字。一个好习惯是使用脚本名字作为函数名字的开头,比如HTMLcolor()
。
局部于脚本的函数必须以 s:
开始。局部于脚本的函数只能在同一脚本和脚本中定义的 函数、用户命令和自动命令里调用。也可以在脚本定义的映射里调用该函数,但必须使用<SID>
而不是s:
,如果映射会在脚本之外被扩展的话。 只有局部于脚本的函数,没有局部于缓冲区或局部于窗口的函数。
上边是Vim
中文文档中关于用户函数的一些说明,没有作用域限制的Vimscript
函数必须以一个大写字母开头!而且大多数的开发者都是这么做的,这样可以为自己避免一些不必要的麻烦。
二、基本语法
1. 一些命令
1 | :fu[nction] " 列出所有函数和它们的参数。 |
2. 定义新函数
1 | fu[nction][!] {name}([arguments]) [range] [abort] [dict] [closure] |
定义 {name}
命名的新函数。函数体在之后的行给出,直到匹配的endfunction
为止。
点击查看格式说明
详情可查看VIM: eval - function
name | 名字必须由字母数字和 '_' 字符组成,而且必须以大写字母或者 "s:" 开头. |
! | 如果同名的函数已经存在而且没有使用 [!],Vim会给出错误信息。如果给出 [!],已有的函数被悄然替代。如果该函数正在执行期间除外。此时,这是一个错误。 |
arguments | 参数的定义只要给出它的名字。在函数里,可以使用 "a:name" 来访问 ("a:" 代表参数(argument))。 |
range | 如果给出 [range] 参数,则该函数自己能理解并处理行范围。该范围通过 "a:firstline" 和 "a:lastline" 定义。如果没有 [range],":{range}call" 会在该范围的每一行分别执行该函数,每次光标都定位在处理行的行首。 |
abort | 如果给出 [abort] 参数,该函数在遇到错误时立即中止。 |
dict | 如果给出 [dict] 参数,该函数必须通过 Dictionary 的项目才能调用。局部变量 "self" 这时设为该字典。 |
closure | 加入 [closure] 参数时,函数可以访问外部作用域的变量和参数,通常这被称为闭包。 |
retu[rn] [expr] | 从函数返回。如果给出 "[expr]",计算该表达式的结果成为函数的返回值。如果没有给出 "[expr]",返回 0。 |
3. 删除函数
在Vim
中,提供了用户函数删除命令
1 | :delf[unction][!] {name} |
name
为要删除的函数的名称。
4. 函数调用
4.1 调用格式
1 | " 方法一: call命令调用 |
4.2 实例1:没有返回值
在text.vim
文件中添加以下内容:
1 | function Test() |
在命令模式中执行:source test.vim
可以看到输出结果如下:
1 | Hello, World! |
4.3 实例2:有返回值
在text.vim
文件中添加以下内容:
1 | function Test() |
在命令模式中执行:source test.vim
可以看到输出结果如下:
1 | Hello, World! |
第一个call test()
输出Hello, World!
,然而第二个却输出的是Hello, World! return Hello, World!
。我们使用call
时,返回值会被丢弃, 所以这种方法仅在函数具有副作用时才有用。
4.4 实例3:隐式返回
在text.vim
文件中添加以下内容:
1 | function Test() |
在命令模式中执行:source test.vim
可以看到输出结果如下:
1 | Hello, World! |
这将会显示两行:Hello, World!
和0
。第一个字符串显然来自于Test()
内部的echo
。第二个则告诉我们, 如果一个Vimscript
函数不返回一个值,它将隐式返回0
。那么这有什么用呢?在text.vim
文件中添加以下内容:
1 | " 检查文本宽度是否过宽函数,超过80返回1,否则隐式返回0 |
在命令模式中执行:source test.vim
可以看到输出结果如下:
1 | ok! |
从测试中看出,这样可以很方便的用于返回真或者假用于判断。
5. 函数参数
很多时候,函数是需要传入参数的,接下来就来看一看函数的传参。
5.1 普通参数
在text.vim
文件中添加以下内容:
1 | function DisplayName(name) |
在命令模式中执行:source test.vim
可以看到输出结果如下:
1 | Hello! My name is: |
看到这里,我可能会问,a:name
是啥个意思呢?往前看,在变量与表达式一文中有说明,a:var_name
表示函数参数(只限在函数内部使用)但是放我们去掉这个a:
的时候,会发现,重新执行时会产生错误:
1 | E121: 未定义的变量: name |
所以,我们在写需要参数的Vimscript
函数的时候,总是需要在参数加上前缀a:
,来告诉Vim
去参数作用域查找。
5.2 可变参数
在text.vim
文件中添加以下内容:
1 | function Varg(...) |
在命令模式中执行:source test.vim
可以看到输出结果如下:
1 | a:0= 3 |
【注意】
(1)函数使用可变参数时,不限制输入参数的个数。
(2)函数内部不可以引用未传入的参数,例如,函数内用a:4
来引用第四个参数,这个时候若是调用函数的时候未传入参数小于四个时,就会报错:
1 | E121: 未定义的变量: a:4 |
点击查看输出说明
... | 说明这个函数可以接受任意数目的参数。 |
a:0 | 当在Vim中定义了一个接受可变参数的函数时,a:0将被设置为我们额外给的参数数量。 |
a:1 | 可以使用a:1, a:2, a:3等等来引用函数接受的每一个额外参数。 |
a:000 | 该值将被设置为一个包括所有传递过来的额外参数的列表(list)。 |
5.3 同时使用
在text.vim
文件中添加以下内容:
1 | function Varg(name, ...) |
在命令模式中执行:source test.vim
可以看到输出结果如下:
1 | a:name= fanhua |
三、函数式编程
函数式编程?这是啥?看到这个词的第一眼,这就是我的反应,以前学的C
,Shell
里边没得啊。其实我也不知道啦,不过网上一搜有一大堆的解释,但是似乎都不明让我理解,但是可以参考这篇文档,解释的相对很明白了:函数式编程入门教程 - 阮一峰的网络日志
1. 不可变的数据结构
什么是不可变的数据结构?意思就是,以列表为例,在text.vim
文件中添加以下内容:
1 | function! Sorted(mylist) |
在命令模式中执行:source test.vim
可以看到输出结果如下:
1 | Before sort: mylist= [2, 1, 4, 3] |
我们会发现,我们使用了排序函数对列表进行排序,即便是用另一个变量接收结果,我们原始的列表也还是发生了变化。
但是这并不是我们的本意,原来的列表我们依然需要保留,那么这个时候,刚才定义的函数就排上了用场,Vim
的sort()
就原列表进行了重排列表,所以我们先创建一个列表的副本,并对副本进行排序, 这样原本的列表不会被改变。这样就避免了副作用,并帮助我们写出更容易推断和测试的代码。
- 更多辅助函数
1 | " 接受一个列表并返回一个新的倒置了元素的列表。 |
2. 作为变量的函数
Vimscript
支持使用变量储存函数,这似乎就跟C
中的指针函数一样。
2.1 变量存储函数
在text.vim
文件中添加以下内容:
1 | " 返回一个在原列表的基础上增加了给定值的新列表。 |
在命令模式中执行:source test.vim
可以看到输出结果如下:
1 | [1, 2, 3] |
无可厚非,最后显示了一个添加了元素的列表。
【注意】如果一个Vimscript
变量要引用一个函数,它就要以大写字母开头。
2.2 列表存储函数
我们不仅可以使用变量存储和调用函数,也可以通过列表。在text.vim
文件中添加以下内容:
1 | " 返回一个在原列表的基础上增加了给定值的新列表。 |
在命令模式中执行:source test.vim
可以看到输出结果如下:
1 | ['a', 'c'] |
【注意】当变量中存储的是列表的时候,首字母不必大写,列表的内容即使是函数,但是合在一起的时候终归还是一个列表,然后就可以通过列表索引来引用相应的函数。
3. 高阶函数
高阶函数就是接受别的函数并使用它们的函数。
3.1 map()
在text.vim
文件中添加以下内容:
1 | " 接受一个列表并返回一个新的倒置了元素的列表。 |
在命令模式中执行:source test.vim
可以看到输出结果如下:
1 | [[2, 1], [4, 3], [6, 8, 9, 7, 5]] |
上边的函数什么意思?接下来我们来分析一下。
点击查看map()函数说明
可以使用:help map()
来查看帮助文档。VIM: eval - map()
1 | map({expr1}, {expr2}) " {expr1} 里的每个项目被 {expr2} 的计算结果替代。 |
【说明】
(1){expr1}
必须是 List]
或 Dictionary
,{expr2}
必须是string
或 Funcref
。
(2)关于{expr2}
如果 {expr2}
是string
,{expr2}
内的 v:val
包含当前项目的值。 Dictionary
中 v:key
包含当前项目的键。List
中v:key
包含当前项目的索引(其中,v:val
就是List
或Dictionary
当前项目的值;v:key
就是Dictionary
当前项目的值)。例如:在text.vim
文件中添加以下内容:
1 | let mylist = [1, 2, 3] |
在命令模式中执行:source test.vim
可以看到输出结果如下:
1 | ['> 1 <', '> 2 <', '> 3 <'] |
如果 {expr2}
是Funcref
,它必须接受两个参数:当前项目的键或索引和当前项目的值。
点击查看string()函数说明
可以使用:help string()
来查看帮助文档。
1 | string({expr}) " 返回 {expr} 转换后的字符串。 |
【说明】如果 {expr}
为数值、浮点数、字符串、blob
或它们的复合形式,那么用 eval() 可以把结果转回去。
Mapped()
接受两个参数:一个funcref
(储存一个函数的变量)和一个列表。 我们用deepcopy()
创建新的列表,然后调用map()
函数,对传入列表的数据进行倒置,最后返回新倒置后的列表。
3.2 filter()
在text.vim
文件中添加以下内容:
1 | function! Filtered(fn, mylist) |
在命令模式中执行:source test.vim
可以看到输出结果如下:
1 | [[1, 2], ['fanhua']] |
点击查看filter()函数说明
可以使用:help filter()
来查看帮助文档。VIM: eval - filter()
1 | filter({expr1}, {expr2}) |
【说明】
(1){expr1}
必须是List
或者Dictionary
。{expr2}
必须是string
或 Funcref
。
(2)关于{expr2}
如果 {expr2}
是string
,{expr2}
内的 v:val
包含当前项目的值。 Dictionary
中 v:key
包含当前项目的键。List
中v:key
包含当前项目的索引(其中,v:val
就是List
或Dictionary
当前项目的值;v:key
就是Dictionary
当前项目的值)。例如:在text.vim
文件中添加以下内容:
1 | let mylist = [1, 2, 3, 'OLD', 'OLDh'] |
在命令模式中执行:source test.vim
可以看到输出结果如下:
1 | [1, 2, 3] |
如果 {expr2}
是Funcref
,它必须接受两个参数:当前项目的键或索引和当前项目的值。
点击查看len()函数说明
可以使用:help len()
来查看帮助文档。VIM: eval -len()
1 | len({expr}) " 返回数值,参数的长度。 |
expr | 返回值 |
字符串 | 返回它使用的字节数 |
数值 | 返回它使用的字节数 |
list | 返回 List 的项目数量 |
Blob | 返回字节数 |
Dictionary | 返回 Dictionary 的项目数量。 |
Filtered()
接受一个谓词函数和一个列表,它返回一个列表的副本, 而这个列表只包括将自身作为谓词函数的输入参数并返回真值的元素。 这里使用了内置的len()
,让它来过滤掉所有长度为0
的元素。
那如果我想要获取列表中长度为0
的元素呢?在text.vim
文件中添加以下内容:
1 | function! Removed(fn, mylist) |
在命令模式中执行:source test.vim
可以看到输出结果如下:
1 | [[], []] |
Vim
显示[[], []]
。Removed()
就像Filtered()
,不过它只保留谓词函数返回非真值的元素。代码中的唯一不同在于调用命令前面的'!' .
,它把谓词函数的结果取反。
3.3 效率
考虑到Vim
不得不持续地创建新的副本并垃圾回收旧的对象,可能会认为不停地制造副本是种浪费。是的,就是这样的!Vim
的列表不像Clojure
的vector
那样支持结构共享(structural sharing)
, 所以这里所有的复制操作是昂贵的。当需要使用庞大的列表,程序就会因此变慢。 在现实世界,我们可能会吃惊地发现自己几乎不会注意到其中的差别。