Как я устал от JavaScript и создал свой собственный язык программирования

9c7f964edad8985378ca67cd9d37812d.jpg

За свою карьеру я успел поработать со множеством языков программирования. Писал flash-игры на ActionScript 3 и Android-игры на Java, сервера на Java, Scala и NodeJS (JavaScript), скрипты на Python, веб и мобильные приложения на React (JavaScript). И на каком бы языке я не писал, меня не покидало ощущение, что синтаксис этого языка слишком многословен, полон излишеств, шума и синтаксического бойлерплейта, мешающего пониманию написанного кода.

Уже тогда я начал делать первые попытки создать свой собственный язык программирования, лишенный этих недостатков. Но для начала мне надо было определиться с тем, что именно не так с существующими языками и каким образом можно формализовать параметры «качества» языка, чтобы начать их улучшать. После долгих размышлений и сравнений я выработал для себя несколько параметров:

  • Человекочитаемость— идеальный язык должен быть читабельным и легким для понимания человеком

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

  • Внутренняя непротиворечивость— синтаксические конструкции языка должны быть внутренне согласованы и хорошо сочетаемы

  • Чистота и красота — язык должен выглядеть максимально чисто и эстетически красиво

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

Единственным параметром, которому не до конца соответствовал Lisp, был параметр чистоты и красоты. Уходящие за горизонт заборы из скобочек не нравились мне с точки зрения эстетики. И я нашел решение: чтобы уменьшить количество скобочек можно сделать значимой индентацию, как это сделано в Python.

Я продолжал заниматься своими экспериментами в приватном репозитории гитхаба, то забрасывая их, то снова к ним возвращаясь. А тем временем моим основным рабочим языком становился JavaScript.

Как я перестал бояться и полюбил JavaScript

Изначально я не любил JavaScript. Не потому что в нем было что-то не так, а просто потому что я подвергся влиянию популярного мнения, что JavaScript — это неполноценный язык для дурачков, а настоящие программисты пишут только на «тру» языках.

Незадолго до знакомства с JavaScript моим основным рабочим языком была Scala. Вот он-то мне казался по-настоящему крутым, ибо он был полон сверхсложной эквилибристки типов и даже для написания простейшего http-сервера на Scala требовался высший пилотаж.

Поэтому когда я открыл для себя NodeJS, я сначала не поверил, что можно писать так просто и лаконично. И чем дольше я знакомился с JavaScript, тем более гениально спроектированным и внутренне согласованным он мне казался. Система объектов и прототипов позволяет легко писать код в объектно-ориентированном стиле, а то, что в основе всего этого лежит функция, делает легким и приятным программирование в привычном мне функциональном стиле. А динамическая природа языка делает его фантастически гибким.

Кроме того в экосистеме JavaScript меня поразило обилие инструментов и хороших библиотек. В Scala в порядке вещей была ситуация, когда библиотека была написана для работы над PhD и заброшена после защиты диссертации. В Java библиотеки были в основном рабочими, но многие из них были абсолютно не дружелюбными — лежали не пойми где, не имели документации в нормальном виде, имели ужасный API и так далее. У каждого языка была своя ужасающая система сборки и управления зависимостями вроде maven, gradle или sbt.

В JavaScript же программисту доступен удобный пакетный-менеджер npm, полный прекрасно отлаженных библиотек с хорошей документацией, удобным API и дружелюбным коммьюнити. То, что кажется обыденностью в экосистеме JavaScript, в экосистемах других языков не является таковым: например, не во всех случаях является возможным понять что же делает какая-нибудь Ruby-библиотека по ее readme в гитхабе.

Отдельным преимуществом JavaScript является то, что сейчас его можно запустить где угодно. Для веб-приложений уже давно стандартом является React, на мобильных девайсах очень распространен React Native, на сервере бешено популярен NodeJS. JavaScript стал де-факто универсальным языком, на котором можно писать под что-угодно. Даже миллиарды микроволновок и утюгов, которые раньше были вотчиной Java, скоро все будут программироваться на JavaScript.

Но есть ли у JS какие-нибудь минусы?

Что не так с JavaScript

Я программирую на JavaScript, в основном, в чисто функциональном стиле. Я использую const, не мутирую объекты и массивы, использую чистые функции. Так что мой код выглядит примерно так:

const incrementNumbers = numbers => numbers.map(number => number + 1)
const takeNumbersGreaterThan = threshold => numbers => numbers.filter(number => number > threshold)

const func = (numbers, threshold) => {
    const incrementedNumbers = incrementNumbers(numbers)
    const filteredNumbers = takeNumbersGreaterThan(threshold)(incrementedNumbers)
    return incrementedNumbers
}

Не смотрите, что этот код является переусложенным и может быть написан в разы проще. Здесь я хочу продемонстрировать то, что если вы как и я пишите код на JavaScript в исключительно функциональном стиле, то кучу места на вашем экране занимает абсолютно ненужный вам синтаксический мусор вроде слова «const», когда все и так всегда константа, и фигурных скобочек, когда все и так разделено индентацией.

Собственно, именно эти недостатки и натолкнули меня на возвращение к проекту собственного языка программирования. Ну, а из-за описанных мною ранее громадных преимуществ экосистемы именно JavaScript я сделал платформой для своего языка.

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

Порядок выполнения операций

Давайте начнем с азов. А именно порядка выполнения операций. Как я писал выше, основой синтаксиса для моего языка послужил Lisp, но чтобы сократить количество скобочек, я решил использовать индентацию.

Поэтому в моем языке выражение:

a (b c) (d e)

можно также записать как:

a
  b c
  d e

Ну, а выражение:

a (b (c d))

может быть записано так:

a
  b (c d)

или так:

a
  b
    c d

Присваивание

Самый базовый оператор в любом языке программирования — это оператор присваивания. В случае функциональных иммутабельных языков программирования правильнее было бы сказать не присваивание, а объявление константы.

В Una константа объявляется так:

= name "John"
= number 1

Если мы хотим присвоить константе значение вычислений, то делаем так:

= z (calculate x y)

а можно обойтись и без скобок, этот оператор позволяет сделать так:

= z calculate x y

или так

= z calclulate
  x
  y

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

Арифметика, сравнения и логика

В Una вы можете пользоваться стандартными JavaScript операторами арифметики, сравнения и логики.

Арифметические операции выглядят так:

= a (+ 1 2)
= b (- 2 1)
= c (* 3 2)
= d (/ 4 2)
= e (% 5 2)

Все из них могут принимать множество параметров. Например:

= sum (+ 1 2 3 4 5)

А минус может принимать и один параметр, что является отрицанием числа:

= a (- 1)
= b -1

Вот пример сложных арифметических расчетов:

= a +
  * 2 4
  / 9 3
  + (* 3 2) (/ 4 2)
  *
    + 1 2
    / 6 3

Конечно также вы можете использовать любые функции из стандартной библиотеки JS:

= randomNumber Math.random ()

Операторы сравнения имеют некоторые отличия от JavaScript. Давайте посмотрим:

= a (== 1 1)
= b (~= 1 '1')
= c (!= 1 '1')
= d (!~= 1 '2')
= e (> 2 1)
= f (>= 2 1)
= g (< 1 2)
= h (<= 1 2)

Оператор == и != — это точное по типу сравнение, аналог === и !== в JavaScript. Для неточного по типу сравнения нужно применять ~= и !~=.

Логические операторы тоже немного отличаются:

= a (& true false)
= b (| true false)
= c (! true)
= d !c

Как вы видите, вместо && используется &, а вместо || используется |.

Условные операторы

Тернарный условный оператор работает также как и в JavaScript:

= message
  ? (> 2 1) "Greater" "Less"

А еще существует условный оператор с возвращением значения:

?! (== number 1) "One"

Он используется в функцях и других вычислимых блоках, чтобы вернуть значение по условию и соответствует следующему коду на JavaScript:

if (number === 1) return "One"

Коллекции

В Una есть две базовые коллекции: массивы и объекты.

Массив задается следующим образом:

= numbers :: 1 2 3 4 5

А объект задается так:

= user :
  name 'John'
  age 13
  passport :
    id 1
    country 'USA'

В Una работают все трюки JavaScript.

Можно задать элемент массива или поле объекта готовой константой:

= a 1
= numbers :: a 2 3

= name 'John'
= user :
  name
  age 13

Работает троеточие:

= threeNumbers :: 1 2 3
= fiveNumbers :: ...threeNumbers 45

= userFields :
  name 'John'
  age 13
= user :
  id 1
  gender 'm'
  isAlive true
  ...userFields

Работает деконструкция:

= numbers :: 1 2 3
= (:: one two three) numbers
console.log one

= user : (name 'John') (age 12)
= (: name) user
console.log name

А чтобы взять определенный элемент массива или значение определенного поля объекта используется точка:

= list :: 1 2 3
= object : (a 1) (b 2)

console.log (. list 0)
console.log (. object 'a')

Также точка может быть использована, чтобы взять конкретное поле объекта таким образом:

= object : (a 1)
console.log (.a object)

Этот синтаксис удобен для вызова функций, лежащих внутри объекта.

Но давайте пойдем дальше и рассмотрим самую крутую фишку языка — стрелочные симметрии.

Стрелочная симметрия синхронных вычислений

Правая стрелка симметрии синхронных вычислений является функцией:

= sum -> (x y)
  + x y

= onePlusTwo -> ()
  = one 1
  = two 2
  + one two

Последняя строчка функции всегда является возвращаемым значением. Данный код на JavaScript будет выглядить так:

const sum = (x, y) => x + y
const onePlusTwo = () => {
  const one = 1
  const two = 2
  return one + two
}

Для преждевременного прерывания выполнения функции можно использовать вышеупомянутый условный оператор:

= isSumGreaterThanFiveIfXisNotZero -> (x y)
  ?! (== x 0) "X is zero"
  = sum (+ x y)
  ? (> sum 5)
    "Greater"
    "Less"

Левая стрелка симметрии синхронных вычислений является мнгновенно выполняемым блоком кода, также называемым Immediately Invoked Function Expression:

= result <-
  = a 1
  = b 2
  + a b

Этот оператор можно использовать для чего-то вроде задания константы значением по каким-то условиям:

= result <-
  ?! (== value 0) "Zero"
  ?! (== value 1) "One"
  ? (< value 10) "Less than ten" "More than ten"

Также его можно использовать для того, чтобы выполнить какой-либо код в теле условного оператора. Например, вывести что-то в консоль по какому-либо условию:

= func -> number
  ? (== number 0)
    <-
      console.log "Number is zero!"
  + number 1

Стрелочная симметрия асинхронных вычислений

Правой стрелкой симметрии асинхронных вычислений является асинхронная функция:

= getUserPosts --> user
  database.loadPosts user.postIds

Левой стрелкой симметрии асинхронных вычислений является await:

= checkIfUserIsAdmin --> userId
  = user <-- (database.loadUser userId)
  == user.role 'admin'

Стрелочная симметрия модулей

Правой стрелкой модульной симметрии является импорт:

=-> './index.css'
=-> 'react' React
=-> './validation' (: validateEmail)

Он превращается в import или в require в зависимости от соответствующей настройки компилятора.

Левой стрелкой модульной симметрии является экспорт.

Дефолтный — export default в JavaScript:

<-= a

Экспорт отдельной константы export const в JavaScript:

<-= = a 1

Экспорт нескольких констант — export {a, b} в JavaScript:

<-= ()
  a
  b

Вы можете импортировать любые JavaScript-модули в Una, и любые Una-модули в JavaScript. Все модули полностью совместимы друг с другом.

Стрелочная симметрия ошибок

Правой стрелкой симметрии ошибок является try-catch:

|->
  <-
    = getName null
    getName ()
  -> error
    console.log error
    'John'

В отличие от JavaScript в Una этот оператор является выражением, всегда возвращающим значение, а также этот оператор не имеет блока finally.

Если нужно исполнить асинхронный код, то соответствующие стрелки симметрии заменяются на асинхронные:

|->
  <--
    getNameAsync ()
  --> error
    console.log error
    "John"

Левой стрелкой симметрии ошибок является выброс ошибки:

= addOneToNumber -> number
  ?! (isNaN number)
    <-| "number is not valid"
  + number 1

Стрелочная симметрия чейнинга

Правая стрелка симметрии чейнинга подставляет выражение как последний параметр следующей за ним функции. Это нужно для удобной работы с такими библиотеками как ramda:

=-> 'ramda' R
= electronics ::
  :
    title ' iPhone '
    type 'phone'

= phones |>
  electronics
  R.find
    R.propEq 'type' 'phone'
  R.prop 'title'
  R.toUpper
  R.trim

Левая стрелка симметрии чейнинга подставляет выражение как первый параметр следующей за ним функции. Это удобно для работу со стандартными методами массивов и lodash:

= sum <| (:: 1 2 3)
  .map (-> x (+ x 1))
  .filter (-> x (> x 2))
  .reduce (-> (x y) (+ x y)) 0

Без чейнинга это выражение выглядело бы уродски:

= sum .reduce
  .filter
    .map (:: 1 2 3) (-> x (+ x 1))
    -> x (> x 2)
  -> (x y) (+ x y)
  0

Интерполяция строк

Одной из самых популярных возможностей современный языков программирования является интерполяция строк. В Una она выглядит не так красиво как в JavaScript, но все же пользоваться ею очень удобно:

= name 'John'
= count 5
= fruit 'apples'

= text `
  'Hello, ${0}' name
  'I have ${0} ${1}'
    count
    fruit

console.log text

Этот пример выведет:

Hello, John
I have 5 apples

Так же вы можете провести интерполяцию строк по какой-либо функции — чтобы сделать это пришлите функцию первым параметром. Это очень часто используется например в styled-components:

= color 'red'
= Container `
  styled.div
  'background-color: ${0};' color

React и React Native

Для написания веб-приложения на React или мобильного приложения на React Native вы не сможете использовать JSX. Вместо этого вы можете использовать функцию createElement, лежащую в основе React.

=-> './index.css'
=-> 'react' React
=-> 'react-dom' ReactDOM
=-> './styles' S

= (: (createElement e)) React

= App -> ((: name))
  = (:: count setCount) (React.useState 0)
  e S.Container :
    e S.Hello (: (color 'green')) 'Hello, '
    e S.Name : name
    e S.IncrementCount
      : (onClick (-> () (setCount (+ count 1))))
      'Press me'
    e S.Count : count

ReactDOM.render
  e App (: (name 'John'))
  document.getElementById 'root'

Первым аргументом функция всегда принимает компонент, вторым объект с параметрами (для пустого объекта в Una вы можете использовать :), а все последующие параметры — это chilldren.

Что еще предстоит сделать

Язык еще не закончен, многое еще предстоит. В скором будущем я постараюсь сделать:

  • регулярные выражения

  • возможность заинстансить класс (оператор new)

  • комментарии в коде

  • плагин для Visual Studio Code с подсветкой синтаксиса

  • и многое другое…

Заключение

Язык Una устанавливается в любой JavaScript-проект простейшим babel-плагином, который компилирует все файлы с расширением .una в JavaScript. Вы можете найти инструкции по установке, подробную документацию и примеры в моем репозитории на гитхабе. Я буду жутко рад, если вы поиграетесь с языком, протестируете его и дадите фидбек, что можно было бы улучшить, а что стоит починить. Ну и от звездочек конечно не откажусь.

Спасибо за внимание!

© Habrahabr.ru