编程语言语言运行时编译器【免费下载链接】wrenThe Wren Programming Language. Wren is a small, fast, class-based concurrent scripting language.项目地址https://gitcode.com/gh_mirrors/wr/wren点击查看免费下载本文以 Wren 官方文档 functions.markdown 为骨架系统讲解 Wren 的函数Function体系如何用Fn.new创建函数、参数传递与返回值规则、闭包捕获外部变量的机制、可调用类Callable Class以及 Wren 特有的块参数Block Argument语法糖并结合仓库源码与测试用例印证底层实现。读完本文你将能够熟练编写、传递和调用 Wren 函数并理解其在列表迭代、事件回调等场景中的实际用法。函数与方法先厘清两个概念与许多现代语言一样Wren 中的函数Function是可以存储在变量中、或作为参数传递给方法的一小段代码。但在深入之前必须先区分两个易混淆的概念方法MethodWren 是面向对象语言绝大多数业务代码都写在类的实例方法或静态方法中函数Function游离于类之外的独立代码块同样非常实用。函数与 Wren 中的一切事物一样都是对象——具体来说是Fn类的实例。这一点在源码中有明确印证核心库 wren_core.wren 中Fn类被声明为空壳类所有行为由虚拟机原生方法提供class Fn {}而 wren_core.c 中的DEF_PRIMITIVE(fn_new)正是Fn.new的底层实现它校验传入参数是函数后直接原样返回该函数对象。创建函数Fn.new 与 .call()创建函数的唯一途径是调用Fn.new它接收一个代码块block作为参数这个代码块就是函数体。调用函数则使用函数实例上的.call()方法var sayHello Fn.new { System.print(hello) } sayHello.call() // hello这里有一个值得注意的细节Fn.new本身接收的也是一个块参数下文会详解构造器所做的全部工作就是把传入的函数原样返回。因此从源码角度看Fn.new { ... }等价于「创建函数并原样交还给你」。call方法是一族重载在 wren_core.c 中Fn类为call()注册了从 0 到 16 个参数共 17 个重载版本call(),call(_),call(_,_)……直到call(_,...,_)共 16 个下划线全部经由call_fn转发到wrenCallFunction执行对应闭包。函数参数|包裹的参数列表没有参数传递的函数价值有限。要给函数加参数只需在函数体左花括号之后紧跟一个用|竖线包裹的参数列表var sayMessage Fn.new {|recipient, message| System.print(message for %(recipient): %(message)) } sayMessage.call(Bob, Good day!)关于参数数量Wren 的规则如下参数过少是错误调用函数时传入的参数少于参数列表声明的数量会直接报错参数过多则忽略如果传入的参数比声明多多余的部分会被静默忽略。测试用例 parameters.wren 给出了从 0 到 16 个参数函数的完整验证序列例如var f2 Fn.new {|a, b| a b } System.print(f2.call(1, 2)) // expect: 3 var f16 Fn.new {|a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p| a b c d e f g h i j k l m n o p } System.print(f16.call(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)) // expect: 136这从侧面印证了call的 16 参数上限以及 Wren 对参数数量有着严格的编译期检查。返回值隐式返回与显式 return函数体本质上是一个代码块block其返回值规则取决于函数体的形态单表达式体隐式返回如果{或参数列表之后没有换行函数体是一个单一表达式函数会隐式返回该表达式的值多行体默认返回 null否则函数体默认返回null需要显式使用return语句返回某个值。换句话说下面两个函数完全等价Fn.new { return value } Fn.new { return return value }返回值会通过call()交还给你var fn Fn.new { some value } var result fn.call() System.print(result) // some value多行函数体默认返回null的行为在测试 newline_body.wren 与 empty_body.wren 中有明确验证var f Fn.new { // Hi. } System.print(f.call()) // expect: null闭包函数能记住定义时的环境与主流语言一致Wren 的函数是闭包Closure——它们可以访问定义在自身作用域之外的变量并且在离开定义作用域之后依然持有这些被捕获变量的引用可以持续读写。经典计数器示例class Counter { static create() { var i 0 return Fn.new { i i 1 } } }create方法返回了第二行创建的函数。该函数引用了在函数外部声明的变量i。即使函数已经从create返回它依然能够读取并修改ivar counter Counter.create() System.print(counter.call()) // 1 System.print(counter.call()) // 2 System.print(counter.call()) // 3从源码层面看闭包由编译器的 upvalue 机制实现。在 wren_compiler.c 中addUpvalue负责把外层作用域的局部变量登记为当前函数的 upvalue且同一变量只会登记一次而 wren_compiler.c 的resolveUpvalue会自动扁平化flatten多层嵌套闭包——即跨多层的闭包捕获会被逐层提升中间层的函数也会自动获得相应的 upvalue因此嵌套闭包也能正常工作。每个函数对象的numUpvalues字段见 wren_value.h记录了捕获的外部变量个数闭包在运行时以ObjClosure形式持有这些变量的共享存储。可调用类任何实现 call() 的类都能充当函数既然Fn是一个响应call()的类那么任何实现了call()方法的类都可以在函数的位置被使用。这在函数作为回调或事件处理器传给某个方法时尤其方便——你既可以传一个Fn实例也可以传一个自定义类的实例class Callable { construct new() {} call(name, version) { System.print(called %(name) with version %(version)) } } var fn Callable.new() fn.call(wren, 0.4.0)注意这里fn.call(...)走的是普通的实例方法派发而不是Fn的原生call族——Wren 的鸭子类型风格让「函数」与「可调用对象」之间的边界变得非常薄。块参数函数作为最后一个参数时的语法糖函数最常见的用途是被作为参数传给其他方法Wren 标准库中这类例子比比皆是例如 列表 的where方法接受一个过滤函数var list [1, 2, 3, 4, 5] var filtered list.where(Fn.new {|value| value 3 }) System.print(filtered.toList) // [4, 5]这种写法读起来、写起来都不够爽快于是 Wren 实现了**块参数block argument**概念当一个函数是传给方法的最后一个参数时可以省去Fn.new只写块的部分。上面的代码等价于var list [1, 2, 3, 4, 5] var filtered list.where {|value| value 3 } System.print(filtered.toList) // [4, 5]这就是我们在map、where等迭代方法中反复见到的写法numbers.map {|n| n * 2 }.where {|n| n 100 }完整实战块参数在事件回调中的应用让我们看一个完整的例子把「定义方」和「使用方」两端都串起来。下面是一个虚构的可点击控件类它既允许只传一个函数默认视为左键点击也允许同时传入按钮编号与函数class Clickable { construct new() { _fn null _button 0 } onClick(fn) { _fn fn } onClick(button, fn) { _button button _fn fn } fireEvent(button) { if(_fn button _button) { _fn.call(button) } } }先使用只接受函数的版本因为我们接受默认的左键按钮 0var link Clickable.new() link.onClick {|button| System.print(I was clicked by button %(button)) } // 发送一次左键点击 // 正常情况下来自其他事件源 link.fireEvent(0) // I was clicked by button 0再使用带按钮参数的版本var contextMenu Clickable.new() contextMenu.onClick(1) {|button| System.print(I was right-clicked) } link.fireEvent(0) // (nothing happened) link.fireEvent(1) // I was right-clicked注意除了最后一个参数走块语法其余参数仍然以普通方式传递只有最后一个参数是特殊的。块参数就是普通函数纯粹语法糖块参数完全等价于「创建函数并传参」这整坨语法下面两种写法等价onClick(Fn.new { System.print(clicked) }) onClick { System.print(clicked) }同样地把函数先存进变量再传也完全合法var onEvent Fn.new {|button| System.print(clicked by button %(button)) } onClick(onEvent) onClick(1, onEvent)Fn.new 自身的块参数正如前文所述Fn.new本身就接受一个块参数。其底层实现wren_core.c中有一行注释直白地写道The block argument is already a function, so just return it.块参数本身就是一个函数直接把它返回即可。DEF_PRIMITIVE(fn_new)做的事情就是校验后把args[1]原样返回——这正是Fn.new { ... }语法之所以成立的根源构造器只是一个「原样交还」的身份函数。小结函数即对象Wren 中的函数是Fn类的实例创建用Fn.new调用用.call()参数与返回值参数列表用|...|包裹参数不足是错误、多余参数被忽略单表达式体隐式返回多行体默认返回null闭包函数捕获外层变量跨层捕获会被编译器自动扁平化计数器是经典用例可调用类任何实现call()的类都能替代函数适合回调与事件场景块参数函数作为方法最后一个参数时可直接写块是Fn.new加传参的纯语法糖。更完整的函数边界情况可继续阅读 语言测试用例 与 closure 系列测试结合 函数文档原文 对照学习。接下来可以进入类与继承的学习。赞分享编程语言语言运行时编译器【免费下载链接】wrenThe Wren Programming Language. Wren is a small, fast, class-based concurrent scripting language.项目地址https://gitcode.com/gh_mirrors/wr/wren点击查看免费下载相关推荐agents-cli 与 Google Cloud Service Terms部署责任与计费说明新手完整指南agents cli 与 Google Cloud Service Terms部署责任与计费说明新手完整指南 agents cli 是谷歌开源的命令行工具CLIAI 应用开发者工具AI 技能XGo 函数与闭包完整指南从参数类型分组到关键字参数与 Lambda 表达式XGo 函数与闭包完整指南从参数类型分组到关键字参数与 Lambda 表达式 函数系统是 XGo 语言最核心的表达力来源之一。本篇指南基于仓库文档 doc/f编程语言编译器开发工具Vike框架终极指南如何在10分钟内构建稳定高效的模块化Web应用Vike框架终极指南如何在10分钟内构建稳定高效的模块化Web应用 你是否厌倦了被框架束缚的开发体验是否想要一个既能提供企业级稳定性又能给予你完全开发自由前端后端Web框架SSR上一篇ClassicSim战斗模拟器用数据驱动优化魔兽世界装备选择下一篇EIP-2458 解析为 active 状态 EIP 引入 updates 与 updated-by 头部字段的模块化更新提案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
