Буквально несколько дней назад компания 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, возможно удастся собрать побольше информации. Так что не забудьте подписаться.
