Подробное руководство по распространенным типам lisp

Может быть, этот вопрос слишком общий, тем не менее, я попробую: есть ли какое-нибудь исчерпывающее руководство по типам в common lisp?

Я немного запутался в этой теме:

Почему непримитивные типы, объявленные в make-array :element-type, продвигаются до t? Есть ли возможность проверки реального объявленного типа во время компиляции или во время выполнения?

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

То же самое для объявлений типов функций с declare.. Это просто намеки на оптимизацию для компилятора?

Кроме того, могу ли я использовать спецификаторы пользовательских типов, включая satisfies в вышеупомянутых местах для некоторых надежных проверок, или их можно использовать только для явных проверок с typep и т. д.?

Как видите, у меня в голове неразбериха, поэтому буду очень признателен за аккуратное руководство (или набор руководств).

Я на SBCL, но также был бы рад узнать о различиях между реализациями.


person leetwinski    schedule 08.06.2016    source источник
comment
Вероятно, в этом вопросе слишком много вопросов, что делает его слишком широким. Но всеобъемлющее руководство, вероятно, представляет собой лишь подборку деталей из HyperSpec. Например, для объявлений типа вы должны посмотреть Declaration TYPE, который точно описывает, что означает объявление типа (почти то, что у вас есть неопределенное поведение, если значение переменной на самом деле не является этим типом). Но неопределенное поведение может быть полезным, потому что компилятор может: (i) вставлять проверки типов для обеспечения безопасности и сообщать вам, если...   -  person Joshua Taylor    schedule 08.06.2016
comment
дела идут не так; или (ii) используйте оптимизированный код, который не будет проверять типы, потому что вы обещаете, что значение будет иметь правильный тип. То, что он будет делать, может зависеть от других объявлений, таких как значение любого ОПТИМИЗИРОВАТЬ объявления.   -  person Joshua Taylor    schedule 08.06.2016
comment
Другой частью HyperSpec, которая может быть частью всеобъемлющего руководства, будет глава 4. Типы и классы. Этот раздел на самом деле немного скуден по содержанию, но содержит ссылки практически на все соответствующие записи (например, где используются типы, что может их определять, как они используются и т. д.).   -  person Joshua Taylor    schedule 08.06.2016
comment
Спасибо! Hyperspec хорош, но он не отвечает на все вопросы: например, о типе make-array или типе слота.. или, может быть, я просто не заметил..   -  person leetwinski    schedule 08.06.2016
comment
Запись в defclass описывает значение параметра :type: Параметр слота :type указывает, что содержимое слота всегда будет иметь указанный тип данных. Он эффективно объявляет тип результата универсальной функции читателя при применении к объекту этого класса. Последствия попытки сохранить в слоте значение, не удовлетворяющее типу слота, не определены. Опция слота :type более подробно обсуждается в Разделе 7.5.3 (Наследование слотов и опций слотов).   -  person Joshua Taylor    schedule 08.06.2016
comment
В этом описании говорится, что (i) вы можете заглянуть в 7.5.3, чтобы узнать больше об этой опции, и, что более важно, (ii) что (ii.a) она эффективно объявляет тип результата универсальной функции читателя при применении к объект этого класса и (ii.b) сохранение значения другого типа имеет неопределенные последствия. Итак, на данный момент вам просто нужно выяснить, какие эффекты имеет объявление типа результата (универсальной) функции, и какие неопределенные последствия могут возникнуть, если вы попытаетесь сохранить другой тип значения (и это может зависеть от объявлений оптимизации, так далее.).   -  person Joshua Taylor    schedule 08.06.2016
comment
Также взгляните на sellout.github.io/2012/ 03/03/common-lisp-type-иерархия   -  person coredump    schedule 08.06.2016
comment
@coredump, вау! это тоже одна из вещей, о которых я хотел попросить, но постеснялся)   -  person leetwinski    schedule 08.06.2016
comment
@Coredump, это очень хороший ресурс. Если вы это сделали, молодец; если нет, то хорошая находка!   -  person Joshua Taylor    schedule 08.06.2016
comment
@JoshuaTaylor Спасибо, это не мое, но мне это тоже очень нравится. Я переместил ссылку на ответ на случай, если комментарии будут удалены.   -  person coredump    schedule 09.06.2016


Ответы (3)


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

CL-USER> (declaim (optimize (safety 3)))
NIL
CL-USER> (defclass foobar () ())
#<STANDARD-CLASS COMMON-LISP-USER::FOOBAR>
CL-USER> (defun foo (a)
           (make-array 1 :element-type 'foobar
                         :initial-contents (list a)))
FOO
CL-USER> (foo (make-instance 'foobar))
#(#<FOOBAR {1005696CE3}>)
CL-USER> (foo 12)
;=> ERROR
CL-USER> (declaim (ftype (function (integer integer) integer) quux))
(QUUX)
CL-USER> (defun quux (a b)
           (+ a b))
QUUX
CL-USER> (quux 12 12)
24 (5 bits, #x18, #o30, #b11000)
CL-USER> (quux 12 "asd")
;=> ERROR

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

(declaim (optimize (safety 3)))

(defun some-predicate-p (a)
  (format t "~&Checking type...")
  (integerp a))

(deftype foo () `(satisfies some-predicate-p))

(defclass bar ()
  ((foo :type foo :initarg :foo)))

(declaim (ftype (function (foo) list) qwerty))
(defun qwerty (foo)
  (loop repeat 10 collecting (make-instance 'bar :foo foo)))

(qwerty 12)
; Checking type...
; Checking type...
; Checking type...
; Checking type...
; Checking type...
; Checking type...
; Checking type...
; Checking type...
; Checking type...
; Checking type...
; Checking type...
;=> (#<BAR {1003BCA213}> #<BAR {1003BCA263}> #<BAR {1003BCA2B3}>
;    #<BAR {1003BCA303}> #<BAR {1003BCA353}> #<BAR {1003BCA3A3}>
;    #<BAR {1003BCA3F3}> #<BAR {1003BCA443}> #<BAR {1003BCA493}>
;    #<BAR {1003BCA4E3}>)

Если вы хотите, чтобы функция всегда проверяла тип места, независимо от настроек оптимизации, вам следует использовать CHECK-TYPE вручную.

person jkiiski    schedule 08.06.2016
comment
Хороший! Обычной практикой является включение проверки типов для тестирования/отладки? и отключить его для производства? - person leetwinski; 08.06.2016
comment
И более глобально: является ли общепринятой практикой вообще использование типов в common lisp? Или я должен просто кодировать, как на любом языке, без проверки типов, например JS? Каков идеологический путь шепелявости? - person leetwinski; 08.06.2016
comment
Рекомендуется иметь объявления типов, когда это не слишком неудобно (особенно для примитивных типов). В разработке вы, вероятно, должны использовать что-то вроде (declaim (optimize (debug 3) (safety 3) (speed 1))), но в производстве это зависит от ваших потребностей. Для некоторых математических фрагментов кода вам может понадобиться (speed 3) (safety 0), в то время как для веб-сервера, где время безотказной работы важнее скорости, (safety 3) (speed 2) плюс правильная обработка ошибок могут быть лучше. - person jkiiski; 08.06.2016
comment
Спасибо! ваши примеры действительно немного наводят порядок в моей голове. Мне придется поиграть с этим некоторое время .. - person leetwinski; 08.06.2016
comment
и еще один вопрос: когда я выполняю ваш пример с make-array в repl, он работает отлично, но когда я буквально запускаю (make-array 1 :element-type 'foobar :initial-contents (list 1)), он создает массив без каких-либо жалоб. Я знаю: никто не делает этого на высшем уровне, но все же, в чем причина этого? - person leetwinski; 09.06.2016
comment
Когда вы запускаете его непосредственно в REPL, SBCL не беспокоится об оптимизации. В моем примере я поместил его внутри функции, так как она всегда должна быть правильно скомпилирована и оптимизирована (по крайней мере, на SBCL). - person jkiiski; 09.06.2016
comment
Также стоит отметить, что реализации могут совершенно свободно рассматривать (declaim (optimize safety)) как комментарий, если захотят. - person kyle; 10.06.2016

Почему непримитивные типы, объявленные в :element-type make-array, повышаются до t? Есть ли возможность проверки реального объявленного типа во время компиляции или во время выполнения?

Параметр :element-type предназначен для того, чтобы реализация могла выбрать оптимизированную структуру памяти для массива — в основном для экономии места в памяти. Это обычно полезно с примитивными типами. Для других типов большинство исполняющих сред Common Lisp не будут иметь оптимизированной реализации хранилища, и поэтому объявление не будет иметь никакого полезного эффекта.

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

Реализация может сделать это.

Закрытие CL:

? (defclass foo () ((bar :type integer :initform 0 :initarg :bar)))
#<STANDARD-CLASS FOO>
? (make-instance 'foo :bar "baz")
> Error: The value "baz", derived from the initarg :BAR,
  can not be used to set the value of the slot BAR in
  #<FOO #x302000D3EC3D>, because it is not of type INTEGER. 

То же самое для объявлений типов функций с помощью declare.. Являются ли они просто подсказками по оптимизации для компилятора?

Объявления типов с помощью declare можно игнорировать - например, в Symbolics Genera большинство объявлений будут игнорироваться. Реализации не требуются для их обработки. Большинство реализаций, по крайней мере, интерпретируют их как гарантию того, что какой-то объект будет иметь этот тип, и создадут для этого оптимизированный код — возможно, без проверок во время выполнения и/или специализированного кода для этого типа. Но обычно необходимо установить соответствующие уровни оптимизации (скорость, безопасность, отладка,...)

Кроме того, компиляторы, производные от компилятора CMUCL (SBCL, ...), могут использовать их для некоторых проверок времени компиляции.

Но ни один из эффектов не указан в стандарте ANSI CL. Стандарт предоставляет объявления и оставляет интерпретацию реализации.

person Rainer Joswig    schedule 08.06.2016

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

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

Тем не менее, объявления типов для функций могут помочь вам в случае, если вы вызываете функцию в контексте, где можно доказать, что во время выполнения типы точно не совпадут (пересечение типов пусто). Чтобы слепо доверять утверждениям типа, вы должны снизить уровни безопасности.

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

http://sellout.github.io/2012/03/03/common-lisp-type-hierarchy

person coredump    schedule 08.06.2016