Зачем нужен Verse если есть Lua?

Зачем нужен Verse если есть Lua?

Продолжим наше знакомство c Verse, в этом посте вы узнаете про эффекты и типы, а также тут будут философские рассуждения на тему: "Зачем вообще создавать очередной бесполезный язык программирования?" и причем здесь отпечатки кофе на столе. С рассуждений и начнем.

Первое знакомство с Verse всегда сопровождается этой мыслью: "Почему не Lua, или C#, или JS, Python, Blueprints и т.д". Действительно, в мире так много языков, зачем нам очередной? Чтобы ответить на этот вопрос, нужно понять, почему компании вообще создают новый язык программирования?

Есть сугубо утилитарные причины, такие как лучшая производительность, проще синтаксис и безопасность. Если смотреть только на них, то Verse совсем неплох, мы будем сравнивать его с Lua, потому что на этом языке пишется большая часть, кхм, любительского игрового кода.

Так вот, в отличие от Lua, язык имеет строгую типизацию. Это уменьшает вероятность ошибок. Строгая типизация вообще делает язык проще, хоть и многословней. Те, кто знают JS, наверное, слышали о смешных ошибках, которые порождены динамической типизацией. В Lua все примерно также. Нужно много чего держать в голове, и сам язык никак не помогает предотвратить ошибки.

Verse еще и мультипарадигменный язык. Можно писать в функциональном, объектно-ориентированном и императивном стиле. Язык дает большую свободу в выборе подходов разработчику.

Про Failure Context я говорил в предыдущей части, так вот. В контексте неудач, можно попытаться выполнить действие, но так и не совершить его. Когда выражение успешно выполняется, его эффекты сохраняются. Однако, если выражение заканчивается с ошибкой, все его эффекты отменяются, и ситуация возвращается к состоянию до выполнения выражения. Таким образом, можно выполнить ряд действий, которые накапливают изменения, но эти действия будут отменены, если сбой произойдет в контексте неудач. На GDC был прекрасный пример заполнения клеток для сапера

Зачем нужен Verse если есть Lua?

А вот примерно тоже самое на Lua.

-- Заполнение числами, указывающими количество мин вокруг клетки for i = 1, height do for j = 1, width do if board[i][j] ~= -1 then local count = 0 for dx = -1, 1 do for dy = -1, 1 do local x, y = j + dx, i + dy if x >= 1 and x <= width and y >= 1 and y <= height then if board[y][x] == -1 then count = count + 1 end end end end board[i][j] = count end end end

Не совсем честное сравнение, можно написать немного проще, но я думаю, вы уловили суть. Нам приходится своими руками обрабатывать потенциальные проблемы.

Контекст неудач (искреннее спасибо Google Translate за этот термин) - мощная вещь. Я нахожу его элегантным решением для языка программирования метавселенных. Игры там могут быть очень сложными, но создавать их будет человек, который и чтец, и жнец, и на дуде игрец. И будет ему лет 12...

Так мы плавно переходим к другой причине, по которой разрабатывают языки - свое сообщество. Новые языки программирования могут привлекать внимание разработчиков и формировать сообщество вокруг них. Один из моих рабочих языков программирования - Dart. Тоже в прошлом маргинальный язык. Его изначально разработали, чтобы убить JS, в итоге так увлеклись, что чуть ли не убили сам Dart, благо появился Flutter, и язык зацвел. Dart теперь - DSL (Domain Specific Language) для Flutter, сообщество вокруг него растет, причем Google делает ставку на развивающиеся рынки, такие как Индия и Африка (последний ивент про нововведения в языке проводился в Кении). Но Dart сделали очень похожим на Java и на C#, эти языки распространены в развивающихся рынках, так почему же Verse так мало на что-то похож? Потому что Verse для многих станет самым первым языком.

Epic, похоже, сознательно сделали его уникальным, потому что он будет у кого-то первым. Знакомство с языком вообще начинается роликом про приготовление пиццы и о том, как думают программисты:

Возможно, вы слышали про гипотезу Сапира-Уорфа или гипотезу лингвистической относительности. В строгой формулировке она звучит так: язык определяет мышление, и, соответственно, лингвистические категории ограничивают и определяют когнитивные категории. С этой теорией многие знакомы благодаря фильму "Прибытие". !!!SPOILER ALERT!!! В нем главная героиня, изучив язык пришельцев, приобрела фантастические способности.

Графическая форма языка инопланетян 
Графическая форма языка инопланетян 

Возможно, и прозвучит наивно, но мне кажется, что Verse даст своего рода фантастические способности начинающим разработчикам. Verse будет формировать программистское мышление у новичков, и тут, видит бог, строгая типизация, контекст неудач, иммутабельные данные и встроенные средства языка для concurrency, возможно, помогут создать качественных разработчиков нового поколения. Если только им удастся создавать действительно интересные игры!

Часть - Техническая.

Теперь как и обещал, поговорим про эффекты и типы.

Спецификаторы эффектов
Спецификаторы эффектов

Что такое эффекты? Эффекты в Verse указывают на категории поведения, которые функция может проявлять. Эффекты также можно указать для класса.

# шаблон объявления функции name()<specifier> : type = codeblock # шаблон объявления класса name := class<specifier>():

Эффекты делятся на два типа:

  1. Эксклюзивные - к функции или ключевому слову "class" можно добавить только один или никакого из эксклюзивных спецификаторов эффектов.
  2. Аддитивные - можно добавить все, некоторые или никакого из спецификаторов аддитивного эффекта в функцию или классу.

По умолчанию любая функция неявно имеет эффект "no_rollback". Этот эффект указывает на то, что любые действия, выполняемые функцией, не могут быть отменены, и поэтому функция не может быть использована в контексте неудач. Этот эффект не может быть указан вручную.

Нас пока будет интересовать комбинация эффектов "transacts" и "decides". "Transacts" указывает на то, что любые действия, выполняемые функцией, могут быть отменены, а "decides" указывает на то, что это failable expression и что эта функция может быть помещена в контекст неудач. С помощью этих эффектов мы можем создать функцию, которая может провалиться.

# У нас есть мапка в которой мы будем хранить значение роли игрока в виде значений True и False # Если значение True то игрок охотник var PlayerRoleMap : [player]?logic = map{} # Мы не можем гарантировать, что в мапке сохранен игрок, поэтому чтобы функцию # Которая возвращает нам роль игрока, мы помечаем эффектами GetPlayerRole(Player: player)<transacts><decides>:?logic= return PlayerRoleMap[Player] # Тут же мы проверяем, кто погиб? Функцию с эффектами мы можем поместить в контекст неудач # а через запятую, мы делаем проверку OnPlayerElimination(EliminatedPlayer:player):void= if (IsHunter := GetPlayerRole[EliminatedPlayer], IsHunter?) { # Я использую фигурные скобки для блоков, но можно и в стиле Python # Использовать двоеточие и отступы Print("Hunter is dead") } else { Print("Victim died") }

В документации можно почитать про другие эффекты для функций. Давайте теперь к типам.
В Verse существуют следующие типы

  • Logic
  • Int
  • Float
  • String
  • Rational
  • Any (ОПА!!!!)
  • Comparable
  • void

Logic - это почти bool, но как вы помните, для того чтобы его использовать в условиях, нужно использовать query оператор. С Int, Float - все ясно, классика. String - это type alias для []char (массива символов).
Что такое Rational? Это специальный тип, который является результатом целочисленного деления. Действительно, ведь деление - это тоже failable expression, нельзя просто так взять и поделить на 0!

Вот пример из документации

# Следующий код использует целочисленное деление, # чтобы определить, сколько стрел игрок может купить за свои монеты. # Целочисленное деление используется в выражении If, поскольку целочисленное деление является отказоустойчивым # и поэтому должно находиться в контексте неудач: if (NumberOfQuiversYouCanBuy : int = Floor(Coins / CoinsPerQuiver)): NumberOfArrowsYouCanBuy : int = NumberOfQuiversYouCanBuy * ArrowsPerQuiver

Вот так язык, обезопасит от досадных ошибок связанных с делением на 0.

Так теперь об Any. Люди знакомые с TypeScript уже наточили копья! Но спешу вас обрадовать, это очень ограниченный тип, который является супертипом для всех других типов. Мы практически ничего не можем делать с этим типом, но знать про него полезно.

Пример из документации

Letters := enum: A B C letter := class: Value : char Main(Arg : int) : void = X := if (Arg > 0) Letters.A else letter{Value := 'D'}

В этом примере, x получит тип any так как это самый низший общий супертип между перечислением Letters и типом letter. Потом с X почти ничего нелзя можно будет сделать. Еще any используют чтобы игнорировать второй аргумент.

FirstInt(X:int, :any) : int = X # это вернет первый аргумент, второй будет проигнорирован # можно написать обобщенную версию функции возвращающей первый аргумент, с помощью # параметризированных типов First(X:t, :any where t:type) : t = X # вот и generic

Comparable тоже интересный тип, например операторы = и <> используют его в своей сигнатуре.

operator'='(:t, :comparable where t:subtype(comparable)):t operator'<>'(:t, :comparable where t:subtype(comparable)):t

Подтип comparable относится к типу any и добавляет требование, что любое значение этого типа должно быть сравнимо с любым другим значением того же типа. Это обеспечивает возможность сравнения и взаимодействия между значениями данного типа. Подтипами comparable являются: Int, logic, float, char и типы контейнеры при определенных условиях. Классы тоже можно унаследовать от этого типа.

Void - это и в Африке void. Ведет себя также как и в других языках.

Теперь про типы контейнеры. Интересный тип option. Он может либо содержать значение, либо быть пустым.

var MaybeANumber : ?int = false # значит тут пусто set MaybeANumber := option{42} # а тут мы присвоим значение 42 # Я уже использовал его в примере с мапкой игроков и их ролей

Есть range или диапазоны. Они почти такие же как в Python

for (Index := 0..5): Print("{Index}")

Массивы, как вы могли догадаться примечательны только тем, что доступ к массиву это failable expression и должны быть помещены в блок if

ExampleArray : []int = array{10, 20, 30, 40, 50} for (Index := 0..ExampleArray.Length - 1): if (Element := ExampleArray[Index]): Print("{Element} in ExampleArray at index {Index}")

Map (мапки, карты, словари, ассоциативные массивы)

# так инициализируем ExampleMap : [string]string = map{"a" => "apple", "b" => "bear", "c" => "candy"} # так итерируемся for (Key->Value : ExampleMap): Print("{Value} in ExampleMap at key {Key}") # Так модифицируем значения if (set ExampleMap["b"] = "beer", ValueOfB := ExampleMap["b"]): Print("Updated key b in ExampleMap to {ValueOfB}") # напишет beer # Добавляем новое if (set ExampleMap["d"] = 4, ValueOfD := ExampleMap["deer"]): Print("Added a new key-value pair to ExampleMap with value {ValueOfD}")

А вот удалять ну удобно, пример из документации

# Удаляет элемент из карты и создает новую без этого элемента RemoveKeyFromMap(ExampleMap:[string]int, ElementToRemove:string):[string]int= var NewMap:[string]int = map{} # Конкатенируем ключи из ExampleMap в NewMap, без ElementToRemove for (Key -> Value : ExampleMap, Key <> ElementToRemove): set NewMap = ConcatenateMaps(NewMap, map{Key => Value}) return NewMap

Есть еще картежи, те кто знают функциональные языки уже заочно с ними знакомы. Это коллекция значений, разных типов. Для них можно указать тип

MyTupleInts : tuple(int, int, int) = (1, 2, 3) MyTupleMixed : tuple(int, float, string) = (1, 2.0, "three") MyTupleNested : tuple(int, tuple(int, float, string), string) = (1, (10, 20.0, "thirty"), "three")

Примечательно что можно передавать картеж в функцию и его элементы станут аргументами функции.

F(Arg1 : int, Arg2 : string) : void = DoStuff(Arg1, Arg2) G() : void = MyTuple := (1, "two") F(MyTuple(0), MyTuple(1)) # Доступ к элементу по индексу F(MyTuple) # А вот так можно сразу его пихнуть

Что будет дальше?

Я бы хотел продолжить написание постов в этом же стиле. Техническую часть я буду оставлять в конце, так как она кажется не всем так интересной. В первых частях будут более философские рассуждения. Дальше мы будем говорить непосредственно о метавселенных, сравнивать Roblox, Core и Fortnite Creative 2.0. Также мы обсудим бизнес-модели и идеологии (если такую найдем). В технической части мы, наконец, сможем попробовать использовать API Fortnite или, может быть, даже создадим какую-нибудь простенькую игру. Пишите в комментариях, что вам больше всего интересно.

3434
50 комментариев

Выглядит очень нечитаемо. Самый важный плюс Lua то что им может пользоваться геймдизайнер или лвлдизайнер. Он легкок для понимания и написания. А вот это чудо уже только для программистов

10
Ответить

Мне кажется, сложность чтения Verse обусловлена тем, что уже читающего уже есть опыт. Если лвлдизайнер и гейдизайнер уже знают какой-то язык, то может быть Lua будет легче понять. Если человек вообще не знаком с программированием, что Lua, что Verse будут одинаково сложны, но в пользу Verse сыграет его строгость и безопасность, а также привьются хорошие привычки и практики.
Хороший код на Lua - напишет только хороший программист, хороший код на Verse - напишет начинающий.

1
Ответить

Наоборот же. Луа как раз для программистов ибо это тот самый родной императивный язычок со всеми штуками вроде `x = x + 1` вызывающими недоумение у обывателя

Ответить

Чет слишком много воды, понятно что эта хуйня лучше js/lua так как есть строгая типизация, но почему просто не использовать c# с той же самой строгой типизацией

4
Ответить

Ну видимо хотят сделать язык для написания игор, а не абстрактных фабрик

1
Ответить

Нужна типизация, бери ts, а не js.

Ответить

Интересно читать, хоть я ни разу не программист) Но в последнее время играю в свои любимые старые игры и всё крутится идея создать для них какой-нибудь мод-уровень для каждой игры, хотя бы небольшой, как проба пера. И эти две прочтенные статьи тоже подогревают это желание)

2
Ответить