微软推出新逆天开源语言,告别 for 循环,提高开发效率!

拥抱开源的微软近日又为开发者带来好消息:在受 TypeScript 语法类型与 ML 和 Node/JavaScript 语义启发下,微软推出了全新的开源编程语言 Bosque。

Bosque 创作者是微软研究院的计算机科学家 Mark Marron,他设计通过拥抱代数运算和避开导致复杂性的技术,试图创造出一种简单易懂的语言,走出 1970 年代兴起的结构化编程模型。如今,Bosque 似乎已经实现了这一点,它不再需要“for”、“while”、“do while”等循环,可让开发者开发效率更高。

与此同时,本文的作者 Erik Pragt 也发现 Bosque 和 Kotlin 有着诸多的相似之处。

微软推出新逆天开源语言,告别 for 循环,提高开发效率!

作者 | 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
// Kotlin
fun 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 PlayerMark
const 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之间的异同。作为本文的结果,我们现在有了三个不同版本的井字棋代码:

  • 原始的Bosque代码:https://github.com/Microsoft/BosqueLanguage/blob/master/docs/tictactoe.md
  • 原始的Kotlin代码,尽可能与上述代码相似:https://gist.github.com/bodiam/749b5174d26522cdaeba43c1401cbc8d
  • 重构后的Kotlin代码,功能与上面的代码相同,但使用了更多的Kotlin最佳实践:https://gist.github.com/bodiam/c98cef9da8660a06c76883326b21a2cb

Kotlin最佳实践

最后一个版本(即重构后的Kotlin代码)使用了一些Kotlin的最佳实践。我不知道Bosque中是否有这些结构,也许Bosque的代码也可以改进。我们可以通过下列变更编写更符合惯例的代码:

使用内嵌类代替类型别名

inline class PlayerMark(val value: String)

这种写法可以给出更好的类型信息,避免在应该使用String的地方使用PlayerMark。

在require中使用范围检查

不要使用单独的比较,我们应该使用范围比较:

require( 0 <= x && x < 3 && 0 <= y && y < 3) // both statements
require(x in 0..2 && y in 0..2) // do the same

去除类型信息

Bosque中的类型用法似乎很冗余。尽管Kotlin中也完全可以这样做,但并不是必须的。所以,下面两条语句是相同的:

// this
val allCellPositions : List<Pair<Int, Int>> = listOf(
 0 to 0, 1 to 0, 2 to 0,
)
// vs
val 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】

相关推荐