在线咨询 切换到宽版
eetop公众号 创芯大讲堂 创芯人才网

 找回密码
 注册

手机号码,快捷登录

手机号码,快捷登录

搜全文
查看: 78|回复: 0

[原创] SKILL 函数定义全解析:从 Procedure 到 Macro,参数与作用域雷区

[复制链接]
发表于 昨天 01:23 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?注册

×
本帖最后由 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`
最通用、最清晰的方式。返回函数名符号。


   

        

                
  1. procedure( trAdd( x y )
  2.   printf( "Adding %d and %d … %d \n" x y x+y )
  3.   x + y
  4. ) ; => 返回符号 trAdd
            

   

    复制代码
procedure定义时候返回函数名符号,在调用时候则返回结果
调用:`trAdd(6, 7)` → 输出日志并返回 `13`。

🔧 匿名函数:`lambda`
返回一个 function object,适合一次性使用或作为回调。


   

        

                
  1. trAddFun = lambda( (x y)
  2.   x + y
  3. ) ; => 返回 funobj:0x1814b90

  4. apply(trAddFun, '(4 5)) ; => 9
            

   

    复制代码
典型用途:配合 `sort` 按属性排序:


   

        

                
  1. signalList = '((nil strength 1.5) (nil strength 0.4))
  2. sort(signalList, lambda((a b) a->strength <= b->strength));lambda后面定义了比较规则,直接给sort用
            

   

    复制代码


   
💡 建议:除非是短小回调,否则优先用 `procedure` 命名函数,代码更易读。


🛠️ 宏定义:`defmacro`
用于自定义语法,在编译时展开。
示例:下面宏定义,定义了一种新语法,叫 `myIf`。作用就是提示编译器,以后凡是看到有人写 `myIf(A B C)`, 请在编译阶段, 把它自动改写成标准语法的 `(if A B C)`。


   

        

                
  1. defmacro(myIf(condition thenClause elseClause)
  2.   `(if ,condition ,thenClause ,elseClause)
  3. );
            

   

    复制代码


   
⚠️ 注意: 宏的参数是未求值的表达式,你在宏体内决定何时求值。


❌ 淘汰语法:`nprocedure` / `mprocedure`
用 `procedure` + `@rest` 或 `defmacro` 替代。

三、参数处理:四大 @ 修饰符实战

SKILL 的参数系统非常灵活,靠 `@` 开头的关键词实现。
1. `@rest`:接收任意数量参数
把多余参数打包成列表。


   

        

                
  1. procedure( trTrace( fun [url=home.php?mod=space&uid=84690]@rest[/url] args )
  2.   let( (result)
  3.     printf("Calling %s with %L\n" fun args)
  4.     result = apply(fun, args)
  5.     printf("Returned: %L\n" result)
  6.     result
  7.   )
  8. )

  9. trTrace('plus, 1, 2, 3)
  10. ; 输出:Calling plus with (1 2 3) → Returned: 6
            

   

    复制代码


   
💡 `apply(func, argList)` 是关键, 它把列表展开为参数调用函数。


2. `@optional`:可选参数(按位置)
必须放在必填参数之后,支持默认值。


   

        

                
  1. procedure( trBuildBBox( height width @optional (xCoord 0) (yCoord 0) )
  2.   list( xCoord:yCoord, (xCoord+width):(yCoord+height) )
  3. )

  4. trBuildBBox(1, 2)        ; => ((0 0) (2 1))
  5. trBuildBBox(1, 2, 4)     ; => ((4 0) (6 1))
            

   

    复制代码


   
⚠️ 注意: 默认值写成 `(var default)` 形式;若未提供,默认为 `nil`。


3. `@key`:关键字参数(无视顺序)
用 `?key value` 方式传参,顺序自由。


   

        

                
  1. procedure( trBuildBBox( [url=home.php?mod=space&uid=316438]@key[/url] (height 0) (width 0) (xCoord 0) (yCoord 0) )
  2.   list( xCoord:yCoord, (xCoord+width):(yCoord+height) )
  3. )

  4. trBuildBBox(?width 5 ?xCoord 10) ; => ((10 0) (15 0))

  5.         

   

    复制代码


   
⚠️ 重要规则:
- `@key` 和 `@optional` 不能共存
- 同一个 keyword 多次出现?只取第一个值,其余进 `@rest`



   

        

                
  1. defun(test(a @key x y @rest z) ...)
  2. (test 0 ?x 1 ?x 2)
  3. ; => a=0, x=1, y=nil, z=(?x 2)

  4.         

   

    复制代码


4. `@aux`:函数内局部辅助变量(SKILL++ 支持)
类似 `let`,但写在参数列表里。


   

        

                
  1. (defun myfunc(a b @key c @aux d e)
  2.   ; d 和 e 是局部变量,初始为 nil(或指定值)
  3. )
            

   

    复制代码


   
💡 语义等价于 `letseq`,适合初始化依赖前序参数的变量。


四、变量作用域:Local vs Global
Local 变量:用 `let` 或 `prog`
`let`:最常用


   

        

                
  1. procedure( trGetBBoxHeight( bBox )
  2.   let( ( (ll car(bBox)) (ur cadr(bBox)) lly ury )
  3.     lly = cadr(ll)
  4.     ury = cadr(ur)
  5.     ury - lly
  6.   )
  7. )

  8.         

   

    复制代码
- `(var init)`:带初值
- `var`:默认 `nil`
- 不能在初始化表达式中引用同层其他变量

`prog`:支持 `go` 跳转和 `return` 多出口
除非需要循环或提前返回,否则用 `let` 更高效。

Global 变量:能不用就不用
风险:命名冲突 & 状态污染
两个独立应用若共用 `sharedGlobal`,执行顺序会影响结果

安全访问:先用 `boundp` 检查


   

        

                
  1. boundp('myGlobal) && myGlobal  ; 安全读取

  2.         

   

    复制代码


命名规范(Cadence 推荐):

- 内部代码:`dmiPurgeVersions()`(小写前缀 + 大写首字母)
- 外部代码:`AcmeGlobalForm`(直接大写开头)
优化技巧:用 property list 聚合全局状态


   

        

                
  1. MyAppGlobals = makePropertyList()
  2. putprop(MyAppGlobals, 'count, 0)
  3. putprop(MyAppGlobals, 'config, "default");类似字典

  4.         

   

    复制代码


   
⚠️ 缺点: 属性列表过长会降低访问速度。


五、类型检查 & 物理限制

动态类型检查
在参数列表末尾加类型模板字符串:


   

        

                
  1. procedure( f(x y "nn") x**2 + y**2 )   ; nn = 两个数字
  2. procedure( cmp(str len "tx") strlen(str)==len ) ; t=string, x=integer

  3.         

   

    复制代码

复合类型字符:
- `n`:数字(整数/浮点)
- `S`:符号或字符串


   
⚠️ `mprocedure` 不支持类型检查


物理硬限制
项目限制
必填参数总数< 65536
可选/关键字参数< 255
`let` 中局部变量< 65536
单函数代码大小默认 < 32KB(约 2 万行)



   
💡 超过 32KB?需设置:


   

        

                
  1. setSaveContextVersion(getNativeContextVersion())

  2.         

   

    复制代码

但会失去旧版本兼容性!

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`——禁止在新代码中使用

整理不易,如有遗漏错误,欢迎补充~


您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

站长推荐 上一条 /2 下一条

手机版| 小黑屋| 关于我们| 联系我们| 用户协议&隐私声明| 版权投诉通道| EETOP 创芯网
( 京ICP备:10050787号 京公网安备:11010502037710 ) |网站地图

GMT+8, 2025-12-22 02:16 , Processed in 0.015934 second(s), 3 queries , Gzip On, Redis On.

eetop公众号 创芯大讲堂 创芯人才网
快速回复 返回顶部 返回列表