|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?注册
×
本帖最后由 ic_fisher 于 2025-12-21 01:23 编辑
写在开头
今天接着更新,这次是整理了函数定义相关的核心机制。这一部分信息量很大,我主要从函数本身的定义方式、参数使用技巧、变量作用域,以及类型检查和物理限制几个方面做了一次系统梳理,算是对官方文档的一次学习笔记式总结。文章同步更新到个人公众号——ic打工人摸鱼笔记,欢迎围观
一、SKILL 中的函数是什么
在 SKILL 里,“function” 和 “procedure” 是同义词——指一段可带参执行的代码块。它有两种存在形式:
- 函数对象:底层是字节码,可像普通数据一样被赋值、传参、比较。
- 符号绑定:比如你写 `procedure(trAdd(...))`,返回的是符号 `trAdd`,它背后绑定了那个函数对象。
三种函数类型:lambda / nlambda / macro
| 类型 | 特点 | 使用场景 | | lambda(默认) | 参数先求值,再传入函数体 | 99% 的自定义函数都用它 | | nlambda | 所有参数不求值,打包成列表传入 | 内置函数如 `quote`、`setq` 属于此类,用户几乎不用写 | | macro | 在编译期展开,返回新代码 | 自定义语法糖,如 `defmacro` |
⚠️ 注意:
- 别碰 `nprocedure` 和 `mprocedure`!文档明确说“仅用于兼容旧版”。
- 宏(macro)是在 compile time 执行的,不是 runtime!这意味着它不能访问运行时变量。
二、如何定义函数?四大语法结构详解
✅ 首选:`procedure`
最通用、最清晰的方式。返回函数名符号。
- procedure( trAdd( x y )
- printf( "Adding %d and %d … %d \n" x y x+y )
- x + y
- ) ; => 返回符号 trAdd
复制代码 procedure定义时候返回函数名符号,在调用时候则返回结果
调用:`trAdd(6, 7)` → 输出日志并返回 `13`。
🔧 匿名函数:`lambda`
返回一个 function object,适合一次性使用或作为回调。
- trAddFun = lambda( (x y)
- x + y
- ) ; => 返回 funobj:0x1814b90
- apply(trAddFun, '(4 5)) ; => 9
复制代码 典型用途:配合 `sort` 按属性排序:
- signalList = '((nil strength 1.5) (nil strength 0.4))
- sort(signalList, lambda((a b) a->strength <= b->strength));lambda后面定义了比较规则,直接给sort用
复制代码
💡 建议:除非是短小回调,否则优先用 `procedure` 命名函数,代码更易读。
🛠️ 宏定义:`defmacro`
用于自定义语法,在编译时展开。
示例:下面宏定义,定义了一种新语法,叫 `myIf`。作用就是提示编译器,以后凡是看到有人写 `myIf(A B C)`, 请在编译阶段, 把它自动改写成标准语法的 `(if A B C)`。
- defmacro(myIf(condition thenClause elseClause)
- `(if ,condition ,thenClause ,elseClause)
- );
复制代码
⚠️ 注意: 宏的参数是未求值的表达式,你在宏体内决定何时求值。
❌ 淘汰语法:`nprocedure` / `mprocedure`
用 `procedure` + `@rest` 或 `defmacro` 替代。
三、参数处理:四大 @ 修饰符实战
SKILL 的参数系统非常灵活,靠 `@` 开头的关键词实现。
1. `@rest`:接收任意数量参数
把多余参数打包成列表。
- procedure( trTrace( fun [url=home.php?mod=space&uid=84690]@rest[/url] args )
- let( (result)
- printf("Calling %s with %L\n" fun args)
- result = apply(fun, args)
- printf("Returned: %L\n" result)
- result
- )
- )
- trTrace('plus, 1, 2, 3)
- ; 输出:Calling plus with (1 2 3) → Returned: 6
复制代码
💡 `apply(func, argList)` 是关键, 它把列表展开为参数调用函数。
2. `@optional`:可选参数(按位置)
必须放在必填参数之后,支持默认值。
- procedure( trBuildBBox( height width @optional (xCoord 0) (yCoord 0) )
- list( xCoord:yCoord, (xCoord+width):(yCoord+height) )
- )
- trBuildBBox(1, 2) ; => ((0 0) (2 1))
- trBuildBBox(1, 2, 4) ; => ((4 0) (6 1))
复制代码
⚠️ 注意: 默认值写成 `(var default)` 形式;若未提供,默认为 `nil`。
3. `@key`:关键字参数(无视顺序)
用 `?key value` 方式传参,顺序自由。
- procedure( trBuildBBox( [url=home.php?mod=space&uid=316438]@key[/url] (height 0) (width 0) (xCoord 0) (yCoord 0) )
- list( xCoord:yCoord, (xCoord+width):(yCoord+height) )
- )
- trBuildBBox(?width 5 ?xCoord 10) ; => ((10 0) (15 0))
复制代码
⚠️ 重要规则:
- `@key` 和 `@optional` 不能共存
- 同一个 keyword 多次出现?只取第一个值,其余进 `@rest`
- defun(test(a @key x y @rest z) ...)
- (test 0 ?x 1 ?x 2)
- ; => a=0, x=1, y=nil, z=(?x 2)
复制代码
4. `@aux`:函数内局部辅助变量(SKILL++ 支持)
类似 `let`,但写在参数列表里。
- (defun myfunc(a b @key c @aux d e)
- ; d 和 e 是局部变量,初始为 nil(或指定值)
- )
复制代码
💡 语义等价于 `letseq`,适合初始化依赖前序参数的变量。
四、变量作用域:Local vs Global
Local 变量:用 `let` 或 `prog`
`let`:最常用
- procedure( trGetBBoxHeight( bBox )
- let( ( (ll car(bBox)) (ur cadr(bBox)) lly ury )
- lly = cadr(ll)
- ury = cadr(ur)
- ury - lly
- )
- )
复制代码 - `(var init)`:带初值
- `var`:默认 `nil`
- 不能在初始化表达式中引用同层其他变量
`prog`:支持 `go` 跳转和 `return` 多出口
除非需要循环或提前返回,否则用 `let` 更高效。
Global 变量:能不用就不用
风险:命名冲突 & 状态污染
两个独立应用若共用 `sharedGlobal`,执行顺序会影响结果
安全访问:先用 `boundp` 检查
- boundp('myGlobal) && myGlobal ; 安全读取
复制代码
命名规范(Cadence 推荐):
- 内部代码:`dmiPurgeVersions()`(小写前缀 + 大写首字母)
- 外部代码:`AcmeGlobalForm`(直接大写开头)
优化技巧:用 property list 聚合全局状态
- MyAppGlobals = makePropertyList()
- putprop(MyAppGlobals, 'count, 0)
- putprop(MyAppGlobals, 'config, "default");类似字典
复制代码
五、类型检查 & 物理限制
动态类型检查
在参数列表末尾加类型模板字符串:
- procedure( f(x y "nn") x**2 + y**2 ) ; nn = 两个数字
- procedure( cmp(str len "tx") strlen(str)==len ) ; t=string, x=integer
复制代码
复合类型字符:
- `n`:数字(整数/浮点)
- `S`:符号或字符串
物理硬限制
| 项目 | 限制 | | 必填参数总数 | < 65536 | | 可选/关键字参数 | < 255 | | `let` 中局部变量 | < 65536 | | 单函数代码大小 | 默认 < 32KB(约 2 万行) |
💡 超过 32KB?需设置:
- setSaveContextVersion(getNativeContextVersion())
复制代码
但会失去旧版本兼容性!
SKILL Lint 会提前报错:
> `definition for <func> cannot have more than 255 optional arguments.`
总结:
| 类别 | 关键字/函数 | 用途 | 注意事项 | | 函数定义 | `procedure` | 主力函数定义 | 返回符号 | | `lambda` | 匿名函数 | 返回 function object | | `defmacro` | 宏定义 | 编译期展开 | | 参数修饰 | `@rest` | 接收剩余参数 | 打包为列表 | | `@optional` | 可选参数(位置) | 需默认值 | | `@key` | 关键字参数(无序) | 与 `@optional` 互斥 | | `@aux` | 局部辅助变量 | SKILL++ 支持 | | 变量 | `let` | 局部变量 | 初始化安全 | | `boundp` | 检查全局变量是否绑定 | 防止 unbound 错误 | | 类型检查 | `"nn"`、`"tx"` | 参数类型模板 | 运行时检查 | | 淘汰语法 | `nprocedure`, `mprocedure` | —— | 禁止在新代码中使用 |
整理不易,如有遗漏错误,欢迎补充~
|
|