Изучение Clojure, часть V

В предыдущей части мы узнали, что такое формы, как Clojure оценивает их в значения и как возвращать код в виде данных. Теперь мы узнаем о фундаментальной структуре данных, лежащей в основе всего этого: списке.

Смысл существования LISP

Название LISP происходит от «LISt Processor», и причина этого в том, что все в этом языке на самом деле является списком. То, что мы называем «списком», на самом деле является структурой данных Linked List, которая представлена в Clojure и всех других исторически сложившихся LISP в виде значений между скобками, разделенных пробелом.

("This" "is" "a" "list")
Вход в полноэкранный режим Выход из полноэкранного режима

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

Изображение: «Формы как связанные списки», созданное автором, является нелицензионным материалом с авторским левом.

(str "This " "is" " a " "list")
;; returns => "This is a list"

(apply str (list "This " "is" " a " "list"))
;; returns => "This is a list"
Вход в полноэкранный режим Выход из полноэкранного режима

Этот связный список формируется с помощью пар элементов, например, (1 (2 (3 ()))), которые обычно называются cons. В большинстве реализаций LISPs cons — это структура из пары элементов (A.B), в Clojure базовой структурой являются Seqs, которые мы рассмотрим позже, но в языке есть некоторые синтаксические хитрости, поэтому мы также называем их cons, и это то, что мы будем использовать сейчас.

Мы можем создавать списки как создание ячеек cons, каждая из которых получает значение элемента как A, а следующая ячейка как B в (A.B):

(cons "This" (cons "is" (cons "a" (cons "list" '()))))
;; returns => ("This" "is" "a" "list")
Войти в полноэкранный режим Выйти из полноэкранного режима

Как мы видим, последний элемент каждого списка — это ячейка nil ‘(), которая представляет собой конец списка и игнорируется, когда мы печатаем все элементы, поскольку это просто пустое значение.

Создание списков значений

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

Если мы попытаемся просто ввести наш список в REPL, то получим ошибку:

("This" "is" "a" "list")
;; returns => Execution error (ClassCastException) at user/eval2051 (REPL:1).
;; => class java.lang.String cannot be cast to class clojure.lang.IFn (java.lang.String is in module java.base of loader 'bootstrap'; clojure.lang.IFn is in unnamed module of loader 'bootstrap')
Enter fullscreen mode Выйти из полноэкранного режима

В большинстве случаев ошибки clojure.lang.IFn возникают из-за того, что что-то связано с функциями, в данном случае из-за того, что «This» не может быть прочитано как функция, поскольку Clojure ожидает, что это будет оператор, который будет применен к остальным элементам.

Поскольку мы создаем списки, нам нужно заключать их в кавычки, чтобы они воспринимались как данные:

'("This" "is" "a" "list")
;; returns => ("This" "is" "a" "list")
Войти в полноэкранный режим Выйти из полноэкранного режима

Или мы можем вместо этого использовать функцию list, как мы делали в примере выше:

(list "This" "is" "a" "list")
;; returns => ("This" "is" "a" "list")
Войти в полноэкранный режим Выйти из полноэкранного режима

Поскольку Clojure является динамическим языком, списки могут содержать любое заданное значение:

'(3.1415 "PI" π)
;; returns => (3.1415 "PI" π)
Войти в полноэкранный режим Выйти из полноэкранного режима

В данном случае мы создали список, содержащий float, string и char.

Получение значений из списка

Главное, что мы должны знать о списках, это то, что когда мы хотим получить значение из списка, мы должны получить доступ ко всем значениям до него. Представьте, что у нас есть следующий список:

'("Brock" "Misty" "Lt. Surge" "Erika")
Войти в полноэкранный режим Выйти из полноэкранного режима

Если мы хотим получить доступ к «Lt. Surge», который является третьим элементом, то сначала мы должны получить доступ к двум элементам перед ним «Brock» и «Misty». Это происходит потому, что это связный список и у нас нет прямой ссылки на позицию каждого значения, так как чем ближе элемент к концу списка, тем больше времени потребуется для получения его значения.

Для получения элементов у нас обычно есть три функции, которые мы комбинируем, чтобы пройти по списку: first, second, last и rest.

первая

Функция first возвращает элемент в начале списка.

(first '("Brock" "Misty" "Lt. Surge" "Erika"))
;; returns => "Brock"
Вход в полноэкранный режим Выход из полноэкранного режима

second

Функция second возвращает второй элемент списка.

(second '("Brock" "Misty" "Lt. Surge" "Erika"))
;; returns => "Misty"
Войти в полноэкранный режим Выход из полноэкранного режима

last

Функция last возвращает элемент в конце списка.

(last '("Brock" "Misty" "Lt. Surge" "Erika"))
;; returns => "Erika"
Войти в полноэкранный режим Выйти из полноэкранного режима

rest

Функции rest возвращают список без первого элемента.

(rest '("Brock" "Misty" "Lt. Surge" "Erika"))
;; returns => ("Misty" "Lt. Surge" "Erika")
Войти в полноэкранный режим Выход из полноэкранного режима

Получение определенных значений

В большинстве случаев при итерации по спискам мы комбинируем некоторые из этих функций, чтобы достичь нужного элемента. Представим, что мы хотим получить третий элемент.

(first (rest (rest '("Brock" "Misty" "Lt. Surge" "Erika"))))
;; returns => "Lt. Surge"
Войти в полноэкранный режим Выход из полноэкранного режима

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

Добавление значений

Есть две основные функции, которые мы используем для добавления значений в список. Первая — это функция cons, которая, как мы видели ранее, используется для построения списков, а вторая — conj, которая используется для создания нового списка, добавляя произвольное количество элементов в начало существующего.

(conj '("Koga" "Sabrina" "Blaine" "Giovanni") "Erika" "Lt. Surge" "Misty" "Brock")
;; returns => ("Brock" "Misty" "Lt. Surge" "Erika" "Koga" "Sabrina" "Blaine" "Giovanni")
Вход в полноэкранный режим Выход из полноэкранного режима

С другой стороны, когда мы используем cons, мы создаем новый список с переданным точным одним элементом в его начале.

(cons "Brock" '("Misty" "Lt. Surge" "Erika"))
;; returns => ("Brock" "Misty" "Lt. Surge" "Erika")
Вход в полноэкранный режим Выход из полноэкранного режима

Вот и все

Теперь вы стали понимать LISP немного больше. В следующих частях мы продолжим изучение некоторых фундаментальных структур данных, которые мы можем использовать для построения наших программ, и как использовать их для структурирования наших данных.

Оцените статью
Procodings.ru
Добавить комментарий