Inferno это идея и прототип языка программирования под JVM экосистему с полной совместимостью, с кодовыми базами на других JVM языках (в частности с Java).
Принципы описанные ниже, могут быть весьма спорными (относительно других языков), но это и является, "вишенкой на торте" этого языка.
- Простота, нет синтаксического "сахара". Минимум ключевых слов, пунктуаторов и синтаксических конструкций и их вариаций.
- Нет перегрузки операторов. Повторяя ошибки предков — Перегрузка операторов
- Разделение на "сущности" и "классы".
- Нет именованных параметров (за исключением аннотаций). Ошибка на миллиард — Именованные параметры
- Нет функций расширения (extension function). Почему экстеншены зло
- Подсказки для компилятора являются аннотацией CompileControl. (никаких кейвордов по типу inline, tailrec и пр.)
- Простой и единообразный синтаксис.
- Специальные expression receivers которые могут упростить контроль над флоу в коде.
Часть языка пишется на Java с использованием ANTLR, соответственно, сгенерированные исходники ANTLR на Java, генератор байт кода с использованием ASM, аналогично на Java, все остальное на Inferno.
Используемые внутри библиотеки: Cactoos, ANTLR, ASM
Разрабатывается на OpenJDK 21. Генерирую код, совместимый с JVM версии 11 (LTS). Для сборки используется система сборки Gradle (8.3)
namespace objects
entity Books ^ BooksEnvelope, Closeable
ctor(field db: Database)
fetch -> List[Book] {
<- db.exec("select id from books").map(id => Book.new(id, db))
; Или используя expression receiver
!return <- db.exec("select id from books").map(id => Book.new(id, db))
}
Инстанцировать объекты (в т.ч и анонимные классы), можно с помощью new "метода"
demo -> Nothing {
books <- Books.new(DatabaseSingletoneFabric.instance())
}
Все ресиверы выражений реализуются на стороне компилятора, и не могут быть добавлены пользователем, принимают только вычисляемое выражение, вот несколько примеров.
exprReceiverDemo -> Nothing {
!guard <- true = true ; Принимает boolean выражение и бросает GuardViolationException если значение false.
!require <- arr.get(0) ; Буквально требует значение или кладет на лопатки JVM с RequireContractException
!return <- arr.get(0) ; Явно возвращает управление из метода
!recover <- arr.get(0) ; Подавляет исключение (Exception) и передает управление дальше.
!defer <- db.close() ; Откладывает безусловное исполнение блока до конца исполнения пользовательского кода в методе.
_ <- db.info() ; Игнорирует, отправляет вникуда значение выражения (подобное Blackhole#consume в JMH).
; Так как принимает вычисляемое выражение, то можно использовать блоки, к примеру:
!recover <- {
val <- array.get(0)
str <- String.new(val)
}
}
Отдельный пример с defer блоком, если работали с Golang то уже понятно о чем речь. Defer expression receiver работает очень просто, откладывает исполнение вычисляемого выражения до конца функции, даже если произойдет исключение.
Если в выражении для defer ресивера будет выброшено исключение, то будет выброшен DeferDiedException
fetch -> Nothing {
sql <- db.connect("connection string")
!defer <- db.close()
sql.exec("select * from animals")
panic("Выбрасываем исключение, но коннект с бд будет закрыт.")
}
В языке всего две конструкции являющиеся выражением это when и тернарный оператор:
test -> Nothing {
count <- state.count = 0 ? 10 : state.count + 1
msg <- when (readln().trim().normalize()) {
"" => "Name can't be empty"
"admin" => "Admin name is restricted"
"root" => "Illegal name"
other => panic()
}
}
В языке можно создать синтаксически List (реализация ArrayList) и обычный jvm массив
collections -> Nothing {
sarr <- ["Hello", " ", "world"] ; Массив строк, выведенный тип является String[].
slist <- [| "Hello", " ", "world" |] ; Лист строк, выведенный тип является List[String] (реализация ArrayList[String])
}
Язык так же предоставляет простые методы по работе с многопоточным программированием, никакой раскраски кода отсылка. Реализация полностью соотносится к прямому использованию CompletableFuture из JDK (CF в дальнейшем).
go -> Nothing {
async(2) ; CompletableFuture.completedFuture(2)
async(() => {}) ; CompletableFuture.runAsync(() => {})
async(() => 2) ; CompletableFuture.supplyAsync(() => 2)
asyncAll(() => 2, () => {}, 2) ; CompletableFuture.allOf(CompletableFuture.supplyAsync(() => 2), CompletableFuture.runAsync(), CompletableFuture.completedFuture)
asyncAny(() => 2, () => {}, 2) ; CompletableFuture.anyOf(CompletableFuture.supplyAsync(() => 2), CompletableFuture.runAsync(), CompletableFuture.completedFuture)
; Простой пример асинхронной задачи
async(() => db.exec("select * from books").map(Book::new))
.exceptionaly(...)
.xxx()
}
В языке так же можно менять семантику конечного кода (промежуточного - JVM Bytecode) с помощью аннотации CompileControl
@CompileControl(
compatible <- Ver1_0,
preview <- true,
tailrec <- true,
inline <- true,
assertions <- false,
trace <- true
)
fetch -> List[Book] {
<- db.exec("select id from books").map(Book::new)
}
В языке есть возможность явно "экспоузить" метод, поле, класс с каким-либо модификатором или аттрибутом. К примеру:
@Expose(attrs <- [Private, Static, Native, Synchronized, Strictfp, Final, Virtual])
fetch -> List[Book] {
!return <- db.exec("select id from books").map(Book::new)
<- db.exec("select id from books").map(Book::new)
}
; Так же есть более краткая запись без явного доступа к attrs:
@Expose(Protected)
field cache <- LogCache.new(RamCache.new())
@Expose([Private, Static, Native, Synchronized, Strictfp, Final, Virtual])
fetch -> List[Book] {
!return <- db.exec("select id from books").map(Book::new)
<- db.exec("select id from books").map(Book::new)
}
В языке так же есть анонимные классы, но ее дизайн пока что экспериментальный и возможно, в конечном итоге их не будет.
Thread.new(Runnable.new() {
run -> Nothing {
println(3)
}
}).start()
Обобщения (в дальнейшем дженерики), реализованы очень просто, примером ярким для меня послужил Golang.
class Collection
; Декларация дженерика на уровне метода
get[T](idx: Int) -> T {
<- todo("using of placeboo stdlib version")
}
; Декларация дженерика на уровне класса
class GenericCollection[T]
get(idx: Int) -> T {
<- todo("using of placeboo stdlib version")
}
Документация как и комментарий декларируется с ;
с отличием что для документации их надо три ;;;
Пример:
;;; Декоратор для загрузки книг из базы данных
;;;
;;; @param What какой-то параметр
;;; @returns Коллекцию книг
;;; @see Books#fetch
;;; @since v1.0
fetch(what: String) -> List[Book] {
db.exec("select id from books").map(Book::new) ; Грузим книжечки и преобразуем в Book
}
- По умолчанию методы и конструктора публичные.
- Все поля иммутабельные и приватные.
- У каждой сущности есть свой уникальный ID.
- Равенство и хешкод базируется на уникальном ID.
- Не наследуемые.