关于 moonbit 类型系统的更新想法

更新修改:

  • 在类型定义中添加=提高可读性
  • 修改了实现语法
  • 解释了设计思路
  • 添加了更多关于 & 对类型的影响
  • 修改了关于枚举项的设计
  • 添加了一个混入的例子
# 定义基础数据结构和泛型, type 后的第一个内容为类型名称
# | 符号后 @ 符号内容为枚举项名称
# `Option0 .| Option1 .` 作为 `Option0 @ () | Option1 @ ()`
# 的语法糖?
type MyType = Int | IsRunning @ Bool | MyStruct[T: Int | Uint | Bool] @ {
	typeName: match type T {
		Int | Uint => "Number",
		Bool => "Bool",
	},
	value: T,
} | MyArray @ Array[Int | Uint,8] | MyTuple @ Tuple[tring, Bool] | "A value can also be a type" | 1002 | 1003

# 只存在一个成员的 Tuple 必须使用
# Tuple 类型名称和泛型声明, 其他 Tuple 可以用括号简写
type Tuple0 = ()
type Tuple1 = Tuple[Int]
type Tuple2 = (Int, String)
type Tuple3 = (Int, String, Bool)
# ...

# 这样即使只是定义一个简单结构体或者类型别名也是符合直觉的
type Vec[T] = {
    this: T,
    next: Vec[T]
}

# 函数的定义
fn log_out_str(A: String) {
    print(A)
}

# 使用 pure 作为关键字定义纯函数
# 纯函数强制禁止使用造成副作用的内容
pure add[T: Int | Uint](A: T, B: T):T {
    A + B
}

# trait 是一个只有函数头没有函数体的定义
# Self 和 self 用于代指实现该 trait 的类型
# 的类型和对象, 作为语法糖 self 可以不用指定类型
trait io(self): Result[() | Self, String]

# 使用 pure 修饰使 trait 的实现只能为纯函数
pure trait showable(self): String

# trait 为类型定义添加上了更多限定
type MyResult[Ok, Err: showable & io] = (Ok | Err)

# 并列类型中的 trait 限定
# & 的优先级没有 | 大, 所以下面两段代码等价
type ShowableOrIO = JustShowable @ showable | JustIO @ io | Both @ (showable & io)
type ShowableOrIO = JustShowable @ showable | JustIO @ io | Both @ showable & io

# & 依旧可以用来限定类型
type ShowableNumIO = (Int | Uint) & showable & io
# 下两种类型等价
type Num = (Int | Uint | String) & (Int | Uint | Bool)
type Num = Int | Uint
# 下两种类型等价
type Node[T] = {
    this: [T]
    left: Node[T]
} & {
    left: Node[T]
    right: Node[T]
}
type Node[T] = {
    this: [T]
    left: Node[T]
    right: Node[T]
}
# 下两种类型等价
type Many = (Int, String) & (Int, Bool)
type Many = (Int, String, Int, Bool)
# 下两种类型等价
type = MyArray @ Array[Int, 8]
type = MyArray @ Array[Int | Uint, 8] & Array[Uint, 8]

# trait 限定也可以用在函数声明中
fn log_out_str_by_trait[T: io](A: T): Result[() | Self, String] {
    A.io()
}
pure to_string[T: showable](A: T): String {
    A.showable()
}

# trait 也可以用限定, 甚至可以添加函数体
# trait 的函数体是该 trait 的默认实现
trait out_as_string[Self: showable & io](self): Result[() | Self, String] {
    self.log_out_str_by_trait(self.to_string())
}

# 使用 impl 作为关键字为一个类型编写 trait 的更多实现
# Tuple2 的定义为 type Tuple2 (Int, String)
impl showable(self: Tuple2): String {
	val (num, str) = self;
    "(\(num), \(str))"
}

# 其他的想法
# - trait 和 trait 的实现如同函数一样也需要被引入才可以使用
# - 使用 func 替代 fn, 用来和 impl, pure 对其,
# 避免 fn 比 pure 少两个字母导致的 fn 乱用
# 私心上希望 非纯函数 的声明方式越复杂越好
# - trait 要是配合函数重载, 允许多个 trait 重名
# 可以大大提升代码多样性

设计思路:

  • 月兔的 OO 机制很有意思, 完全围绕单函数和类型绑定来设计的这个设计中的 trait 和类型设计也希望尽可能配合这个思想保留尽可能多的自由度
  • @ 符号的采用目的是为了和模式匹配中的 pin 操作符 @ 保持一致性
  • 面向 wasm 的月兔天生是跨平台的一把好手, 更零散的抽象代码可以更方便依赖单文件脚本或者较小的库来传播
  • 尽可能保持函数式的强大和 ml 的一些设计思路

抽象思路:

# lib/a.mbt
pure trait showable(self): String

trait outputInt[Self: Int & showable](self): Result[(), String] {
    print(self.showable())
}

trait callOutputInt[Self: outputInt](self) {
    val _ = self.outputInt()
}

# main/main.mbt
use @lib.a.(showable, outputInt, callOutputInt)

impl showable(self: Int): String {
	self.to_string()
}

fn init() {
    12.callOutputInt() // 12
}
# lib/moon.pkg.json
{
    "name": "a"
}
  • 你这里的 trait 看上去就是一个单方法的 Moonbit interface?很多时候还是会希望把几个方法 group 在一起,所以 interface 会更泛用一点。不过除了方法数量的区别,你这里的 trait 的功能和现在的 interface 应该基本是一样的。impl 的话,未来可能会加入这个语法。行为是普通地定义 impl 里的方法,然后确认这个类型确实已经实现了对应的 interface,否则报错
  • trait 先声明才能使用这一点,目前 Moonbit 没有区分 “用来实现 interface” 和 “只是用来 dot syntax 调用” 的 method,而后者是不需要提前声明的,所以目前 Moonbit 的所有 method 都不需要提前声明。不过未来有可能会有变动
  • 关于 pure:Moonbit 对副作用的态度是该用就用,所以应该不会去让 impure 的东西很难声明。type and effect system 如果强制手动标注(目前 Moonbit 的 toplevel 的类型是强制手动标注的),会有比较大的负担,所以暂时也没有考虑
  • 你的代码示例里有几个类型系统功能,例如 match type,union type 和 &(看上去像 intersection type,但又会把 tuple 给 concat 了,我不知道你想要的具体行为是什么样的),一旦和 generic 同时出现,对类型推导的破坏性非常大,所以应该不会加
  • 不过,像 f[X: Int | UInt] 这种,type union 形式的约束(Go 的 generics 提案就有类似的东西),我们有考虑过,也有可能会在未来出现
  • 你的一个例子的注释里提到了 type Tuple2 (Int, String) 这种语法,其实我们现在就在考虑用类似的语法做一个类似 Haskell newtype 的机制。不过可能做不到和原类型完全隐式地互相转换
2 个赞

其实我核心还是希望 intersection type 要是和 trait object 一起出现, 这样进行类型建模的时候自由度会高很多…
trait(或者 interface)设置为单方法是因为我觉得既然方法都可以是 (self, ...) => {}, 所以也希望把 impl 和 trait 也单方法话. 把类型和实现彻底隔离, 更加方便建模贫血模型.
我注意到月兔的类型系统很接近 GraphQL 的类型系统, 未来可以考虑将 GraphQL 的 Schema 建模纳入月兔的语法中, 这样就月兔在 Web 领域就多了一大优势

目前还没有 trait object,即使加 trait object 大概也会是 interface (Eq + Show + Compare) 这种形式,不会做类型层面的 intersection。直接在类型上 intersection 和参数多态互动会很麻烦。

1 个赞

看着Rust味很重,作为水平不高的我看这代码已经感到很劝退了。

我建议做成隐式类型系统自动判断,自动转换.

==****==
要兼容的话,

最好就是做个哈希表用来存储关键字文档代码的中间表示(数字形式,比如十进制或十六进制).

就像LLVM IR一样.

哈希表能保证性能, 中间表示能实现一种格式塔.(数字形式,比如十进制或十六进制)

就像16进制能当做人与计算机的中间桥梁一样,

我们现在的存储, 运算, 输入(软键盘), 输出(显示 字体 字符), 哪样不是经过16进制进行中间转换的.

中间表示(数字形式,比如十进制或十六进制),不但能解决各种编程语言之间的互译,还能解决各种自然语言之间的互译,

最终在编程语言与自然语言之间架起互译的桥梁,

若是把这种互译做成运行时, 就像JIT一样,静态的汇编器做成动态的实时运行在内存里的一个函数. 用什么语言的字符根本就不是问题,就算是一个程序里掺杂了各种自然语言的字符与编程语言的字符, 也不影响最后转换为16进制的程序的运行.

类型问题做个自动判断就是了,或者说软路由,让它自动路由就是, 就像支持隐式类型的编程语言实现.

如果你说的“隐式类型系统自动判断,自动转换”是指下面这种情况:

fn init {
  println(1 + 1.2) // 1被自动提升为 Double (Int -> Double)
}

这个特性仍在讨论中,更多的细节将在之后公布