У каждого программиста есть мечта — создать свой язык программирования. Самый лучший и самый удобный, конечно же — не то что существующие. Лично для меня идеей-фикс было создание языка, в котором совершенно не будет бойлерплейта, который будет максимально краток, но при этом чрезвычайно красноречив. В течение двух лет я делал попытки добиться желаемого результата, но с чего бы я не начинал, в конце концов после отсечения всего лишнего у меня всегда получался Lisp. И тогда мне в голову пришла гениальная мысль — взять Lisp, и усовершенствовать его в соответствие с моими идеями. В честь первой попавшейся мне на глаза вещи, проект был назван Sova.
Чтобы язык из коробки работал и на сервере, и в вебе, и на мобильных платформах я решил сделать компиляцию в Javascript, который по-моему мнению без шуток является одним из лучше всего спроектированных языков нашего времени и что гораздо более важно обладает мощнейшей экосистемой npm, Node.js, React и React Native.
И перед началом нашего путешествия, чтобы сразу вызвать интерес к дальнейшему прочтению статьи, вот вам пример одного файла Express-приложения на Sova:
=-> './database' database
=-> 'express' express
= application express ()
application.get '/'
-> (request response) (response.sendStatus 200)
application.get '/user/:id'
-> (request response) (response.send (database.getUserById request.params.id))
application.listen 8080
-> () (console.log 'Application is listening on port 8080')
Ну что ж, начнем...
Хоть этот комикс и вызывает у меня улыбку каждый раз, как я на него смотрю, я совершенно не согласен с основным его утверждением. Скобочки — это не элегантно, скобочки — это бойлерплейт.
Поэтому первым делом в своем чудесном языке я решил избавится от скобочек. Лучшим решением стала имеющая значение индентация, прямо как в Python. Возьмем, к примеру, такой кусок кода на lisp:
(* 2 (+ 1 2) (- 4 (/ 2 1)))
Конечно, это выражение можно разнести по разным строкам, но от забористости из засоряющих глаз скобочек это нас не избавляет.
(* 2
(+ 1 2)
(- 4 (/ 2 1)))
А теперь посмотрим, как элегантно и воздушно можно написать это же выражение на Sova с помощью имеющей значение индентации:
* 2
+ 1 2
- 4 (/ 2 1)
То есть в Sova выражение a (b c) (d (e f))
равнозначно выражению:
a
b c
d
e f
Что мне не нравилось в большинстве языков программирования, так это засоренность синтаксиса ничего не значащим, но занимающим место на экране бойлерплейтом — лишними ключевыми словами, бессмысленными знаками препинания и многим другим. Даже в common lisp вместо простых и всем понятных символов часто используются слова для обозначения простейших операций, вроде того же defn
.
Возьмем к примеру объявление константы в Javascript:
const a = 1
Sova — язык исключительно функциональный и в нем все переменные иммутабельны, поэтому не нужно указывать дополнительное ключевое слово const, а пишется все просто:
= a 1
Основным элементом любого языка являются функции. Вот так минималистично они выглядят в Sova:
= addOne -> number
+ number 1
= doubleAndAddOne -> number
= doubled (* number 2)
addOne doubled
console.log (doubleAndAddOne 2)
Как и в любых функциональных языках последнее выражение в теле функции является возвращаемым. То есть код выше в скомпилированном JavaScript будет выглядеть как:
const addOne = number => {
return number + 1
}
const doubleAndAddOne = number => {
const doubled = number * 2
return addOne(doubled)
}
console.log(doubleAndAddOne(2))
У условного выражения в Sova может быть как два так и один аргумент.
Вот примеры условий, имеющих два аргумента:
console.log
? true 1 0
console.log
? (> 2 1) 'Greater' 'Less'
console.log
? (> 2 1)
? (> 1 2) 'x' 'y'
'z'
А вот тут например в функции checkNumber мы возвращаем значения по условию:
= checkNumber -> number
? (=== number 1) (<- 'One')
? (=== number 2) (<- 'Two')
? (<= number 9) (<- 'From three to nine')
'Ten or more'
console.log (checkNumber 1)
console.log (checkNumber 4)
console.log (checkNumber 11)
В скомпилированном JavaScipt это выглядит как:
const checkNumber = number => {
if (number === 1) return 'One'
if (number === 2) return 'Two'
if (number <= 9) return 'From three to nine'
return 'Ten or more'
}
console.log(checkNumber(1))
console.log(checkNumber(4))
console.log(checkNumber(11))
Основной коллекцией любого языка является массив. Вот так объявление и деконструкция массива выглядят в Sova:
= list | 1 2 3 4
console.log list
console.log
list.map (-> x (+ x 1))
= (| first second) list
console.log first
console.log second
В скомпилированном JavaScript это будет выглядеть как:
const list = [1, 2, 3, 4]
console.log(list)
console.log(list.map(x => x + 1))
const [first, second] = list
console.log(first)
console.log(second)
Второй самой важной коллекцией является хешмапа. В Sova объявление и деконструкция мапы выглядит так:
= map :
a 1
b 2
c :
d 3
e 4
f 'Hello'
console.log map
= (: a (c (: d e))) map
console.log a
console.log d
console.log e
В скомпилированном JavaScript это выглядит так:
const map = { a: 1, b: 2, c: { d: 3, e: 4 }, f: 'Hello' }
console.log(map)
const { a, c: { d, e }} = map
console.log(a)
console.log(d)
console.log(e)
Если мы хотим вызвать у объекта метод, то есть два способа это сделать. Мы можем вызвать его как object.method parameter1 parameter2
либо как .method object parameter1 parameter2
. Второй способ позволяет нам создавать цепь вызовов методов.
Импортировать в Sova код можно модули как из других .sv
файлов, так и из .js
файлов. Например, в данном примере, импортируются два модуля — data/index.js
и handler/index.sv
:
=-> './data' (: greeting name)
=-> './handler' handle
handle greeting name
В скомпилированном JavaScript это выглядит так:
const { greeting, name } = require('./data')
const handle = require('./handler')
handle(greeting, name)
Импорт как JavaScript, так и Sova модулей дает возможность по чуть-чуть внедрять Sova в существующий Javascript проект.
В данном примере, из модуля экспортируется функция:
<-= -> (greeting name)
console.log greeting
console.log name
В скомпилированном JavaScript это выглядит так:
module.exports = (greeting, name) => {
console.log(greeting)
console.log(name)
}
Чтобы увидеть всю красоту, воздушность и лаконичность Sova, нужно посмотреть на более менее крупную программу. Например, вот программа вычисляющая средний возраст людей и находящая имя человека, чей возраст наиболее близок к среднему возрасту.
=-> 'lodash' _
= people |
: (name 'Alice') (age 24)
: (name 'Bob') (age 15)
: (name 'Chris') (age 46)
: (name 'Daniel') (age 35)
: (name 'Elisabeth') (age 29)
: (name 'Fred') (age 52)
= averageAge /
.reduce (.map people (-> man man.age))
-> (x y) (+ x y)
0
.length people
= manWithClosestToAverageAge _.minBy
.map people (-> man (: (name man.name) (distance (Math.abs (- averageAge man.age)))))
'distance'
console.log averageAge
console.log manWithClosestToAverageAge.name
Благодаря тому что язык компилируется в JavaScript, становится возможной разработка под любые платформы. Например, вот небольшой пример React-приложения под веб-браузеры:
=-> 'react' React
=-> 'react-dom' ReactDOM
=-> './styles' styles
= (: createElement:e) React
= App -> ((: name))
e 'div' (: (style styles.container))
e 'div' (: (style styles.hello)) 'Hello'
e 'div' (: (style styles.name)) name
ReactDOM.render (e App (: (name 'John'))) (document.getElementById 'root')
Так же в репозитории есть примеры Express-сервера и React Native приложения под мобильные платформы.
Таким образом, язык Sova вобрал в себя лучшее из нескольких других языков:
Код компилятора с примерами использования языка лежит тут https://github.com/sergeyshpadyrev/sova. Буду рад увидеть звезды на репозитории от всех тех, кому понравилась концепция языка и кто хотел бы, чтобы работа над ним продолжилась. Но сразу предупрежу, что пока что это исключительно proof of concept, и даже поиграться с языком из-за отсутствия документации и некоторых возможностей крайне трудно. Например, в языке пока что отсутствует обработка исключений, классы и другие необходимые вещи. И под Windows запустить его вряд ли получится.
Следующими шагами я планирую дополнить и стабилизировать синтаксис, переписать парсер, написать тесты на парсер и транслятор, написать документацию. А пока, спасибо за внимание и до новых встреч.
К сожалению, не доступен сервер mySQL