Лучший в мире язык программирования -7


У каждого программиста есть мечта — создать свой язык программирования. Самый лучший и самый удобный, конечно же — не то что существующие. Лично для меня идеей-фикс было создание языка, в котором совершенно не будет бойлерплейта, который будет максимально краток, но при этом чрезвычайно красноречив. В течение двух лет я делал попытки добиться желаемого результата, но с чего бы я не начинал, в конце концов после отсечения всего лишнего у меня всегда получался 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')

Ну что ж, начнем...


Шаг 1: Вырываем с корнем скобочки из Lisp с помощью индентации Python


image
Хоть этот комикс и вызывает у меня улыбку каждый раз, как я на него смотрю, я совершенно не согласен с основным его утверждением. Скобочки — это не элегантно, скобочки — это бойлерплейт.


Поэтому первым делом в своем чудесном языке я решил избавится от скобочек. Лучшим решением стала имеющая значение индентация, прямо как в 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

Шаг 2: Делаем язык лаконичным


Что мне не нравилось в большинстве языков программирования, так это засоренность синтаксиса ничего не значащим, но занимающим место на экране бойлерплейтом — лишними ключевыми словами, бессмысленными знаками препинания и многим другим. Даже в 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))

Коллекции


Array


Основной коллекцией любого языка является массив. Вот так объявление и деконструкция массива выглядят в 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)

Object


Второй самой важной коллекцией является хешмапа. В 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 вобрал в себя лучшее из нескольких других языков:


  • простота и мощь Lisp
  • чистота индентации Python
  • рантайм и экосистема JavaScript

Код компилятора с примерами использования языка лежит тут https://github.com/sergeyshpadyrev/sova. Буду рад увидеть звезды на репозитории от всех тех, кому понравилась концепция языка и кто хотел бы, чтобы работа над ним продолжилась. Но сразу предупрежу, что пока что это исключительно proof of concept, и даже поиграться с языком из-за отсутствия документации и некоторых возможностей крайне трудно. Например, в языке пока что отсутствует обработка исключений, классы и другие необходимые вещи. И под Windows запустить его вряд ли получится.


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




К сожалению, не доступен сервер mySQL