You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[i8] list = null // x, null 不能赋值给 [i8] 类型
[i8] list // x, 这相当于 [i8] list = null
[i8]|null list // v, 此时 list 的值为 null
list = null // v, 允许的
string str // x,同上,这是不被允许的
string str = "" // v 这也是被允许的
string|null str // v 这是允许的
var s = str as string // v,当你明确 str 不包含 null 时,可以使用 as 语法进行断言
❗️as 同时也用于强制类型转换语法
有了 union types 就必须考虑其中的赋值操作。在没有想明白之前我们总是按照最严格的模式进行限制,严格限制意味着开放操作后总是可以兼容当前的操作。
string|null a
null|string b
a = b // x union type 不同,不允许进行相互赋值
string|null|bool a
string|null b
a = b // x
b = a // x
string|null a
string|null b
a = b // v
b = a // v
letfoo: number|string="hello"console.log(foo.length);// 5foo=24console.log(foo.length)// Property 'length' does not exist on type 'number'.
但是在编译形语言中,基本无法在编译时确定某一个阶段变量的值时多少,除非 foo 是一个不可变量。
int|string foo = "hello"
if (...) {
foo = "int"
} else {
foo = 24
}
// Is foo an int or a string?
所以我们必须能够检测出 foo 此时是什么类型才能进行具体的操作,所以这里首次引入类型判断的语法,大多数语言中使用 typeof(foo) == int 这样的判断,但是我希望能够重用类似 as 表达式基于类型的操作。我们使用 is 表达式来进行判断。
语法 bool b = foo is int ,判断 foo 的类型是否为 int 并返回一个 bool 类型的值。
int|string foo = "hello"
if (...) {
foo = "int"
} else {
foo = 24
}
if (foo is int && ... logic) {
// x 虽然你已经知道了 foo 此时就是 int 类型,但是编译器此时并不知道。
int bar = foo + 1 // x
int bar = (foo as int) + 1 // v
return
}
// 接下来又是一个语法糖
// 如果你已经明确知道了 foo 是 string 类型,并且后续需要频繁的操作 foo,并且不希望重新声明一个变量的名字,毕竟起名是一件很困难的事情
// 那么你可以明确的告诉编译器,后续请把 foo 当成 string 类型处理
// 其本质上等于 var foo = foo as int,但是如果你真的这么做,你会得到编译时的变量重复定义的错误
// let vs assert vs local 当然这是一个提案语法,我暂时优先选择最简短的 let
let foo as string
string bar = foo + "bar" // v, foo 此时明确为 bar 类型
foo = null // x, 此时 foo 具有明确的 string 类型,所以不可以再将 null 值赋值给 foo 变量。
语法 let foo as string 让 foo 在当前作用域中具备明确的 string 类型。就像注释说明的一样,其本质上就是 var foo = foo as string
但是需要注意的是
type foot = struct {
int|null bar
}
var foo = foot {
bar = 12
}
// 不能通过这种语法来让 foo.bar 作为 int 类型
// var foo.bar = foo.bar as int 是一种不合法的语法声明方式
let foo.bar as int // x
var bar = foo.bar as int // v
nullable 简化
T|null 作为 union types 最常用的一种情况,所以通常会给予一定的语法糖进行语法简化
string|null a 可以改写成 string? a 表示 a 允许为空。很多语言都选择了这么做。
💡 因为泛形语法还在思考中,基于泛形语法也许可以进行如 type nullable<T> = T|null 类似这样的简化。所以 T? 的方式是否需要支持还需要进一步确定。至少会延期到泛形语法开发时才会确定的进行支持
error handle
使用 union type 我们同样可以进行 error 处理。nature 中函数中总是包含一个返回值,且返回值的类型是确定的,但是由于 throw 语法的存在,所以函数并不总是返回确定的类型,其可能会返回一个 errort 类型,所以现在,对于任意一次函数调用,我们可能会得到类似于 type result<T> = T|errort 这样的返回值。
// 通过 catch 我们可以得到一个 union type, int|errort foo
// foo 可能是其中一种类型,所以可能会有如下的写法
var foo = catch call()
if (foo is errort) {
let foo as errort
// log and abort to user
log(foo.msg)
abort(foo.msg)
return
}
let foo as int
// normal handle ....
但是遇到错误时,我们可能需要这么判断,那能不能进一步进行语法书写上的优化呢?
既然 union type 同一时间总是表示一种类型,在编译形语言中我们又不能向 TS 一样做类型跟踪。那不妨牺牲一点空间,将 union type 转换为 product type,从而可以减少类型断言带来的编码负担。
Product type(积类型)是指将多个类型的值组合在一起形成一个新的类型。它由多个成员组成,每个成员都有其自己的类型。可以同时获取这些成员的值。常见的 Product type 的例子是 struct 或 tuple,其中可以同时包含多个具有不同类型的字段。
var result = catch call()
var (foo, err) = 💥 result
if (err) {
log(err.msg)
abort(err.msg)
return
}
var bar = foo + 1
nullable
这是 golang 中一个示例
在 golang 中 null 可以作为一个值赋值给任意的复合类型,所以最后一行
bar := a[0]
会产生一个运行时 panic,在编译时并不能很容易的检测出这种错误。在 golang 中一个复合类型的值是不是 null 只有我们的用户自己知道,当我们明确知道一个复合类型不为 null 时,我们可以放心的编写代码。在实际编码中,我们总是会和弱类型的语言如 mysql/json 打交道,比如使用 mysql 存储 nat 数据时,如果 nat 还没有探测出来,我们应该如何创建一条记录存储 nat 数据呢?
我依旧拿我比较熟悉的 golang 进行举例,看看如何在 golang 中如何存储 nat 数据
当一个值允许为 null 时,golang 中通常使用指针加类型存储这个数据,因为指针包含了 nil 的含义。
*nat
只要只要足够谨慎就完全没有问题,当你确定一个*nat
类型的数据一定不为 null 时,你可以放心的使用*nat
这样的操作,而不需要担心空指针引用的问题。所以其实 golang 给了用户最大的自由,让我们能够编写出足够简洁的代码。但越来越多的强类型语言已经将 null 值作为一个特殊的值进行处理,即不允许将 null 赋值给除了 null 以为的其他类型。这样虽然增加了代码编写的复杂度(需要键入更多的字符),但是可以很大程度上避免基于 null 引用而产生的运行时错误,我想这应该是值得的。
语法支持
那 nature 中应该怎么做呢?虽然在上一个版本中
[int] list = null
是被允许的和 golang 一样。但是我认为
*int8
不是一种合适的用来表达不存在的含义,我们应该使用更加明确的方式来表达某个值允许为 null,而不是使用*int8
去模拟这种情况。恰好 nature 目前还没有指针,那不妨学习一下 TS 中的实现,使用 union types 来表达一个值允许为 null。
nature 中使用 union type 表达 nullable,在 nature 中 null 的类型和值都适用关键字 'null' 表示
union types 将作为一种标准的语法进行支持,any 其实就是一种 union 了所有类型的 union type。类似的
type numbers = int|float|uint
也将是被允许的,但是有什么用还是未知数,毕竟目前 nature 没有基于类型类型的扩展函数的语法。对于复合类型,上一个版本其还有默认值 null,下一个版本将不会再支持啦 🙇
有了 union types 就必须考虑其中的赋值操作。在没有想明白之前我们总是按照最严格的模式进行限制,严格限制意味着开放操作后总是可以兼容当前的操作。
语法简化
ts 属于运行时的动态语言,所以其包含一个求值环境模型来追踪变量当前的实际类型,所以类似这样的语法是可以做到的
但是在编译形语言中,基本无法在编译时确定某一个阶段变量的值时多少,除非 foo 是一个不可变量。
所以我们必须能够检测出 foo 此时是什么类型才能进行具体的操作,所以这里首次引入类型判断的语法,大多数语言中使用 typeof(foo) == int 这样的判断,但是我希望能够重用类似 as 表达式基于类型的操作。我们使用 is 表达式来进行判断。
语法
bool b = foo is int
,判断 foo 的类型是否为 int 并返回一个 bool 类型的值。语法
let foo as string
让 foo 在当前作用域中具备明确的 string 类型。就像注释说明的一样,其本质上就是var foo = foo as string
但是需要注意的是
nullable 简化
T|null
作为 union types 最常用的一种情况,所以通常会给予一定的语法糖进行语法简化string|null a
可以改写成string? a
表示 a 允许为空。很多语言都选择了这么做。error handle
使用 union type 我们同样可以进行 error 处理。nature 中函数中总是包含一个返回值,且返回值的类型是确定的,但是由于 throw 语法的存在,所以函数并不总是返回确定的类型,其可能会返回一个 errort 类型,所以现在,对于任意一次函数调用,我们可能会得到类似于
type result<T> = T|errort
这样的返回值。但是我们应该时时刻刻的在每一次 call 时关心错误么?我觉得不需要,我们应该只关心我们能够处理的错误,对于不能处理或者预料之外的错误,我们没有必要去拦截或者处理它,应该将它继续向上传递,直到遇到一个能够处理这种错误的上级。
所以 nature 将选择一种和 golang 截然不同的更加传统的 try catch 的解决错误的方式。
作为一个 caller 当我能够处理可能的错误时,我将进行可能的错误的拦截,但并不是每一次调用都会产生错误,所以我需要进行适当的判断
但是遇到错误时,我们可能需要这么判断,那能不能进一步进行语法书写上的优化呢?
既然 union type 同一时间总是表示一种类型,在编译形语言中我们又不能向 TS 一样做类型跟踪。那不妨牺牲一点空间,将 union type 转换为 product type,从而可以减少类型断言带来的编码负担。
今天引入的语法有点多了,所以 💥 语法将暂时不会被集成到 nature 中。💥 将被集成在 catch 中。如下所示
The text was updated successfully, but these errors were encountered: