Skip to content

Liscript short overview

Ivana- edited this page Feb 12, 2017 · 1 revision

Contents

Introduction

назад к оглавлению

Основные сведения о Liscript. Язык является представителем Lisp-семейства, концептуально очень близок к Scheme (на написание вдохновила известная книга SICP), имеет единое пространство имен для значений любых типов (Lisp-1), с динамической типизацией и строгим порядком вычисления выражений. Существует несколько реализаций интерпретаторов, в том числе на разных языках и платформах. Время от времени в различные реализации вносятся изменения, порой радикальные, так что настоящее описание может не соответствовать текущим свойствам и возможностям отдельной реализации. Далее для определенности будем иметь в виду реализацию, написанную на Java.

Общая концепция данного семейства языков - вычисление списков. Сначала вычисляется первый элемент. Если результат вычисления - операция над базовыми типами, особая форма языка, функция или макрос - то весь список вычисляется по правилам этой операции. В остальных случаях строго последовательно вычисляются все элементы списка и в качестве результата возвращается последнее значение. Более подробно концепции и семантика Lisp/Scheme описаны в многочисленной литературе.

Types

назад к оглавлению

Базовые типы. Тип любого выражения 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

Numbers

назад к оглавлению

Числа. Поддерживается два типа чисел - целые (тип Integer в Java) и с плавающей точкой (Double). При арифметических операциях целые числа автоматически расширяются до плавающей точки если один из операндов имеет данный тип. Определены следующие операции:

    +  -  *  /  mod
    >  >=  <  <=  =  /=
    ?  (+ 1 2 3)
    => 6
    ?  (+ 1 2.0 3)
    => 6.0
    ?  (+ 1 2 (+ 3 1000.0))
    => 1006.0

Strings

назад к оглавлению

Строки. Пока определена всего одна операция - конкатенация:

    ++

При вычислении аргументы приводятся к строковому представлению:

    ?  (++ "'" 1 "абв" + "где" (quote (1 2 3)) "'")
    => '1абвAR_ADDгде(1 2 3)'

Special forms

назад к оглавлению

Особые формы ядра языка. Многие из них полиарны - допускают передачу ряда параметров и/или работают не совсем так, как их одноименные аналоги в других диалектах Lisp (см. примеры):

    eq? : проверка равенства всех переданных объектов

    def, set!, get : создание/изменение/получение значений

    cons, car, cdr : конструктор и геттеры списка

    typeof  : определяет тип выражения
    quote   : оператор цитирования
    cond    : многокейсовый условный оператор
    eval    : вычисляет выражение
    eval-in : вычисляет выражение в переданном контексте

    lambda  : конструктор функции
    macro   : конструктор макроса

    read, print : ввод/вывод

В Java-реализации присутствуют дополнительные особые формы

    class   : обращение к классу по переданному имени
    java    : вызов конструктора / методов класса

eq?, def, set!, get:

назад к оглавлению

    ?  (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

cons, car, cdr:

назад к оглавлению

    ?  (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 ()))
    => ()

cond, eval, eval-in, lambda, macro:

назад к оглавлению

    ?  (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

read, print:

назад к оглавлению

    ?  (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

class, java:

назад к оглавлению

    ?  (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

Functions

назад к оглавлению

Функции. Конструируются особой формой 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

Macroses

назад к оглавлению

Макросы. 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

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

назад к оглавлению