Определения, функции и чтение скобок
открытый урокЭтот раздел читается без входа. Войди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти
Определения, функции и чтение скобок
Синтаксис данных закрыт, берёмся за синтаксис кода. Главный навык урока: читать скобочный код как дерево вызовов, а не как поток символов. С него и начнём, а дальше
defиvar, функции с необязательными и переменными аргументами, деструктуризация.
Как читать скобки
Это единственное, что пугает в лиспах. На разбор уйдёт минута, и лучше потратить её сейчас, чем спотыкаться весь урок.
Правило одно: (f a b) означает вызвать f с аргументами a и b. Первый элемент списка это то, что вызывают, остальные аргументы. Всё.
Вложенность читается изнутри наружу:
(* 2 (+ 3 4)) # 14
Пощёлкай по примерам, слева дерево вызовов, справа та же запись в привычном виде:
Что это даёт взамен привычной записи: арифметические операторы это обычные функции, поэтому принимают сколько угодно аргументов, а приоритетов операций попросту не существует. Структура выражения задана скобками однозначно, и спорить про то, что вычислится раньше, не о чем.
Специальные формы
Некоторые конструкции выглядят как вызовы, но вычисляются иначе. Например, if не может быть функцией: функция сначала вычислила бы оба аргумента, а потом выбрала.
Будь if функцией, напечатались бы обе строки: функция получает аргументы уже вычисленными.
Таких специальных форм ровно двенадцать: def, var, fn, if, while, do, set, quote, quasiquote, splice, break, upscope. Всё остальное, что выглядит управляющей конструкцией (when, cond, each, loop, ->), это макросы, построенные поверх этой дюжины. В уроке про макросы мы научимся писать такие же, и вот это по-настоящему меняет ощущение от языка: граница между “встроено в язык” и “написано в библиотеке” почти исчезает.
Splice
Оператор ; перед коллекцией раскрывает её в список аргументов:
В Clojure и Scheme для этого нужна функция apply, в Janet это часть синтаксиса и работает в любой позиции вызова.
Комментарии
# Комментарий до конца строки
(+ 1 2) # и в конце строки тоже
Точка с запятой это не комментарий, в отличие от Scheme и Clojure. Это splice. Ошибка обидная: скопировал строку из чужого кода, получил невнятную ошибку компиляции.
def и var
По умолчанию бери def. var нужен там, где значение действительно меняется во времени: счётчик, аккумулятор. Для арифметики над var есть сокращения:
def это не неизменяемость. Запись (def arr @[1 2 3]) запрещает связать имя arr с другим значением, но сам массив остаётся изменяемым, и (array/push arr 4) сработает. def защищает имя, а не данные. Та же разница, что между const и заморозкой объекта в JavaScript, мы её разбирали в уроке про freeze и неизменяемость.
Функции
Докстрока это не комментарий: она уезжает в метаданные определения и достаётся через (doc square). Привыкай писать её сразу, это дёшево и окупается на второй неделе жизни кода.
Анонимная функция это fn, а defn просто def плюс fn:
Необязательные и переменные аргументы
Деструктуризация
Работает и в списке параметров, и в def:
Тело возвращает последнее выражение
Ключевого слова return нет:
(defn describe [n]
(print "проверяю " n) # выполняется, результат отбрасывается
(if (even? n) :even :odd)) # это и есть результат
Из этого следует полезное свойство: в Janet почти всё это выражение, и оно что-то возвращает. if возвращает значение выбранной ветки, поэтому тернарный оператор языку не нужен.
Первый скрипт
Создай файл hello.janet:
#!/usr/bin/env janet
(defn greet [name]
(string "Привет, " name "!"))
(defn main [& args]
(def name (get args 1 "мир"))
(print (greet name)))
Запуск:
janet hello.janet
janet hello.janet Janet
Привет, мир!
Привет, Janet!
Что здесь происходит:
(defn main [& args] ...)это точка входа. Если в файле определена функцияmain, Janet вызовет её после загрузки файла.argsэто аргументы командной строки. Нулевой элемент имя самого скрипта, поэтому первый пользовательский аргумент лежит в(get args 1).- Третий аргумент
getэто значение по умолчанию, если ключа нет. Удобнее, чем проверять наnilруками. (string ...)это конкатенация с приведением типов, склеивает что угодно, не только строки.
Чтобы файл запускался как обычная программа, добавь строку shebang (она уже в примере) и права на выполнение:
chmod +x hello.janet
./hello.janet Janet
Загрузить файл и остаться в REPL. janet -r hello.janet выполняет файл и оставляет тебя в REPL с его определениями: это та самая петля из трёх шагов, которую мы завели в 31-janet/02 · Установка и REPL: разговор вместо чтения.
Что запомнить
defсвязывает имя,varзаводит изменяемую ячейку,setменяет толькоvar.defзащищает имя, а не данные: массив подdefостаётся изменяемым.&optдаёт необязательные аргументы,&собирает остаток, деструктуризация работает и в параметрах, и вdef.- Тело возвращает последнее выражение,
returnв языке нет. (f a b)это вызовf. Первый элемент вызывается, остальные аргументы, приоритетов операций нет.- Специальных форм двенадцать, всё остальное макросы поверх них.
;это splice, а не комментарий. Комментарий это#.- Функция
mainв файле это точка входа,(get args 1)первый пользовательский аргумент.
Упражнения
Дальше
Дальше самая важная тема первой половины раздела: почему (= @[1] @[1]) даёт false, что делает присваивание и как осознанно выбирать между изменяемой и неизменяемой веткой.
домашка