Общая лисповская разница между объявленным типом проверки - PullRequest
3 голосов
/ 07 февраля 2020

Может ли кто-нибудь объяснить различия между следующими двумя случаями (в частности, что говорят комментарии, если для меня это не понятно), которые исходят от CLHS по функции :

;; This function assumes its callers have checked the types of the
;; arguments, and authorizes the compiler to build in that assumption.
(defun discriminant (a b c)
  (declare (number a b c))
  "Compute the discriminant for a quadratic equation."
  (- (* b b) (* 4 a c))) =>  DISCRIMINANT
(discriminant 1 2/3 -2) =>  76/9

;; This function assumes its callers have not checked the types of the
;; arguments, and performs explicit type checks before making any assumptions. 
(defun careful-discriminant (a b c)
  "Compute the discriminant for a quadratic equation."
  (check-type a number)
  (check-type b number)
  (check-type c number)
  (locally (declare (number a b c))
    (- (* b b) (* 4 a c)))) =>  CAREFUL-DISCRIMINANT
(careful-discriminant 1 2/3 -2) =>  76/9

Ответы [ 4 ]

5 голосов
/ 07 февраля 2020

Я пытаюсь выучить некоторые CL самостоятельно, поэтому я предоставлю лучший ответ, который смогу. Common Lisp - это язык Dynami c по сравнению с языком STATI c. Для языка * stati c проверьте Haskell - он выполняет кучу проверок времени компиляции, чтобы убедиться, что типы совпадают для всех функций, и дает вам знать, если он выходит из строя. Однако в Common Lisp вещи немного отличаются .

Однако в Common Lisp переменные не печатаются так, как в таких языках, как Java или C ++. То есть вам не нужно объявлять тип объекта, который может содержать каждая переменная. Вместо этого переменная может содержать значения любого типа, а значения содержат информацию о типе, которую можно использовать для проверки типов во время выполнения. Таким образом, Common Lisp динамически типизирован - ошибки типа обнаруживаются динамически. Например, если вы передадите в функцию + что-то отличное от числа, Common Lisp сообщит об ошибке типа. С другой стороны, Common Lisp является строго типизированным языком в том смысле, что будут обнаружены все ошибки типов - нет способа трактовать объект как экземпляр класса, которым он не является.

Поэтому переменные, которые мы объявляем как аргументы функции, по умолчанию не имеют типа. Это может быть хорошим чтением для вас: https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node15.html. В первом абзаце это выглядит следующим образом:

Важно отметить, что в Лиспе это типизированные объекты данных, а не переменные. Любая переменная может иметь любой объект Lisp в качестве значения. (Можно сделать явное объявление о том, что переменная на самом деле будет принимать только одно из ограниченного набора значений. Однако такое объявление всегда может быть опущено, и программа все равно будет работать правильно. Такое объявление просто представляет собой совет от пользователя, который может быть полезен для повышения эффективности. См. объявление.)

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

И если мы предпримем предприятие на Declare , мы увидим следующее:

Существует два различных использования объявления, один из которых объявить переменные Лиспа как «специальные» (это влияет на семантику соответствующих привязок переменных), а другой - дать совет, чтобы помочь системе Common Lisp (на самом деле компилятору) запускать ваш код на Lisp быстрее или с более сложными опциями отладки.

Наконец, если мы посмотрим на тип проверки , мы увидим:

сигналы проверки типа корректно Ошибка типа type-error, если содержимое места не относится к типу typepe c.

В обоих случаях для объявления и проверки типа мы даем системе Common Lisp рекомендации по типам и проверке типов. Давайте рассмотрим две предоставленные вами примеры функций.

Во-первых, «дискриминантная» функция использует функцию Declare для подтверждения того, что аргументы действительно являются числами и что компилятору не нужно их проверять. Функция осторожного распознавания использует check-type, чтобы убедиться, что каждая переменная действительно является числом, а затем выполняет операцию.

Вы можете спросить «Почему я должен беспокоиться об этом?», Где ответ предоставить либо более оптимизированную функцию (дискриминант), либо функцию, которая обеспечивает лучшую отладку и больше информации об ошибке (осторожно-дискриминант). Чтобы показать разницу, я запустил SBCL и определил обе функции. Затем я использовал дизассемблирование, чтобы показать машинный код каждого. Обратите внимание, что осторожный дискриминант выполняет больше проверок, чем дискриминант, что приводит к большему количеству машинного кода!

(дизассемблирует # 'дискриминант)

; disassembly for DISCRIMINANT
; Size: 83 bytes. Origin: #x10023700D7                        ; DISCRIMINANT
; 0D7:       498B5D10         MOV RBX, [R13+16]               ; thread.binding-stack-pointer
; 0DB:       48895DF8         MOV [RBP-8], RBX
; 0DF:       840425F8FF1020   TEST AL, [#x2010FFF8]           ; safepoint
; 0E6:       488B55E8         MOV RDX, [RBP-24]
; 0EA:       488B7DE8         MOV RDI, [RBP-24]
; 0EE:       FF1425C0000020   CALL QWORD PTR [#x200000C0]     ; GENERIC-*
; 0F5:       488955D8         MOV [RBP-40], RDX
; 0F9:       488B55F0         MOV RDX, [RBP-16]
; 0FD:       BF08000000       MOV EDI, 8
; 102:       FF1425C0000020   CALL QWORD PTR [#x200000C0]     ; GENERIC-*
; 109:       488B7DE0         MOV RDI, [RBP-32]
; 10D:       FF1425C0000020   CALL QWORD PTR [#x200000C0]     ; GENERIC-*
; 114:       488BFA           MOV RDI, RDX
; 117:       488B55D8         MOV RDX, [RBP-40]
; 11B:       FF1425B8000020   CALL QWORD PTR [#x200000B8]     ; GENERIC--
; 122:       488BE5           MOV RSP, RBP
; 125:       F8               CLC
; 126:       5D               POP RBP
; 127:       C3               RET
; 128:       CC10             INT3 16                         ; Invalid argument count trap
NIL

(дизассемблирует #' осторожный дискриминант)

; disassembly for CAREFUL-DISCRIMINANT
; Size: 422 bytes. Origin: #x10023701E3                       ; CAREFUL-DISCRIMINANT
; 1E3:       4D8B4510         MOV R8, [R13+16]                ; thread.binding-stack-pointer
; 1E7:       4C8945F8         MOV [RBP-8], R8
; 1EB:       840425F8FF1020   TEST AL, [#x2010FFF8]           ; safepoint
; 1F2:       EB44             JMP L1
; 1F4:       660F1F840000000000 NOP
; 1FD:       0F1F00           NOP
; 200: L0:   488B7DF0         MOV RDI, [RBP-16]
; 204:       4883EC10         SUB RSP, 16
; 208:       488B1571FFFFFF   MOV RDX, [RIP-143]              ; 'A
; 20F:       488B3572FFFFFF   MOV RSI, [RIP-142]              ; 'NUMBER
; 216:       4C894DD8         MOV [RBP-40], R9
; 21A:       488B056FFFFFFF   MOV RAX, [RIP-145]              ; #<SB-KERNEL:FDEFN SB-KERNEL:CHECK-TYPE-ERROR>
; 221:       B906000000       MOV ECX, 6
; 226:       48892C24         MOV [RSP], RBP
; 22A:       488BEC           MOV RBP, RSP
; 22D:       FF5009           CALL QWORD PTR [RAX+9]
; 230:       4C8B4DD8         MOV R9, [RBP-40]
; 234:       488955F0         MOV [RBP-16], RDX
; 238: L1:   840425F8FF1020   TEST AL, [#x2010FFF8]           ; safepoint
; 23F:       488B45F0         MOV RAX, [RBP-16]
; 243:       448D40F1         LEA R8D, [RAX-15]
; 247:       41F6C001         TEST R8B, 1
; 24B:       7512             JNE L2
; 24D:       4180F80A         CMP R8B, 10
; 251:       740C             JEQ L2
; 253:       41F6C00F         TEST R8B, 15
; 257:       75A7             JNE L0
; 259:       8078F129         CMP BYTE PTR [RAX-15], 41
; 25D:       77A1             JNBE L0
; 25F: L2:   EB47             JMP L4
; 261:       660F1F840000000000 NOP
; 26A:       660F1F440000     NOP
; 270: L3:   488B7DE8         MOV RDI, [RBP-24]
; 274:       4883EC10         SUB RSP, 16
; 278:       488B1519FFFFFF   MOV RDX, [RIP-231]              ; 'B
; 27F:       488B3502FFFFFF   MOV RSI, [RIP-254]              ; 'NUMBER
; 286:       4C894DD8         MOV [RBP-40], R9
; 28A:       488B05FFFEFFFF   MOV RAX, [RIP-257]              ; #<SB-KERNEL:FDEFN SB-KERNEL:CHECK-TYPE-ERROR>
; 291:       B906000000       MOV ECX, 6
; 296:       48892C24         MOV [RSP], RBP
; 29A:       488BEC           MOV RBP, RSP
; 29D:       FF5009           CALL QWORD PTR [RAX+9]
; 2A0:       4C8B4DD8         MOV R9, [RBP-40]
; 2A4:       488955E8         MOV [RBP-24], RDX
; 2A8: L4:   840425F8FF1020   TEST AL, [#x2010FFF8]           ; safepoint
; 2AF:       488B45E8         MOV RAX, [RBP-24]
; 2B3:       448D40F1         LEA R8D, [RAX-15]
; 2B7:       41F6C001         TEST R8B, 1
; 2BB:       7512             JNE L5
; 2BD:       4180F80A         CMP R8B, 10
; 2C1:       740C             JEQ L5
; 2C3:       41F6C00F         TEST R8B, 15
; 2C7:       75A7             JNE L3
; 2C9:       8078F129         CMP BYTE PTR [RAX-15], 41
; 2CD:       77A1             JNBE L3
; 2CF: L5:   EB3D             JMP L7
; 2D1:       660F1F840000000000 NOP
; 2DA:       660F1F440000     NOP
; 2E0: L6:   498BF9           MOV RDI, R9
; 2E3:       4883EC10         SUB RSP, 16
; 2E7:       488B15B2FEFFFF   MOV RDX, [RIP-334]              ; 'C
; 2EE:       488B3593FEFFFF   MOV RSI, [RIP-365]              ; 'NUMBER
; 2F5:       488B0594FEFFFF   MOV RAX, [RIP-364]              ; #<SB-KERNEL:FDEFN SB-KERNEL:CHECK-TYPE-ERROR>
; 2FC:       B906000000       MOV ECX, 6
; 301:       48892C24         MOV [RSP], RBP
; 305:       488BEC           MOV RBP, RSP
; 308:       FF5009           CALL QWORD PTR [RAX+9]
; 30B:       4C8BCA           MOV R9, RDX
; 30E: L7:   840425F8FF1020   TEST AL, [#x2010FFF8]           ; safepoint
; 315:       458D41F1         LEA R8D, [R9-15]
; 319:       41F6C001         TEST R8B, 1
; 31D:       7513             JNE L8
; 31F:       4180F80A         CMP R8B, 10
; 323:       740D             JEQ L8
; 325:       41F6C00F         TEST R8B, 15
; 329:       75B5             JNE L6
; 32B:       418079F129       CMP BYTE PTR [R9-15], 41
; 330:       77AE             JNBE L6
; 332: L8:   4C894DD8         MOV [RBP-40], R9
; 336:       488B55E8         MOV RDX, [RBP-24]
; 33A:       488B7DE8         MOV RDI, [RBP-24]
; 33E:       FF1425C0000020   CALL QWORD PTR [#x200000C0]     ; GENERIC-*
; 345:       488955E0         MOV [RBP-32], RDX
; 349:       4C8B4DD8         MOV R9, [RBP-40]
; 34D:       488B55F0         MOV RDX, [RBP-16]
; 351:       BF08000000       MOV EDI, 8
; 356:       FF1425C0000020   CALL QWORD PTR [#x200000C0]     ; GENERIC-*
; 35D:       4C8B4DD8         MOV R9, [RBP-40]
; 361:       498BF9           MOV RDI, R9
; 364:       FF1425C0000020   CALL QWORD PTR [#x200000C0]     ; GENERIC-*
; 36B:       488BFA           MOV RDI, RDX
; 36E:       4C8B4DD8         MOV R9, [RBP-40]
; 372:       488B55E0         MOV RDX, [RBP-32]
; 376:       FF1425B8000020   CALL QWORD PTR [#x200000B8]     ; GENERIC--
; 37D:       4C8B4DD8         MOV R9, [RBP-40]
; 381:       488BE5           MOV RSP, RBP
; 384:       F8               CLC
; 385:       5D               POP RBP
; 386:       C3               RET
; 387:       CC10             INT3 16                         ; Invalid argument count trap
NIL

Как видно здесь, Common Lisp также может быть скомпилирован, что смущает некоторых людей. Лучше ответ здесь: Как Lisp dynamici c и скомпилирован? .

4 голосов
/ 07 февраля 2020

Объявление влияет на то, что происходит во время компиляции . Форма check-type - это время выполнения охранник.

Итак, форма объявления гласит: «Эй, компилятор, значения, содержащиеся в параметрах a, b, c, могут быть только числами ». Форма check-type гласит: «Эй, функция, в этот момент выполнения проверьте, что заданные значения относятся к указанному типу».

4 голосов
/ 07 февраля 2020

Разница между макросами check-type и объявлениями типа заключается в том, что первое не может быть проигнорировано компилятором (и, если проверка не пройдена, можно интерактивно исправить входные данные ), в то время как последние являются просто подсказками компилятору (и, что гораздо важнее, читателям кода), которые могут игнорироваться компилятором.

2 голосов
/ 13 февраля 2020

CHECK-TYPE: проверка и исправление типа во время выполнения

check-type выполняет проверку фактического времени выполнения. Обычно он также предоставляет способ интерактивного исправления значения.

* (let ((a "1"))
    (check-type a number)
    (+ a 2))

debugger invoked on a SIMPLE-TYPE-ERROR in thread
#<THREAD "main thread" RUNNING {10005184C3}>:
  The value of A is "1", which is not of type NUMBER.

Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [STORE-VALUE] Supply a new value for A.
  1: [ABORT      ] Exit debugger, returning to top level.

(SB-KERNEL:CHECK-TYPE-ERROR A "1" NUMBER NIL)
0] 0

Enter a form to be evaluated: 1
3

ОБЪЯВЛЕНИЕ: Объявление типа

Common Lisp динамически типизируется: каждый объект данных имеет тип.

Common Lisp дополнительно позволяет использовать типы * stati c для переменных и функций. Существует

  • различных типов и составных типов
  • способов определения новых типов с помощью deftype
  • объявлений типов с declare
  • подтипом проверяет с помощью subtypep
  • типа выполнения проверяет с помощью typep
  • условно типа выполнения с typecase, ctypecase и etypecase

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

Основное использование объявлений типов stati c с (declare (type ...)) в компиляторах Common Lisp:

  • игнорируя их. Обычно интерпретаторы Lisp и некоторые компиляторы полностью их игнорируют

  • используют их для оптимизации скорости и пространства. Это делается многими компиляторами. Они могут использовать эти объявления типов для создания специализированного кода.

  • использовать их для проверки типов во время выполнения. Некоторые реализации используют объявления типов для проверок во время выполнения.

  • используют их для проверок типов во время компиляции. Некоторые реализации используют объявления типов для проверки типов во время компиляции. Примерами являются sbcl и cmucl.

Обратите внимание, что стандарт Common Lisp не говорит о том, как используются эти объявления типов. Он просто предоставляет синтаксис для определения и объявления типов. Обычные реализации Lisp затем либо используют их, либо игнорируют их.

Особо изощренное использование объявлений типов можно найти в SBCL и CMUCL.

Пример для проверок типов

Давайте посмотрим, как SBCL использует объявления типов для проверок типов во время выполнения и во время компиляции:

Проверка типов во время выполнения с SBCL:

* (defun add (a b)
    (declare (type number a b))
    (list a b))
ADD
* (add 1 "3")

debugger invoked on a TYPE-ERROR in thread
#<THREAD "main thread" RUNNING {10005184C3}>:
  The value
    "3"
  is not of type
    NUMBER
  when binding B

Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(ADD 1 "3") [external]
   source: (SB-INT:NAMED-LAMBDA ADD
               (A B)
             (DECLARE (TYPE NUMBER A B))
             (BLOCK ADD (LIST A B)))
0] 

Как мы видим, SBCL использует объявление типа для проверки во время выполнения. Но в отличие от check-type он не предлагает предоставлять другое значение и соответствующую повторную проверку ...

Проверка типа во время компиляции с SBCL:

* (defun subtract (a b)
    (declare (type number a b))
    (concatenate 'string a "-" b " is " (- a b)))

; in: DEFUN SUBTRACT
;     (CONCATENATE 'STRING A "-" B " is " (- A B))
; 
; caught WARNING:
;   Derived type of (SB-KERNEL:SYMEVAL 'A) is
;     (VALUES NUMBER &OPTIONAL),
;   conflicting with its asserted type
;     SEQUENCE.
;   See also:
;     The SBCL Manual, Node "Handling of Types"
; 
; compilation unit finished
;   caught 1 WARNING condition
SUBTRACT

Как вы можете видите, мы пытаемся использовать число в качестве последовательности. SBCL обнаруживает это во время компиляции и предупреждает.

...