Буквально несколько дней назад компания Microsoft представила публике новый язык программирования. Языку дали название Bosque. Главная миссия дизайна языка — лучше быть богатым и здоровым, чем бедным и больным чтобы он был прост и понятен как для человека, так и для компьютера.
Информации пока что очень мало, язык еще очень свежий и нестабильный. Есть лишь paper от Марка Марона и дока в начальной стадии написания.
Давайте попробуем рассмотреть некоторые особенности языка.
1) Все значения в Bosque являются неизменяемыми (immutable), чтобы каждый блок кода не содержал никаких сайд-эффектов. По мнению авторов языка это полезно как для человека, так и для компьютера.
При этом, как ни странно, чуть далее по тексту рассказывается, что можно объявить изменяемую переменную ключевым словом var!
. И это не просто сахар для более удобной инициализации иммутабельной переменной, это действительно настоящая переменная.
Ладно, допустим, это некий компромисс, и в локальном скоупе так сделали. Но дальше идет упоминание о еще не реализованной фиче языка — передаче аргументов функции по ссылке
function internString(ref env: Map<String, Int>, str: String): Int {
if(env.has(str)) { //use the ref parameter
return env.get(str);
}
env = env.add(str, env.size()); //update the ref parameter
return env.size();
}
Может я чего-то не понимаю, но странная какая-то иммутабельность в языке, если мы можем передать Map по ссылке, и фунция ее намутирует.
2) В языке нет циклов for, while и т.д. Вообще никаких нет. Вместо этого есть коллекции и пайплайны. Другими словами, вместо циклов нужно использовать более высокоуровневые штуки типа map, filter и т.д.
3) Строки можно делать разных типов. Т.е., например, можно сделать строку-имя или строку-zipcode, и для type-чекера это будут две разные строки. Если вы в аргументе функции ожидаете zipcode, а вам по ошибке туда пихают имя, то компилятор это не проглотит. Синтаксис такой: String[Zipcode].
Тоже сомнительная штука, почему не просто ZipCode. Один раз объявить, что ZipCode — это строка и везде в сигнатурах это писать. И почему именно для строк эта типизация сделана, а не для интов, например.
4) Вызов функций можно делать с указанием названия аргументов из сигнатуры функции, например: myfunc(x=1, y=2)
5) В стандартной библиотеке есть различные коллекции, и с коллекциями можно работать по разному. Можно просто по цепочке вызывать map, потом filter и т.д., а можно работать через пайплайны.
var v: List[Int?] = List@{1, 2, none, 4};
//Chained - List@{1, 4, 16}
v->filter(fn(x) => x != none)->map[Int](fn(x) => x*x)
//Piped none filter - List@{1, 4, 16}
v |> filter(fn(x) => x != none) |> map[Int](fn(x) => x*x)
//Piped with noneable filter - List@{1, 4, 16}
v |??> map[Int](fn(x) => x*x)
//Piped with none to result - List@{1, 4, none, 16}
v |?> map[Int](fn(x) => x*x)
6) рекурсия считается злом, которое может усложнить программу, поэтому рекурсивные фунции надо помечать словом rec
7) программы на Bosque являются детерминированными. Другими словами в языке нет неопределенного поведения. Например, нельзя использовать переменные, пока они не были определены; алгоритмы сортировки только стабильные и т.д. Если программа выдала какой-то результат, то такой же результат будет и потом, никаких сюрпризов
8) Вместо классов и интерфейсов в языке есть понятия entity и concept.
concept Bar {
field f: Int;
}
entity Baz provides Bar {
field g: Int;
field h: Bool = true;
}
var y = Baz@{f=1, g=2, h=false}; //Create a Baz entity with the given field values
var x = Baz@{f=1, g=2}; //Create a Baz entity with default value for h
Пример кода
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
//
//This is a bosque test/benchmark for a tic-tac-toe program.
//
namespace NSMain;
entity Board {
const playerX: String[PlayerMark] = 'x'#PlayerMark;
const playerO: String[PlayerMark] = 'o'#PlayerMark;
const allCellPositions: List[[Int, Int]] = List[[Int, Int]]@{
@[ 0, 0 ], @[ 1, 0 ], @[ 2, 0 ],
@[ 0, 1 ], @[ 1, 1 ], @[ 2, 1 ],
@[ 0, 2 ], @[ 1, 2 ], @[ 2, 2 ]
};
const winPositionOptions: List[List[[Int, Int]]] = List[List[[Int, Int]]]@{
List[[Int, Int]]@{ @[ 0, 0 ], @[ 0, 1 ], @[ 0, 2 ] },
List[[Int, Int]]@{ @[ 0, 1 ], @[ 1, 1 ], @[ 2, 1 ] },
List[[Int, Int]]@{ @[ 0, 2 ], @[ 1, 2 ], @[ 2, 2 ] },
List[[Int, Int]]@{ @[ 0, 0 ], @[ 1, 0 ], @[ 2, 0 ] },
List[[Int, Int]]@{ @[ 1, 0 ], @[ 1, 1 ], @[ 1, 2 ] },
List[[Int, Int]]@{ @[ 2, 0 ], @[ 2, 1 ], @[ 2, 2 ] },
List[[Int, Int]]@{ @[ 0, 0 ], @[ 1, 1 ], @[ 2, 2 ] },
List[[Int, Int]]@{ @[ 0, 2 ], @[ 1, 1 ], @[ 2, 0 ] }
};
//Board is a list of marks, indexed by x,y coords from upper left 0 based
field cells: List[String[PlayerMark]?];
factory static createInitialBoard(): { cells: List[String[PlayerMark]?] } {
return @{ cells=List[String[PlayerMark]?]::createOfSize(9, none) };
}
method getOpenCells(): List[[Int, Int]] {
return Board::allCellPositions->filter(fn(pos: [Int, Int]): Bool => {
return !this->isCellOccupied(pos[0], pos[1]);
});
}
method getCellContents(x: Int, y: Int): String[PlayerMark]?
requires 0 <= x && x < 3 && 0 <= y && y < 3;
{
return this.cells->at(x + y * 3);
}
method isCellOccupied(x: Int, y: Int): Bool {
return this->getCellContents(x, y) != none;
}
method isCellOccupiedWith(x: Int, y: Int, mark: String[PlayerMark]): Bool
requires mark == Board::playerX || mark == Board::playerO;
{
return this->getCellContents(x, y) == mark;
}
method markCellWith(x: Int, y: Int, mark: String[PlayerMark]): Board
requires mark == Board::playerX || mark == Board::playerO;
requires 0 <= x && x < 3 && 0 <= y && y < 3;
requires !this->isCellOccupied(x, y);
{
return this<~(cells=this.cells->set(x + y * 3, mark));
}
hidden method checkSingleWinOption(opt: List[[Int, Int]], mark: String[PlayerMark]): Bool {
return opt->all(fn(entry: [Int, Int]): Bool => this->isCellOccupiedWith(entry[0], entry[1], mark));
}
hidden method checkSingleWinner(mark: String[PlayerMark]): Bool {
return Board::winPositionOptions->any(fn(opt: List[[Int, Int]]): Bool => this->checkSingleWinOption(opt, mark));
}
method checkForWinner(): String[PlayerMark]? {
if(this->checkSingleWinner(Board::playerX)) {
return Board::playerX;
}
elif(this->checkSingleWinner(Board::playerO)) {
return Board::playerO;
}
else {
return none;
}
}
}
entity Game {
field winner: String[PlayerMark]? = none;
field board: Board = Board@createInitialBoard();
method hasWinner(): Bool {
return this.winner != none;
}
method getWinner(): String[PlayerMark]
requires this->hasWinner();
{
return this.winner->as[String[PlayerMark]]();
}
method makeAutoMove(mark: String[PlayerMark], rnd: Int): Game
requires !this->hasWinner();
{
var! nboard: Board;
if(!this.board->isCellOccupied(1, 1)) {
nboard = this.board->markCellWith(1, 1, mark);
}
else {
var opts = this.board->getOpenCells();
var tup = opts->uniform(rnd);
nboard = this.board->markCellWith(...tup, mark);
}
return this<~( board=nboard, winner=nboard->checkForWinner() );
}
method makeExplicitMove(x: Int, y: Int, mark: String[PlayerMark]): Game
requires !this.board->isCellOccupied(x, y);
{
var nboard = this.board->markCellWith(x, y, mark);
return this<~( board=nboard, winner=nboard->checkForWinner() );
}
}
entity PlayerMark provides Parsable {
field mark: String;
override static tryParse(str: String): PlayerMark | None {
return (str == "x" || str == "o") ? PlayerMark@{ mark=str } : none;
}
}
entrypoint function main(): Game {
var! game = Game@{};
game = game->makeAutoMove(Board::playerX, 0);
game = game->makeAutoMove(Board::playerO, 1);
game = game->makeAutoMove(Board::playerX, 2);
game = game->makeExplicitMove(2, 0, Board::playerO);
game = game->makeExplicitMove(2, 1, Board::playerX);
return game;
}
Итого
В общем, желаю языку удачи, конечно, но пока что Bosque выглядит странновато и сыровато. Скорее proof of concept. Причем, не совсем ясно, что именно является киллер-фичей. Документация порождает больше вопросов, чем ответов.
Компилятор для Bosque написан на Typescript, а не на самом языке, как это принято (например, компилятор Typescript написан на Typescript). Т.е. язык, скорее всего, еще недостаточно развит для написания более менее сложных программ.
В ближайшем выпуске подкаста "Цинковый прод" мы обязательно обсудим новый язык Bosque, возможно удастся собрать побольше информации. Так что не забудьте подписаться.
Автор: Антон Околелов