-
Notifications
You must be signed in to change notification settings - Fork 3
Liscript short overview
Основные сведения о Liscript. Язык является представителем Lisp-семейства, концептуально очень близок к Scheme (на написание вдохновила известная книга SICP), имеет единое пространство имен для значений любых типов (Lisp-1), с динамической типизацией и строгим порядком вычисления выражений. Существует несколько реализаций интерпретаторов, в том числе на разных языках и платформах. Время от времени в различные реализации вносятся изменения, порой радикальные, так что настоящее описание может не соответствовать текущим свойствам и возможностям отдельной реализации. Далее для определенности будем иметь в виду реализацию, написанную на Java.
Общая концепция данного семейства языков - вычисление списков. Сначала вычисляется первый элемент. Если результат вычисления - операция над базовыми типами, особая форма языка, функция или макрос - то весь список вычисляется по правилам этой операции. В остальных случаях строго последовательно вычисляются все элементы списка и в качестве результата возвращается последнее значение. Более подробно концепции и семантика Lisp/Scheme описаны в многочисленной литературе.
Базовые типы. Тип любого выражения Liscript может принадлежать множеству базовых типов, включающему
- булевские значения
- числа (подтипы чисел определяются конкретной реализацией)
- строки
- особые формы (встроенные функции ядра языка)
- символы
- cons-списки
- функции
- макросы
В зависимости от реализации, состав типов может расширяться вплоть до значений любого типа, поддерживаемых платформой. Для определения типа выражения существует особая форма typeof, возвращающая строковое представление типа. На основе этой формы в стандартной библиотеке определены макросы, возвращающие истину при соответствующем типе выражения: list?, func? и т.д. Примеры выражений, имеющих значения базовых типов:
? (typeof true)
=> Boolean
? (typeof 1)
=> Integer
? (typeof 1.0)
=> Double
? (typeof "abc")
=> String
? (typeof +)
=> SpecialForm
? (typeof cons)
=> SpecialForm
? (typeof (quote (+ 1 2 3)))
=> ConsList
? (typeof map)
=> Func
? (typeof defn)
=> Macr
? (typeof (quote abc))
=> SymbolТипы платформы реализации. В Java-версии значения могут принимать любые типы, доступные в данном сеансе работы приложения:
? (typeof (class "java.lang.Math"))
=> Class
? (typeof (java (class "java.lang.Math") "random"))
=> Double
? (typeof (class "java.awt.Color"))
=> Class
? (typeof (java (class "java.awt.Color") "new" 255 0 0))
=> Color
? (typeof (class "java.util.HashMap"))
=> Class
? (typeof (java (class "java.util.HashMap") "new"))
=> HashMapЧисла. Поддерживается два типа чисел - целые (тип Integer в Java) и с плавающей точкой (Double). При арифметических операциях целые числа автоматически расширяются до плавающей точки если один из операндов имеет данный тип. Определены следующие операции:
+ - * / mod
> >= < <= = /= ? (+ 1 2 3)
=> 6
? (+ 1 2.0 3)
=> 6.0
? (+ 1 2 (+ 3 1000.0))
=> 1006.0Строки. Пока определена всего одна операция - конкатенация:
++При вычислении аргументы приводятся к строковому представлению:
? (++ "'" 1 "абв" + "где" (quote (1 2 3)) "'")
=> '1абвAR_ADDгде(1 2 3)'Особые формы ядра языка. Многие из них полиарны - допускают передачу ряда параметров и/или работают не совсем так, как их одноименные аналоги в других диалектах Lisp (см. примеры):
eq? : проверка равенства всех переданных объектов
def, set!, get : создание/изменение/получение значений
cons, car, cdr : конструктор и геттеры списка
typeof : определяет тип выражения
quote : оператор цитирования
cond : многокейсовый условный оператор
eval : вычисляет выражение
eval-in : вычисляет выражение в переданном контексте
lambda : конструктор функции
macro : конструктор макроса
read, print : ввод/выводВ Java-реализации присутствуют дополнительные особые формы
class : обращение к классу по переданному имени
java : вызов конструктора / методов класса ? (eq? "абв" "абв" (++ "а" "б" "в"))
=> true
? (def a1 1 b1 (+ a1 1) (++ "c" a1) (+ a1 b1))
=> OK
? (++ "a1 = " a1 ", b1 = " b1 ", c1 = " c1)
=> a1 = 1, b1 = 2, c1 = 3
? (set! (++ "a" 1) 5 c1 10)
=> OK
? (++ "a1 = " a1 ", b1 = " (get (++ "b" 1)) ", c1 = " c1)
=> a1 = 5, b1 = 2, c1 = 10 ? (def a (cons 1 2 3))
=> OK
? a
=> (1 2 3)
? (cons a 4)
=> ((1 2 3) 4)
? (cons 0 a)
=> (0 1 2 3)
? (cons 0 a nil)
=> (0 (1 2 3))
? (car a)
=> 1
? (car 5)
=> 5
? (car (quote ()))
=> ()
? (cdr a)
=> (2 3)
? (cdr 5)
=> ()
? (cdr (quote ()))
=> () ? (def f (lambda (x) (cond (< x 0) -1 (> x 0) 1 0)))
=> OK
? (cons (f -100) (f 100.5) (f 0))
=> (-1 1 0)
? ((def i 0) (while (< i 5) (print i ", ") (set! i (+ 1 i))))
=> 0, 1, 2, 3, 4, OK
? (eval (cons + (quote (1 2 3))))
=> 6
? (def m (macro (i r) (cond (<= i 0) (quote r) (m (- i 1) (cons i r)))))
=> OK
? (m 3 nil)
=> (cons (- (- 3 1) 1) (cons (- 3 1) (cons 3 nil)))
? (eval (m 3 nil))
=> (1 2 3)
? (def f (lambda (x y) lambda ()) a (f 1 2) b (f 10 20))
=> OK
? (eval-in a (+ x y))
=> 3
? (eval-in b (def x 30) (+ x y))
=> 50 ? (def read-int (lambda () (print "Введите целое число: ") (def n (read)) (cond (eq? "Integer" (typeof n)) n (read-int))))
=> OK
? ((def n (read-int)) (++ "Удвоим введенное число: " (* 2 n)))
=> Введите целое число: "utrutr"
Введите целое число: 3.7
Введите целое число: 23
Удвоим введенное число: 46 ? (java (class "java.lang.Math") "random")
=> 0.2343446502811879
? (java (class "java.lang.Math") "random")
=> 0.6890430987479069
? ((def m (java (class "java.util.HashMap") "new")) m)
=> {}
? ((java m "put" 1 "a") (java m "put" "2" 33) m)
=> {1=a, 2=33}
? (java m "get" 1)
=> aФункции. Конструируются особой формой lambda (или макросом defn, определенном с использованием lambda в стандартной библиотеке). В рекурсивном варианте эвалюатора рализована автоматическая оптимизация хвостовых вызовов - ТСО, в итеративном все вычисления производятся в куче и переполнения стека не происходит ни при каких вызовах. При создании любая функция захватывает текущий контекст (создает замыкание). При вызове (применении) аргументы вычисляются строго последовательно, связываются с именами формальных параметров функции в отдельном окружении, подчиненном захваченному функцией контексту, и в этом окружении вычисляется тело функции.
Примеры ТСО:
? (defn is-even (n) (cond (= n 0) true (is-odd (- n 1))))
=> OK
? (defn is-odd (n) (cond (= n 0) false (is-even (- n 1))))
=> OK
? (is-even 10000)
=> true
? (is-even 10001)
=> false
? (defn go (n a) (cond (<= n 0) a (go (- n 1) (+ n a))))
=> OK
? (go 10000 0)
=> 50005000Соглашения о вызове. Если аргументов передано больше, чем формальных параметров функции, то последний формальный параметр свяжется со списком вычисленных оставшихся переданных аргументов. Если меньше - то эти формальные параметры окажутся не связаны в окружении вычисления функции и при вычислении ее тела будут искаться в окружениях верхнего уровня иерархии. Это не считается ошибкой, но надо аккуратно использовать этот механизм.
? ((defn f (a b) (++ "a = " a ", b = " b)) (f 1 2 3 4 5))
=> a = 1, b = (2 3 4 5)
? (def s "Global ")
=> OK
? (defn f (i s) (cond (< i 1) "end" (++ s (f (- i 1)))))
=> OK
? (f 3 "local ")
=> local Global Global end
? (defn g (i a s) (cond (< i 1) a (g (- i 1) (++ s a))))
=> OK
? (g 3 "end" "local ")
=> Global Global local endМакросы. Liscript поддерживает так называемые runtime-макросы, которые являются объектами первого класса языка. Их можно создавать, связывать с именами, возвращать как результат функций и выполнять в процессе работы (интерпретации кода). Полученное из текста исходного кода выражение сразу начинает интерпретироваться, без предварительной стадии раскрытия макросов, поэтому макросы остаются полноправными типами языка и раскрываются и вычисляются в процессе интерпретации по всем правилам вычисления макросов - сначала производится подстановка в тело макроса невычисленных переданных аргументов а затем это тело макроса вычисляется в текущем окружении (в отличие от тела функции, которое всегда вычисляется в отдельном собственном окружении, в котором уже присутствуют предварительно вычисленные значения фактических параметров). Соглашения о вызове макросов в плане связи имен формальных параметров с фактическими аналогичны соглашениям о вызове функций, с учетом отличия в передаче параметров в макрос без предварительного вычисления.
? (def m (macro (x) (++ (quote x) " = " x)))
=> OK
? (m (+ 1 2))
=> (+ 1 2) = 3
? (def f (lambda (x) (++ (quote x) " = " x)))
=> OK
? (f (+ 1 2))
=> x = 3Вот вкратце и все описание языка. Набор часто используемых определений, функций и макросов реализован посредством стандартной библиотеки, которая автоматически подгружается из файла при старте приложения. Можно посмотреть ее код и при желании добавить или изменить ее содержание. То же касается кода любых демо примеров.