拥抱开源的微软近日又为开发者带来好消息:在受 TypeScript 语法类型与 ML 和 Node/JavaScript 语义启发下,微软推出了全新的开源编程语言 Bosque。
Bosque 创作者是微软研究院的计算机科学家 Mark Marron,他设计通过拥抱代数运算和避开导致复杂性的技术,试图创造出一种简单易懂的语言,走出 1970 年代兴起的结构化编程模型。如今,Bosque 似乎已经实现了这一点,它不再需要“for”、“while”、“do while”等循环,可让开发者开发效率更高。
与此同时,本文的作者 Erik Pragt 也发现 Bosque 和 Kotlin 有着诸多的相似之处。
作者 | Erik Pragt
译者 | 弯月
责编 | 屠敏
出品 | CSDN(ID:CSDNnews)
以下为译文:
昨天,我的一个同事Sahil发给我一个链接,链接的文章介绍了微软研究院发布的新语言Bosque。在读了这篇文章,看了里面的代码示例后,我不禁想看看它和Kotlin有多少相似之处。我想通过这篇文章,分享我使用Bosque语言的短暂经验,以及它和Kotlin的比较。我不会深入讨论Kotlin和Bosque的生态系统,这篇文章的重点是语言结构和语法本身。
什么是Bosque编程语言?
Bosque是微软研究院发明的一门新的编程语言。在我撰写本文的时候,Bosque语言才刚满11天!根据Bosque的GitHub页面(
https://github.com/Microsoft/BosqueLanguage),Bosque的设计目标是“编写简单、明确且人和机器都容易理解的代码”。因此,Bosque吸取了许多概念,例如不可更改性、原子构造器、工厂等,还有丰富的类型系统。
Bosque是一门非常新的的语言,它的语法受到了TypeScript的启发。从我的另一篇文章《Kotlin vs TypeScript》(
https://www.jworks.io/dart-vs-typescript-vs-kotlin-js/)中,你就可以看出两者之间有许多相似之处,因此不难想象,Kotlin和Bosque之间也会有许多共同点。
Bosque vs Kotlin:语法
Bosque的GitHub页面上给出了一小段Bosque代码示例,这段代码很好地展示了它与Kotlin之间的相似性:
// Bosque function add2(x: Int, y: Int): Int { return x + y;}add2(2, 3) //5// Kotlinfun add2(x: Int, y: Int): Int { return x + y;}add2(2, 3) //5
尽管这段代码非常简单,但应当注意到,Bosque的语法与非常规版本的Kotlin非常相似。唯一的区别就是关键字function与fun的不同,其他代码完全一样。我们来看看更多的示例。
Bosque网站上给出了一个简单的井字棋游戏(
https://github.com/Microsoft/BosqueLanguage/blob/master/docs/tictactoe.md)。虽然我完全没有编写Bosque代码的经验,但这段代码非常直观,除了一些像'x'#PlayerMark;(有类型的字符串)、@[ 2, 2 ](元组/结构)和this<~(cells=this.cells..(原子批量数据操作)等语法糖之外。
下表总结了Bosque的井字棋游戏游戏示例中用到的一些语言概念,以及对应的Kotlin概念。这个列表并不完整,但可以让你方便地理解两种语言的代码示例:
语言特性
Bosque
Kotlin
不可更改的值(Immutable Values)
全部
部分(基本类型,集合)
有类型的字符串(Typed Strings)
支持
内嵌类或类型别名
灵活调用(Flexible invocations)
命名实参
命名实参
批量代数数据操作(Bulk Algebraic Data Operations)
x<~(f=-1, g=-2)
copy()方法
短路运算符(o?.m)
使用问号
使用问号
原子构造器(Atomic Constructors)
支持
伴生对象(Companion Objects)
元组(Tuples)
支持
不支持(仅支持Pair/Triple)
函数类型(Function types)
支持
支持
现在我们对两门语言有了一点理解,接下来我们再来深入看一看井字棋游戏的代码,看看两者具体的比较情况。如果你想直接看结果可以点这里(
https://gist.github.com/bodiam/749b5174d26522cdaeba43c1401cbc8d)。如果你有耐心看下去,我可以带你阅读一些代码,并给出相应的Kotlin代码。
Bosque井字棋
井字棋的示例是一个小游戏,玩家可以明确指定走哪一步,也可以随机走一步。游戏不需要修改状态,因为一切都是不可修改的。
Bosque有类型字符串
在井字棋示例中我们看到的第一个语言概念就是有类型字符串(Typed Strings):
const playerX: String[PlayerMark] = 'x'#PlayerMark;const playerO: String[PlayerMark] = 'o'#PlayerMark;
上面这段代码创建了两个常量:playerX和playerO,都是有类型的字符串。也就是说,PlayerMark类型的字符串不能与包含邮政编码的字符串兼容。
Kotlin不支持有类型字符串,但提供了内嵌类(Inline Class)或类型别名(Type Aliases),可以用在类似的场合。内嵌类可能更好(我们稍后会讲到),但现在先来看看类型别名。类型别名促使Kotlin代码更接近Bosque代码:
const val playerX: PlayerMark = "x" as PlayerMarkconst val playerO: PlayerMark = "o" as PlayerMark
Bosque类型
接下来看看这段代码:List[[Int, Int]]。可以想象这是一个列表,但列表的元素类型为结构类型[Int, Int]。因此,该列表只能包含整数对。
在Kotlin中,如果类型能够推断出来,我们就不需要明确定义,但如果要定义的话可以写成List<Pair<Int, Int>>,写法非常相似。
Bosque结构类型
如果需要保存数据,但不希望定义类,则可以使用结构(Struct)。在Bosque中,结构类似于:@[ 0, 0 ]。它定义了一个结构,包含两个值:0和0。
虽然Scala等语言都有结构,但Kotlin从早期的某个版本开始就不再支持结构了。听起来似乎有点奇怪,因为尽管有时候结构的存在是合理的,但是结构并不会提高代码的可读性,而且创建数据类也非常容易。同时,Kotlin原生支持Pair和Triple,因此在Kotlin中可以利用to方法创建上述结构:0 to 0,得到的结果是一个Pair对象。
Bosque前置条件
另一个有趣的概念是前置条件(preconditions),该功能可以通过requires关键字检查函数的输入。
requires 0 <= x && x < 3 && 0 <= y && y < 3;
Kotlin也有同样的概念:
require( 0 <= x && x < 3 && 0 <= y && y < 3)
但是,除了支持前置条件外,Bosque还支持后置条件(postconditions)。后置条件可以检查函数是否返回了正确的值,例如ensures _result_ % 2 == 0; 可以检查返回值是否为偶数。
Kotlin也支持范围检查,因此我们可以通过重构用更加常规的形式编写上述代码成。
Bosque的原位更新
有一个很不错的功能,但Kotlin中却没有:通过拷贝的方式更新一个不可修改的列表。在Bosque中可以用下面的代码实现:
return this<~(cells=this.cells->set(x + y * 3, mark));
上面这段代码创建了cells列表的拷贝,并将x + y * 3位置上的值更新成了mark。在Scala中,该操作可以通过updated方法实现,但Kotlin不支持这个方法。该方法已经有了一个YouTrack问题票,所以如果你想要这个功能,可以去为该功能投票。同时,你还可以通过创建扩展函数的方式来创建自己的updated方法:
private fun <E> List<E>.updated(index: Int, value: E): List<E> { return this.toMutableList().apply { set(index, value) }}
如果你希望编写一段效率更高、但可读性较差的代码,则可以参考重构后的Kotlin版本,或参考上述YouTrack问题票。
Bosque的扩散操作符
Bosque代码中还有一行非常有意思:
var tup = opts->uniform(rnd);nboard = this.board->markCellWith(...tup, mark);
这段代码创建了新的棋盘(nboard),该棋盘是当前棋盘的拷贝,是根据一个从列表中的数据项半随机(uniform,表示根据种子来选取)地创建的。由于tup变量是元组,因此可以利用...操作符来扩散(spread)其内容。
尽管Kotlin也有扩散运算符,但没这么灵活。它只能在调用vararg方法的时候使用。但是,Kotlin中只能做到使用Pair的第一个或第二个值,或者更Kotlin的方式是,像下面这样先将Pair解构:
val (x, y) = opts.random(Random(rnd))nboard = this.board.markCellWith(x, y, mark)
但是,由于我们希望尽可能地与原始代码相似,所以暂时线直接使用第一个和第二个值:
nboard = this.board.markCellWith(tup.first, tup.second, mark)
结果
上面的比较给出了一些Kotlin和Bosque之间的异同。作为本文的结果,我们现在有了三个不同版本的井字棋代码:
Kotlin最佳实践
最后一个版本(即重构后的Kotlin代码)使用了一些Kotlin的最佳实践。我不知道Bosque中是否有这些结构,也许Bosque的代码也可以改进。我们可以通过下列变更编写更符合惯例的代码:
使用内嵌类代替类型别名
inline class PlayerMark(val value: String)
这种写法可以给出更好的类型信息,避免在应该使用String的地方使用PlayerMark。
在require中使用范围检查
不要使用单独的比较,我们应该使用范围比较:
require( 0 <= x && x < 3 && 0 <= y && y < 3) // both statementsrequire(x in 0..2 && y in 0..2) // do the same
去除类型信息
Bosque中的类型用法似乎很冗余。尽管Kotlin中也完全可以这样做,但并不是必须的。所以,下面两条语句是相同的:
// thisval allCellPositions : List<Pair<Int, Int>> = listOf( 0 to 0, 1 to 0, 2 to 0,)// vsval allCellPositions = listOf( 0 to 0, 1 to 0, 2 to 0,)
去掉`this`
Bosque代码似乎用了许多this,但在Kotlin中不需要这样做,所以重构后的版本中去掉了this。
总结
总之,我希望在上文中我们很好地比较了两种语言,我们也很期待Bosque语言将来的发展,而其他语言如C#、TypeScript或Kotlin能否采纳这些概念呢,让我们拭目以待吧。
原文:
https://www.jworks.io/tictactoe-in-bosque-and-kotlin/
本文为CSDN翻译,转载请注明来源出处。
【END】