Yesterday, one of my colleagues (thanks Sahil!) shared a link introducing me to Bosque, a new programming language by Microsoft Research. After reading some of the information about this new language and looking at the code samples, I couldn’t help but seeing a large number of similarities with Kotlin. In this blog post, I’ll share some of my short experiences with the Bosque programming language and how it compares to Kotlin. I won’t dive into the whole Kotlin vs Bosque ecosystem, this post mostly focuses on language constructs and syntax.
What is the Bosque Programming Language?
Bosque is a new programming language created by Microsoft Research. At this time of writing, the Bosque language is only 11 days(!) old. According to the Bosque Github page, Bosque is “designed for writing code that is simple, obvious, and easy to reason about for both humans and machines.“. As such, Bosque leans a lot on the concept of immutability, Atomic Constructors and Factories, and an extensive type system.
Bosque is a very new programming language at this time of writing and is inspired by TypeScript in terms of syntax. As can be seen in my Kotlin vs TypeScript article, there’s a lot of similarity between the two, and it’s no surprise that there’s a big overlap between Kotlin and Bosque too.
Bosque vs Kotlin syntax
A simple Bosque code sample listed on the Bosque Github page nicely demonstrates the similarities between Kotlin and Bosque.
// 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
While the above example is of course very trivial, it’s interesting to note how familiar the Bosque syntax is compared to the non-idiomatic Kotlin. The only difference in the above is the function
vs fun
usage. All other code is identical. Let’s dive into a more code example.
On the Bosque website, a simple TicTacToe example is listed. While I have zero experience with writing Bosque code, the code is mostly very straightforward, with the exception of some syntactic sugar such as 'x'#PlayerMark;
(Typed Strings), @[ 2, 2 ]
(Tuples/Structs) and this<~(cells=this.cells..
(Atomic Bulk Data Operation).
Below is a small overview of some of the language concepts used in the Bosque TicTacToe example, and the Kotlin Equivalents. It’s not a complete overview of the language similarities but helps in more easily understanding both code examples.
Feature | Bosque | Kotlin |
Immutable Values | All | Some (primitive types, collections) |
Typed Strings | Supported | Inline classes or Type Alias |
Flexible invocations | Named arguments | Named arguments |
Bulk Algebraic Data Operations | x<~(f=-1, g=-2); | copy() method |
short-circuit operators (o?.m) | Using question mark | Using question mark |
Atomic Constructors | Supported | Companion objects |
Tuples | Supported | Not supported (only Pair/Triple) |
Function types | Supported | Supported |
Now that we have a small understanding of how the languages compare, let’s dive into the TicTacToe code, and see how they compare. For those who want to skip to the end result immediately, the result can be seen here. For those who are a bit more patient, I’ll walk you through some of the code, and explain the Kotlin equivalents.
Bosque TicTacToe
The TicTacToe example is a code sample in which a game of Tic Tac Toe is player, either by doing explicit moves or automatic (random) moves. The game doesn’t mutate state since everything is immutable.
Bosque Typed Strings
One of the first language concepts we see in the TicTacToe example is the usage of Typed Strings:
const playerX: String[PlayerMark] = 'x'#PlayerMark;
const playerO: String[PlayerMark] = 'o'#PlayerMark;
The above creates two constants, playerX
and playerO
as a Typed String. This means that a String containing a PlayerMark wouldn’t be compatible with a String containing a ZipCode.
Kotlin doesn’t support Typed Strings, but provides Inline Classes or Type Aliases which can be used for a similar purpose. Inline Classes are probably the best solution and we’ll dive into that later, but right now we’re going for Type Aliases. This makes the Kotlin code look more similar to the Bosque code, including an obsolete cast:
const val playerX: PlayerMark = "x" as PlayerMark
const val playerO: PlayerMark = "o" as PlayerMark
Bosque Types
Next up, we see the following code: List[[Int, Int]]
. As you can imagine, it’s a list, but it’s a list with an [Int,Int]
Struct type. So, the list can only contain Integer pairs.
In Kotlin, we don’t have to declare the types explicitly if they can be inferred, but if we would, it would look like this: List<Pair<Int, Int>>
, which would be quite similar.
Bosque Struct Types
If you want to have a data holder without defining a class, you can use a Struct. In Bosque, a Struct looks like this: @[ 0, 0 ]
. This defines a struct pair with two values, 0 and 0.
While languages like Scala do have structs, Kotlin stopped supporting them in an early version of Kotlin. This might sound strange since there are situations in which structs make sense, but structs have a tendency to not make your code more readable, and creating a data class is very simple. Also, Kotlin supports Pairs and Triples out of the box, so the above struct can be created in Kotlin by using the to
method: 0
to
0
, which results in a Pair
object.
Bosque Preconditions
Another interesting concept is preconditions, which allow you to check the input to a function by using the requires
keyword.
requires 0 <= x && x < 3 && 0 <= y && y < 3;
Kotlin has exactly the same functionality in terms of preconditions:
require( 0 <= x && x < 3 && 0 <= y && y < 3)
However, besides supporting preconditions, Bosque also supports postconditions. This allows you to check that a function returns the correct value, such as ensures _result_ % 2 == 0;
to check that the result is even.
Kotlin also supports range checks, which allows us to refactor the above code to something more idiomatic.
Bosque Update in Place
A really nice feature, which is, unfortunately, missing in Kotlin, is to create an update an immutable list by creating a copy of it. In Bosque, this is done with the following code:
return this<~(cells=this.cells->set(x + y * 3, mark));
The above creates a new copy of the cells
list, with an updated mark at the x + y * 3
position. In Scala, this is done using the updated method, but in Kotlin this option is missing. There is a YouTrack issue for this, so if you want this, please vote for this. In the meantime, you can create your own updated
method by creating an extension function:
private fun <E> List<E>.updated(index: Int, value: E): List<E> {
return this.toMutableList().apply { set(index, value) }
}
For a more performant, but less readable version, have a look at the refactored Kotlin version or the YouTrack issue.
Bosque Spread operator
Another interesting line in the Bosque code is the following:
var tup = opts->uniform(rnd);
nboard = this.board->markCellWith(...tup, mark);
It creates a new board (nboard
) which is a copy of the current board, based on a semi-random item from the list (uniform
, chosen using a seed). Since the tup
variable is a tuple, we can spread the contents using the ...
operator.
While Kotlin does have a spread operator, it’s not this flexible. It can only be used when calling a vararg method. However, what is possible in Kotlin is to either use the first and second value of the Pair, or, more Kotlinesque, to destructure the Pair into two values like this:
val (x, y) = opts.random(Random(rnd))
nboard = this.board.markCellWith(x, y, mark)
However, since we want to resemble the original code as much as possible, for now, we’re using the approach of using the first and second value:
nboard = this.board.markCellWith(tup.first, tup.second, mark)
Result
The above comparisons give a bit of insight in the difference between Kotlin and Bosque, and in the areas where they are similar. As a result of this article, there are now 3 different versions of the TicTacToe code:
- The Original Bosque TicTacToe
- The Original Kotlin TicTacToe, which resembles the above as much as possible
- The Refactored Kotlin TicTacToe, which represents the Kotlin one, but uses more Kotlin best practices.
Kotlin best practices
The last version, the Kotlin Refactored sample, uses a few Kotlin best practices. I am not sure if any of these constructs are available in Bosque, so maybe the Bosque one can be improved. A list of changes which are done to create more idiomatic Kotlin are:
Using an inline class instead of a type alias.
inline class PlayerMark(val value: String)
This gives better type-information and prevents us from using a PlayerMark type alias where a String was expected.
Use range checks in requires
Instead of individual comparisons, we do a range check.
require( 0 <= x && x < 3 && 0 <= y && y < 3) // both statements
require(x in 0..2 && y in 0..2) // do the same
Remove type information
Bosque seems to be quite verbose in its use of types. While this is perfectly fine in Kotlin too, it’s not needed. So, the 2 statements below represent the same:
// 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,
)
Removal of `this`
While the Bosque code seems to be using a lot of this
references, in the Kotlin version this is not needed, and they have been removed in the refactored version.
Conclusion
All in all, I hope the above gives a good comparison between the two, and it’s interesting to see where the Bosque programming language will go, and if we will see any of these concepts being used in languages like C#, TypeScript or Kotlin.
Thanks for reading, if you have any questions or feedback, please leave a message below! The final project can be found in Github.