Tolk vs FunC:详细介绍
下面是一份非常庞大的清单。有人有足够的耐心读到最后吗?
✅ Traditional comments :)
FunC | Tolk |
---|---|
;; comment | // comment |
{- multiline comment -} | /* multiline comment */ |
✅ 2+2
是 4,不是标识符。标识符只能是字母数字
在 FunC 中,几乎所有字符都可以作为标识符的一部分。
例如,2+2
(不含空格)就是一个标识符。
你甚至可以用这样的名称声明一个变量。
在 Tolk 中,空格不是必须的。2+2
是 4,如所料。3+~x
是 3 + (~ x)
,以此类推。
FunC | Tolk |
---|---|
return 2+2; ;; undefined function `2+2` | return 2+2; // 4 |
更确切地说,一个标识符可以从 [a-zA-Z$_]
开始,并由 [a-zA-Z0-9$_]
继续。请注意,?
、:
和其他符号都不是有效的符号,found?
和 op::increase
也不是有效的标识符。
您可以使用反标包围标识符,然后它可以包含任何符号(类似于 Kotlin 和其他一些语言)。它的潜在用途是允许将关键字用作标识符,例如在使用方案生成代码时。
FunC | Tolk |
---|---|
const op::increase = 0x1234; | const OP_INCREASE = 0x1234; |
;; even 2%&!2 is valid | // don't do like this :) |
✅ 默认情况下不纯净,编译器不会放弃用户函数调用
FunC 有一个 impure
函数指定符。如果没有,函数将被视为纯函数。如果其结果未被使用,则编译器删除了其调用。
虽然这种行为已经记录在案,但对于新手来说,还是非常出乎意料。 例如,各种不返回任何内容的函数(如在不匹配时抛出异常), ,都会被默默删除。FunC 不检查和验证函数体, 允许在纯函数内部进行不纯净的操作,从而破坏了这种情况。
在 Tolk,默认所有功能都是不纯洁的。 你可以用注释标记纯函数, 然后禁止其身体中的不纯操作(异常、全局修改、 调用非纯函数等)。
✅ 新函数语法:fun
关键字、@
属性、右侧的类型(如 TypeScript、Kotlin、Python 等)
FunC | Tolk |
---|---|
cell parse_data(slice cs) { } | fun parse_data(cs: slice): cell { } |
(cell, int) load_storage() { } | fun load_storage(): (cell, int) { } |
() main() { ... } | fun main() { ... } |
变量类型 - 也在右侧:
FunC | Tolk |
---|---|
slice cs = ...; | var cs: slice = ...; |
(cell c, int n) = parse_data(cs); | var (c: cell, n: int) = parse_data(cs); |
global int stake_at; | global stake_at: int; |
修改器 inline
及其他 - 带注释 :
FunC | Tolk |
---|---|
| @inline |
| @inline_ref |
global int stake_at; | global stake_at: int; |
forall
- 是这样的:
FunC | Tolk |
---|---|
forall X -> tuple cons(X head, tuple tail) | fun cons<X>(head: X, tail: tuple): tuple |
asm
实现--与 FunC 中一样,但由于正确对齐,看起来更漂亮:
@pure
fun third<X>(t: tuple): X
asm "THIRD";
@pure
fun iDictDeleteGet(dict: cell, keyLen: int, index: int): (cell, slice, int)
asm(index dict keyLen) "DICTIDELGET NULLSWAPIFNOT";
@pure
fun mulDivFloor(x: int, y: int, z: int): int
builtin;
还有一个 @deprecated
属性,不影响编译,但可用于人和 IDE。
✅ get
代替 method_id
在 FunC 中,method_id
(不含参数)实际上声明了一个 get 方法。而在 Tolk 中,使用的是简单明了的语法:
FunC | Tolk |
---|---|
int seqno() method_id { ... } | get seqno(): int { ... } |
get methodName()
和 get fun methodName()
都是可以接受的。
对于 method_id(xxx)
(在实践中不常见,但有效),有一个属性:
FunC | Tolk |
---|---|
| @method_id(1666) |
✅ 必须声明参数类型(尽管本地参数可有可无)
// not allowed
fun do_smth(c, n)
// types are mandatory
fun do_smth(c: cell, n: int)
有一种 auto
类型,因此 fun f(a: auto)
是有效的,但不推荐使用。
如果参数类型是强制性的,则返回类型不是(这通常是显而易见的啰嗦)。如果省略,则表示 "自动":
fun x() { ... } // auto infer return
对于局部变量,类型也是可选的:
var i = 10; // ok, int
var b = beginCell(); // ok, builder
var (i, b) = (10, beginCell()); // ok, two variables, int and builder
// types can be specified manually, of course:
var b: builder = beginCell();
var (i: int, b: builder) = (10, beginCell());
✅ 不允许在同一作用域中重新声明变量
var a = 10;
...
var a = 20; // error, correct is just `a = 20`
if (1) {
var a = 30; // it's okay, it's another scope
}
因此,不允许部分重新分配:
var a = 10;
...
var (a, b) = (20, 30); // error, releclaration of a
请注意,这对 loadUint()
和其他方法来说不是问题。在 FunC 中,它们返回一个修改后的对象,因此 var (cs, int value) = cs.load_int(32)
这种模式非常常见。在 Tolk 中,此类方法会改变对象:var value = cs.loadInt(32)
,因此不太可能需要重新声明。
fun send(msg: cell) {
var msg = ...; // error, redeclaration of msg
// solution 1: intruduce a new variable
var msgWrapped = ...;
// solution 2: use `redef`, though not recommended
var msg redef = ...;
✅ 类型系统的变化
Tolk 第一个版本中的类型系统与 FunC 中的相同,但做了以下修改:
void
实际上是一个空张量(命名为unit
更规范,但void
更可靠);另外,return
(不含表达式)实际上是return()
,是从 void 函数返回的一种方便方式。
fun setContractData(c: cell): void
asm "c4 POP";
auto
表示 "自动推断";在 FunC 中,_
用于此目的;注意,如果函数没有指定返回类型,它就是auto
,而不是void
。self
,以创建可链式方法,如下所述;实际上,它不是一种类型,它只能出现在函数中,而不是函数的返回类型中cont
更名为continuation
✅ recv_internal / recv_external 的另一种命名方式
fun onInternalMessage
fun onExternalMessage
fun onTickTock
fun onSplitPrepare
fun onSplitInstall
所有参数类型及其顺序重命名不变,只是命名有所改变。fun main
也可用。
✅ #include → import.严格导入
FunC | Tolk |
---|---|
#include "another.fc"; | import "another.tolk" |
在 Tolk 中,如果不导入该文件,就无法使用 a.tolk
中的符号。换句话说,就是 用什么导入什么