共计 6889 个字符,预计需要花费 18 分钟才能阅读完成。
设计背景
几年前我在开发多平台的 XyKey 时,因为过后的跨平台计划还未成熟,所以我没有抉择跨平台实现,而是抉择了每个平台都应用官网指定的语言进行开发,为此我接触了 Java/Kotlin(Android)、Swift/OC(iOS)、C#(UWP)。于此同时我本职工作方向是区块链技术,现有支流区块链计划也大量应用了 JS + Go 的组合开发前后端产品。
在不同语言之间来回切换学习之后,我对不同语言表达同一种性能的语法差异性产生了趣味,随后开始了语法设计方面的钻研摸索,最终诞生了 Feel 语言。
语言设计问题之一
很多语言外面,函数存在不止一种表达方法。
咱们须要为同样的需要设计不同的语法吗?
以下我举一些我应用过的语言中函数的示意办法,所有的 eg 都是函数。
Go: 大部分时候函数都应用 func 结尾申明,算是一致性比拟好的设计之一,但在 interface 中还是应用了不一样的形容形式。
func eg1(x int) int {return 1}
var eg2 = func(x int) int {return 1}
type foo struct {eg3 func(int) int
}
type bar interface {eg4(int) int
}
C#: 大部分时候都应用了 C 式的形容形式,但在函数类型中应用了反直觉的泛型类型,并且 Lambda 语法也看不出与函数的分割。
int eg1(int x)
{Func<int,int> eg2 = (int x) =>
{return 1;};
return 1;
}
interface foo
{int eg3(int x);
}
Action<int> eg4;
Kotlin: 函数定义、函数类型、Lambda 是三种格调。
fun eg1(x: Int): Int {val eg2: (Int) -> Int = { i ->
1
}
return 1
}
val eg3 = fun(x: Int): Int {return 1}
interface name {fun eg4(x: Int)
val eg5: (Int) -> Unit
}
Swift: swift 比拟好的中央是函数定义和函数类型应用了同样的箭头示意,但在 Lambda 中却应用了 in 来宰割。
func eg1(x: Int) -> Int {let eg2: (Int) -> Int = { i in
return 1
}
return 1
}
protocol name {func eg3(x: Int)
}
var eg4: (Int) -> ()
为一个资源绑定一个名称,同样也有很多不同的写法。
以下我举一些我应用过的语言中标识符示意办法,所有的 eg 都是某个资源的标识符。
Swift: 为不同类型绑定标识符应用不同前缀,算是比拟好的实际之一。
var eg1 = 1
let eg2 = 1
func eg3() {}
class eg4 {}
protocol eg5 {}
Go: 变量、常量和函数应用了一种格调,定义类型应用了另一种格调。
var eg1 = 1
const eg2 = 1
func eg3() {}
type eg4 struct{}
type eg5 interface{}
C#:类和接口应用了一种格调,变量、常量、函数应用了三种不同的格调。
int eg1 = 1
const int eg2 = 2
int eg3() {}
class eg4 {}
interface eg5 {}
语言设计问题之二
一旦咱们开始开发一个具备肯定规模的我的项目,就肯定会反复强调编码标准的重要性。模式不一的代码格调会给咱们的合作带来不小的艰难。
如果标准如此重要,咱们是否应该在语言级别强制?
上面我给出几种不同的代码格调,在不对立格调的状况下,咱们可能会见到如下几种代码并存:
if (foo == 0)
{print(foo)
}
else if (foo == 1)
{print(foo)
}
else
{print(foo)
}
//////////////
if (foo == 0) {print(foo)
}
else if (foo == 1) {print(foo)
}
else {print(foo)
}
//////////////
if (foo == 0) {print(foo)
} else if (foo == 1) {print(foo)
} else {print(foo)
}
当然,也可能有人会写出上面这样的:
if (foo == 0)
{print(foo)
}
else if (foo == 1)
{print(foo)
}
else
{print(foo)
}
//////////////
if (foo == 0) print(foo)
else if (foo == 1) print(foo)
else print(foo)
语言设计问题之三
一些语言是动静的,一些语言是动态的,它们各有各的好,然而咱们却尝试在动静语言中退出动态个性(TypeScript),也尝试在动态语言中退出动静个性(Go)。
这动态与动静两头是否存在一个均衡的计划?
TypeScript:通过隐式接口,给动静类型加上类型查看,只有满足接口要求的对象能力被应用。
interface LabelledValue {label: string;}
function printLabel(labelledObj: LabelledValue) {console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
Go:通过隐式接口,实现了动态鸭子类型,只有满足函数签名要求的对象能力被应用。
type LabelledValue interface {Label() string
}
func printLabel(labelledObj LabelledValue) {println(labelledObj.Label())
}
type Obj struct {size int}
func (this Obj) Label() string {return "Size 10 Object"}
语言设计问题之四
不同语言的关键字有多有少,但实际上大部分语言都能够实现图灵齐备。
咱们须要很多关键字吗?或者说,如果没有关键字,是否也能够?
上面是某个语言的关键字,还有局部上下文关键字未展现进去。
keyword | |||
---|---|---|---|
abstract | as | base | bool |
break | byte | case | catch |
char | checked | class | const |
continue | decimal | default | delegate |
do | double | else | enum |
event | explicit | extern | false |
finally | fixed | float | for |
foreach | goto | if | implicit |
in | int | interface | internal |
is | lock | long | namespace |
new | null | object | operator |
out | override | params | private |
protected | public | readonly | ref |
return | sbyte | sealed | short |
sizeof | stackalloc | static | string |
struct | switch | this | throw |
true | try | typeof | uint |
ulong | unchecked | unsafe | ushort |
using | virtual | void | volatile |
while |
关键问题
一、同一种性能是否须要多种语法?
咱们不须要多种语法,雷同的需要能够有机对立。
二、强制标准是否有必要?
强制标准能够缩小代码浏览和保护的压力,在语言级施行标准能够晋升所有使用者的合作效率。
三、咱们想要的是动态类型还是动静类型?
咱们既想要动态查看,也想要动静自由度。动态鸭子类型可能是一个计划。
四、关键字是必要的吗?
如果语法结构足够少,咱们能够试试移除关键字。
函数
咱们对函数语法需要能够总结为如下几点:
- 函数也是值,定义函数的形式与定义函数变量的形式应该统一。
- 函数定义里的类型,与函数的类型应该统一。
- Lambda 表达式与函数定义应该具备显著的类似度。
假如咱们所有资源的定义形式都应用 let id = XXX
的模式,并且对立应用 type {}
的模式构建某种类型的值。
那么咱们能够先给出这样一个函数语法:
let foo = func(x: int) int {return 1}
这个语法十分一般,应用 func
关键字定义类型,()
里申明形参,前面申明返回值。
接下来咱们思考一下古代函数设计通常容许多返回值,因而咱们须要应用 ()
包装更多返回值。
let foo = func(x: int, y: bool) (int, bool) {return (1, true)
}
这样形参和返回值类型的 ()
太靠近了,浏览起来比拟费劲,须要一个分隔符,咱们能够引入->
。
let foo = func(x: int, y: bool) -> (int, bool) {return (1, true)
}
当 ->
退出之后,()->()
就能够形成一个函数的类型构造,此时 func
的存在就没有必要性了,所以咱们去掉这个关键字。
let foo = (x: int, y: bool) -> (int, bool) {return (1, true)
}
每次都要写两遍 ()
也挺麻烦的,其实咱们不太须要两个 ()
,能够将形参和返回值类型放在一起,应用->
宰割。顺便也能够将 return
的 ()
也省略掉。
let foo = (x: int, y: bool -> int, bool) {return 1, true}
等等,咱们还须要 return
这个关键字吗?我想应该是不须要了,咱们能够应用更好看的<-
。
let foo = (x: int, y: bool -> int, bool) {<- 1, true}
函数左右如同有点不均衡,咱们能够强制返回值类型也加上一样的名称形容,让它们看起来更统一,并且也能给使用者更敌对的阐明。
let foo = (x: int, y: bool -> a: int, b: bool) {<- 1, true}
到这里咱们的函数形容形式曾经成型,应用 (->)
表白函数类型。Lambda 语法也能够十分天然用 ->
宰割,应用 {}
定义整个函数的内容。
let foo: (int, bool -> int, bool) = { x, y ->
1, true
}
无参函数与函数参数的例子:
let foo = (->) {}
let bar = (fn: (->) -> ) {}
定义
咱们对定义语法需要能够总结为如下几点:
- 所有创立名称的行为都是定义,应该应用同一种形式。
- 名称比类型的浏览优先级更高,名称应该在前。
- 辨别可变与不可变。
假如咱们先应用 let xxx = value
定义不变量,应用 var xxx = value
定义变量。
let foo: int = 0
var bar: int = 0
应用 var
相当于多了一种定义申明的形式,不如应用 mut
来申明可变性,这样可能具备一致性。
let foo: int = 0
let mut bar: int = 0
咱们思考一下,这种构造其实不须要 let
这个关键字也能成立,所以咱们去掉它。
foo: int = 0
mut bar: int = 0
当初只剩下 mut
这个关键字了,咱们也去掉,应用 !
替换它,示意可变性、不确定性。
foo: int = 0
!bar: int = 0
到这里,只有反对类型推导,咱们也不须要明确写类型了,省略掉类型后,能够组合成:=
。
foo := 0
!bar := 0
当然,如果不肯定带值,咱们也能够省略左边,保留类型。
foo: int
!bar: int
当初咱们的定义语法曾经实现了,替换后面函数的例子,不须要换多少货色。
foo := (x: int, y: bool -> a: int, b: bool) {<- 1, true}
抉择构造
现有的抉择构造个别蕴含 if 与 switch 两种,它们的状态和性能曾经十分成熟了,但咱们仍有从格局上进一步简化的可能性。
咱们先给出一个常见的 if
语句,并且假设 {
不能够换行。
if (foo == 0) {......} else if (foo == 1) {......} else if (foo == 2) {......} else {......}
去掉 ()
仿佛不会影响什么,咱们先去掉它。
if foo == 0 {......} else if foo == 1 {......} else if foo == 2 {......} else {......}
咱们假设 else if
也不能够换行,必须跟在 }
前面。有了这个束缚,咱们就能够省略 else if
。
if foo == 0 {......} foo == 1 {......} foo == 2 {......} else {......}
同样的情理,else
其实也能够不须要,咱们能够应用 _
来替换它。
if foo == 0 {......} foo == 1 {......} foo == 2 {......} _ {......}
当初只剩下一个 if
了,咱们用 ?
来示意可选择性,替换掉它。
? foo == 0 {......} foo == 1 {......} foo == 2 {......} _ {......}
当初咱们实现了抉择构造的根本状态,通过强制标准来压缩更多代码。
对于 switch
,与 if
最大的区别是一个是对单值的匹配,一个是不同值的匹配。所以很显然咱们能够在当初的根底上减少语法适配,这里应用 []
来示意对单个指标的匹配。
? [foo] 0 {......} 1 {......} 2 {......} _ {......}
这样咱们就失去了两种根底的抉择构造。
循环构造
现有的循环构造个别蕴含 Foreach、For 三段式、While 三种,咱们一样能够从格局上进一步简化。
先给出最简略的例子:
for (let i in foo) {......}
省略掉作用不大的()
。
for let i in foo {......}
let
和 in
都是为了定义从汇合里获取的对象,咱们能够改成后面的定义语法。
for i := foo {......}
当初也只剩 for
这个关键字了,键盘上可用的符号也不多,我这里取了 @
替换掉它。
@ i := foo {......}
这样咱们就失去了循环构造的根本状态。
咱们再来看看传统的三段式构造:
for (i := 0; i < 10; i++) {......}
这种构造模式比较复杂,不少新语言都放弃了这种写法,而应用区间运算符代替,大部分模式为 begin .. end
。
咱们最常应用的区间蕴含 递增左闭右开 , 递增全闭 , 递加左闭右开 , 递增全闭 四种。
因而只须要组合四种模式的区间运算符就足够咱们应用了,以下按程序给出。
begin ..< end
begin .. end
begin ..> end
begin ... end
当初咱们能够应用区间运算符来实现大部分三段式的需要。
@ i := 0 ..< 10 {......}
最初间接传入一个表达式,就能满足 While 的需要。
@ foo < bar {......}
对象模版
类型是咱们形容对象的数据和行为的一种形式,咱们既能够用它充当结构数据的模版,也应该能把它视为形容行为的接口。
这样咱们就能够将它作为动态的鸭子类型应用,同时承载了数据、行为以及束缚的能力。
假设咱们将它称为 class
:
foo := class {
label := "I am Label"
show := (->) {print(label)
}
}
foo 同时蕴含了字段及函数,它们能被统一的应用。
咱们能够将惟一的关键字 class
去掉,替换为另一个常常用来示意模版的$
。
foo := $ {
label := "I am Label"
show := (->) {print(label)
}
}
而后 foo 就能被视为 type,咱们能够统一地构建了。
a := foo{}
a.show()
基于鸭子类型的个性,咱们能够定义一个 shower 的接口,去应用 foo 的行为。
shower := $ {show: (->)
}
useShower := (s: shower->) {s.show()
}
useShower(a)
因为 foo 具备了 show 办法,它就能被视为 shower 天然的应用。
咱们再引入一个语法,在对象模版内引入一个类型,就会主动隐含它所有的货色,这样很便当复用代码。
例如 reader 蕴含 shower,就蕴含了它的 show:
reader := $ {
shower
read: (->v: string)
}
那么咱们也能够让 bar 蕴含 foo,就蕴含了 label 和 show。
bar := $ {
foo
read := (->v: string) {<- label}
}
这时咱们就能将 bar 视为 reader 应用了。
useReader := (r: reader->) {r.show()
print(r.read())
}
b := bar{}
useReader(b)
最初
欢送 star https://github.com/kulics-works/feel
Feel 语言目前还处于试验阶段,不具备生产条件,局部设计可能会随着 llvm 端的推动而扭转,欢送探讨邮件 (kulics@outlook.com) 或 issue 探讨。
再贴一段 leetcode#16 的代码供参考
threeSumClosest := (nums: [list int], target: int->v: int) {
length := nums.len
nums.sort()
!closs := nums[0]+nums[1]+nums[2]
@ i := 0 ..< length {
l, r := i+1, length-1
@ l < r {sum := nums[i]+nums[l]+nums[r]
? abs(sum-target) < abs(closs-target) {closs = sum} sum > target {r -= 1} _ {l += 1}
}
}
<- closs
}