LINUX.ORG.RU

это баг в SBCL или я что-то не понял?

 


0

3
(declaim (optimize (speed 3) (safety 0) (compilation-speed 0) (space 0) (debug 0)))

(declaim (ftype (function (fixnum ) fixnum) FOO ))

(defun FOO  (X  )
 (declare (type FIXNUM  X ))
  (+ X 1)
  )

(declaim (ftype (function (NUMBER ) NUMBER) BAR ))

(defun BAR (X)
  (declare (type NUMBER X))
  (FOO X)
  )

Этот код компилируется без предупреждений. Если заменить number на string, то будет warning. Чтение мануала не помогло. Другие значения safety (до 3) не помогли.

★★★★★

Последнее исправление: den73 (всего исправлений: 2)

Этот код компилируется без предупреждений

И какое по-твоему тут должно быть предупреждение на этапе компиляции?

no-such-file ★★★★★
()

Лисп прощает почти все на этапе «компиляции» т.к. этой самой компиляции-то и нет.

А что именно твой код делает, моих знаний не хватает понять :-/

timdorohin ★★★★
()
Ответ на: комментарий от no-such-file

Можно считать, что fixnum ≈ short, а bignum ≈ long. Такой код неправильный. Т.е., fixnum - это подтип number, но не любой number - это fixnum.

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 1)

Этот код компилируется без предупреждений. Если заменить number на string, то будет warning.

Так и должно быть. Я же поэтому и говорил, что declare type не статическая типизация.

Warning появляется в случае, если типы противоречат. А так противоречия нет, существуют такие number, которые являются fixnum. Если вместо number написать real, будет warning.

При этом ошибка неустранима, потому что если при тех же типах тело bar выглядит

(defun BAR (X)
  (declare (type NUMBER X))
  (if (fixnump x) (FOO X) 0)
  )
то ошибки уже нет, а типы те же самые.

В отличие от Typed Racket в SBCL нет подтипирования в ветках кода.

Например,

* (defun baz (x) (foo x))
* (describe #'baz)
...
Derived type: (FUNCTION (T) (VALUES FIXNUM &REST T))

monk ★★★★★
()
Ответ на: комментарий от monk

И ещё одна причина, почему нельзя писать предупреждение на сужение типа: если нет ftype, то тип будет t, на все входящие аргументы. Получить по предупреждению на каждый вызов функции только за отсутствие декларации в динамическом языке было бы неожиданно.

monk ★★★★★
()

Ну и где в стандарте написано, что компилятор должен ругаться? :-) И ты, наверное, забыл, что в Лиспе нет типов у переменных, а есть типы у значений :-) И ты не объявляешь тип переменной своими declare, а декларируешь тип значений :-) В данном случае какой-нибудь твой number сузится до fixnum по-разному на разных архитектурах и делов то :-) Нет в Лиспе статической типизации, не ищи :-)

anonymous
()
Ответ на: комментарий от monk

Warning появляется в случае, если типы противоречат. А так противоречия нет, существуют такие number, которые являются fixnum.

Ладно, допустим это - осознанный выбор создателей SBCL. Но типизация-то всё равно статическая.

Если я задаю непересекающиеся диапазоны целых в foo и bar, например, (integer 3 17) и (integer 100 200), то я получаю ошибку компиляции, а если они пересекаются, то не получаю. А если путаю float и number (к-рый фактически оказывается целым), то при safety 0 получаю unhandled exception.

Т.е. выбор странный (хотя его можно понять), а типизация статическая. Чем не статическая? Просто такая вот идея про то, что пересекающиеся диапазоны считаем совместимыми при передаче.

На самом то деле, можно рассмотреть конкатенацию строк. Строго говоря, сложение строк длины не более 10 - это строка длины не более 20. Хотя на самом-то деле мы можем надеяться, что эта строка будет длины не более 15, по каким-то причинам, и есть языки, которые позволяют так складывать (язык хранимых процедур Firebird).

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от monk

если нет ftype, то тип будет t, на все входящие аргументы

Если нет ftype в foo, то это не сужение, а расширение. А вот возвращаемое значение - там да. Я думаю, получить по предупреждению - лучше, чем такое вот решето. Задал вопрос в SBCL-devel. Я не думаю, что это нормально.

den73 ★★★★★
() автор топика
Ответ на: комментарий от timdorohin

т.к. этой самой компиляции-то и нет.

Тогда это:

; disassembly for BAR
; Size: 18 bytes. Origin: #x100209160A
; 0A:       488BD1           MOV RDX, RCX                     ; no-arg-parsing entry point
; 0D:       B902000000       MOV ECX, 2
; 12:       FF7508           PUSH QWORD PTR [RBP+8]
; 15:       B878045320       MOV EAX, #x20530478              ; #<FDEFN FOO>
; 1A:       FFE0             JMP RAX

... результат какого процесса?

ados ★★★★★
()
Ответ на: комментарий от ados

jit-а, не?

Или это не sbcl?

Если не он, тогда я не могу сказать что за костыли с компиляцией они всунули в изначально интерпретируемый язык.

В случае sbcl код генерируется на лету, и очень сильно зависит от того, что скармливать (или скармливалось) функции. Т.е. «компиляция» происходит когда ты вызываешь функцию, и полученный код зависит и от аргументов функции.

И да, различие между компиляцией и интерпретацией с jit - то, что ты можешь выполнить eval с не известным ранее кодом, и это сработает. Именно по-этому java - не интерпретатор, а компилятор, пусть и использует байт-код.

timdorohin ★★★★
()
Последнее исправление: timdorohin (всего исправлений: 1)
Ответ на: комментарий от timdorohin

Если не он, тогда я не могу сказать что за костыли с компиляцией они всунули в изначально интерпретируемый язык.

Ты глуп.

В случае sbcl код генерируется на лету, и очень сильно зависит от того, что скармливать (или скармливалось) функции. Т.е. «компиляция» происходит когда ты вызываешь функцию, и полученный код зависит и от аргументов функции.

И глупость твоя безгранична.

anonymous
()
Ответ на: комментарий от timdorohin

В случае sbcl код генерируется на лету, и очень сильно зависит от того, что скармливать (или скармливалось) функции. Т.е. «компиляция» происходит когда ты вызываешь функцию, и полученный код зависит и от аргументов функции.

Нет. Вызовов FOO ровно нуль, и тем не менее:

sectoid@dagon:~$ echo "(defun foo (x) (+ x 1 2 3)) (disassemble #'foo)" | sbcl
This is SBCL 1.3.14.debian, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.
 
SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.
*
FOO
*
; disassembly for FOO
; Size: 38 bytes. Origin: #x1001E7A0F4
; 0F4:       498B4C2460       MOV RCX, [R12+96]               ; thread.binding-stack-pointer
                                                              ; no-arg-parsing entry point
; 0F9:       48894DF8         MOV [RBP-8], RCX
; 0FD:       BF0C000000       MOV EDI, 12
; 102:       488BD3           MOV RDX, RBX
; 105:       41BBA0020020     MOV R11D, 536871584             ; GENERIC-+
; 10B:       41FFD3           CALL R11
; 10E:       488B5DF0         MOV RBX, [RBP-16]
; 112:       488BE5           MOV RSP, RBP
; 115:       F8               CLC
; 116:       5D               POP RBP
; 117:       C3               RET
; 118:       CC10             BREAK 16                        ; Invalid argument count trap
NIL

Sectoid ★★★★★
()
Ответ на: комментарий от den73

Если нет ftype в foo, то это не сужение, а расширение.

Я имею в виду, если нет ftype в bar. А если нет ftype в foo, то тоже должно бы было ругаться, так как параметр типа t передаётся в #'+, который принимает number.

Я думаю, получить по предупреждению - лучше, чем такое вот решето.

Будет огромное количество предупреждений на любой код без ftype, использующий стандартную библиотеку. Это очень плохо.

monk ★★★★★
()
Ответ на: комментарий от monk

что declare type не статическая типизация.

А почему не статическая? Статическая ведь означает, что проверка типов идет во время компиляции. А вообще, если я верно понял, все правильно сделали. если разрешается тип, значит должны быть разрешены все его подтипы, если соблюдается Лисков, никаких проблем тут возникать не должно. Я вчера касался этого вопроса в одной теме, но ее снесли:)

numbershnumber
()
Ответ на: комментарий от timdorohin

Если не он, тогда я не могу сказать что за костыли с компиляцией они всунули в изначально интерпретируемый язык.

Зато ты можешь резво рассуждать о том, как было изначально :-)

И да, различие между компиляцией и интерпретацией с jit - то, что ты можешь выполнить eval с не известным ранее кодом

Тут в каждом слове «Лол» :-) Лол :-)

anonymous
()
Ответ на: комментарий от timdorohin

И да, различие между компиляцией и интерпретацией с jit - то, что ты можешь выполнить eval с не известным ранее кодом, и это сработает. Именно по-этому java - не интерпретатор, а компилятор, пусть и использует байт-код.

И CL и жаба — это компилируемые языки, а точней, транслируемые. Полноценно-интерпретируемым языком можно назвать только такой, который имеет свою собственную машину(интерпретатор).

numbershnumber
()
Ответ на: комментарий от timdorohin

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

numbershnumber
()
Ответ на: комментарий от numbershnumber

А почему не статическая? Статическая ведь означает, что проверка типов идет во время компиляции.

Не во время, а ещё до компиляции :-) Статическая типизация предполагает то, что типы всех сущностей лексического окружения задаются в момент их объявления :-) В Лиспе в общем случае нет статической типизации, потому что декларации типов не являются обязательными :-) Однако, в отдельных частных случаях, когда все возможные _значения_ переменных с объявленным типом реально известны компилятору, работает частный случай статической типизации, например:

(loop for i fixnum from 1 to (1+ most-positive-fixnum))
Такие дела :-)

anonymous
()
Ответ на: комментарий от lovesan

*посыпает голову пеплом* Ладно, не jit а aot, от этого он не перестает быть интерпретатором.

И нет, не одна из этих реализаций не компилирует программу. Тупо потому что сие действо невозможно для языка, который имеет полноценный eval (если конечно ты не считаешь что когда sb-ext:save-lisp-and-die и аналогичные объединяют весь компилятор вместе с программой - получается «скомпилированное» приложение)

Это связано со свойствами языка, нет возможности скомпилировать helloworld в бинарник не притаскивая сам компилятор-интерпретатор в рантайм. (есть реализации которые пытаются обойти это, до первого попавшегося eval'а)

timdorohin ★★★★
()
Ответ на: комментарий от numbershnumber

Jit тебе компилирует и оптимизирует функции, но не собирает бинарник программы для использования. И работает только с рантаймом в виде самого jit-интерпретатора.

timdorohin ★★★★
()
Ответ на: комментарий от timdorohin

Тупо потому что сие действо невозможно для языка, который имеет полноценный eval

Всё имеет полноценный eval :-) Даже цепепе и C :-) Только в программе на цепепе нужно запускать gcc/g++ в рантайме и с помощью dlopen/dlclose манипулировать скомпилированным в рантайме объектником, что очень неудобно :-) А в Лиспе достаточно просто прочитать внешний код и он уже скомпилен и готов :-)

нет возможности скомпилировать helloworld в бинарник не притаскивая сам компилятор-интерпретатор в рантайм

В цепепе, например, нет возможности скомпилировать hello world, не притаскивая сам рантайм в виде стандартной библиотеке :-) Теперь цепепе - интерпретируемый язык? :-)

anonymous
()
Ответ на: комментарий от anonymous

Только в программе на цепепе нужно запускать gcc/g++ в рантайме

А если в системе он не стоит и программу собирали на другой? Тогда что, весь gcc к себе линковать надо?

В цепепе, например, нет возможности скомпилировать hello world, не притаскивая сам рантайм в виде стандартной библиотеке :-) Теперь цепепе - интерпретируемый язык? :-)

В рантайме c++ находится весь полнофункциональный компилятор?

timdorohin ★★★★
()
Ответ на: комментарий от numbershnumber

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

Нет, это не другой вопрос :-) Ещё раз - статическая типизация предполагает обязательное объявление типов всех переменных окружения :-) Какой-то один declare type где-то в каком-то defun - это не статическая типизация, а одна декларация где-то в каком-то определении функции :-)

anonymous
()
Ответ на: комментарий от timdorohin

А если в системе он не стоит и программу собирали на другой? Тогда что, весь gcc к себе линковать надо?

А если он не стоит, то eval на той системе работать не будет :-) Пользователь вынужден будет поставить g++/gcc :-) Так же, как он вынужден ставить какой-нибудь Qt, чтобы узреть GUI :-)

В рантайме c++ находится весь полнофункциональный компилятор?

Т.е. рантайм цепепе включал в себя сам компилятор, то цепепе был бы интерпретатором? :-) Лол :-)

anonymous
()
Ответ на: комментарий от anonymous

Т.е. рантайм цепепе включал в себя сам компилятор, то цепепе был бы интерпретатором? :-) Лол :-)

Если бы c++ согласно стандарту имел бы в рантайме весь компилятор и позволял бы себе компилить функции по-требованию он был бы интерпретируемым языком. Смех-смехом, а по факту было бы так. А g++ выступал бы интерпретатором, лол.

timdorohin ★★★★
()
Последнее исправление: timdorohin (всего исправлений: 1)
Ответ на: комментарий от timdorohin

Если бы рантайм c++ согласно стандарту имел бы в рантайме весь компилятор и позволял бы себе компилить функции по-требованию он был бы интерпретируемым языком.

Теперь осталось только понять почему :-) Почему если g++ можно запускать только из шелла не делает из цепепе «интерпретируемого языка», а если g++ можно запускать как функцию стандартной библиотеке цепепе уже сразу делает из цепепе «интерпретируемый язык»? :-) Что-то не видно логики :-)

anonymous
()
Ответ на: комментарий от anonymous

Теперь осталось только понять почему

Да это туфта, не станет он от этого интерпретируемым. Тут надо противопоставлять не «компиляции» на самом деле, а трансляции. Язык либо интерпретируется либо транслируется, а эта «компиляция» — это просто левый термин который приклеялся не в кассу и прижился

numbershnumber
()
Ответ на: комментарий от anonymous

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

numbershnumber
()
Ответ на: комментарий от numbershnumber

Язык либо интерпретируется либо транслируется

Т.е. либо выполняется с помощью транслированных в машинные коды инструкций интерпретатора, либо выполняется как самостоятельный машинный код :-) И не важно, на лету был сгенерин этот машинный код, в райтайме, или запуском g++ из шелла :-)

anonymous
()
Ответ на: комментарий от anonymous

Основная разница в том, имеет ли язык собственную машину, или является набором аббревиатур(макроподстановок) для другого языка

numbershnumber
()
Ответ на: комментарий от numbershnumber

Основная разница в том, имеет ли язык собственную машину, или является набором аббревиатур(макроподстановок) для другого языка

Язык может имеет и собственную машину (для IO, например), и транслятор в машинный код в рантайме, и интерпретатор :-) Например, тот же SBCL :-)

anonymous
()
Ответ на: комментарий от timdorohin

jit-интерпретатором

Чувак, нет такого понятия, уймись. Есть jit-компиляторы - название говорит само за себя.

no-such-file ★★★★★
()
Ответ на: комментарий от timdorohin

короче ты утомил. это все разные понятия. Если язык требует сборки — он собираемый, если он транслируется — значит транслируемый, если интерпретируется — значит интерпретируемый. Так понятней?

numbershnumber
()
Ответ на: комментарий от numbershnumber

Учитфывая что x86 - является интерпритируемой на «внутренней» risc-машине, то я уже сдаюсь.

Или тут уместнее говорить «транслируемой»?

timdorohin ★★★★
()
Ответ на: комментарий от timdorohin

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

numbershnumber
()
Ответ на: комментарий от monk

Будет огромное количество предупреждений на любой код без ftype, использующий стандартную библиотеку. Это очень плохо

Ты прав, а в последней фразе - дважды прав. Ладно, будем с этим жить. По идее нужен режим, когда такой «cast» не проходит без последствий.

С другой стороны, я смотрел на это, беря за образец поведения Си, а ведь там тоже всё плохо. Можно прибавить единицу к 2147483647 и получится -2147483648, тоже без всяких предупреждений.

Интересно, есть ли язык, где эта проблема решена?

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

И кстати, это поведение задокументировано в руководстве CMU CL:

If the compiler can prove at compile time that some portion of the program cannot be executed without a type error, then it will give a warning at compile time.

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

(defun raz (foo)
  (let ((x (case foo
             (:this 13)
             (:that 9)
             (:the-other 42))))
    (declare (fixnum x))
    (foo x)))
Ведь при некоторых значения foo данная функция выполнится без проблем. Мы можем вынести case в отдельную функцию, возвращающую (or fixnum null) и принять его результат в функцию, принимающую просто fixnum. Выполнение программы без ошибок возможно, поводов для предупреждения нет.

Т.е. я пока остаюсь с выводом, что система типов - это некий костыль кастомной формы, просто в разных языках эта форма разная. «Дети, это понять нельзя, это надо запомнить».

Однако я остаюсь при своём мнении о том, что declare type - это статическая типизация.

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

Ну кстати я проделал такой эксперимент:

(defun dva (foo)
  (let ((x (tri foo)))
    (declare (fixnum x))
    (chetyre x)))

(defun tri (foo)
  (case foo
    (:this 13)
    (:that 9)
    (:the-other 42)))

(defun chetyre (x)
  (+ x 1))
Предупреждения нет. А если мой исходный пример запихать в одну функцию?
(defun BAR2 (X)
  (declare (type NUMBER X))
  (flet ((FOO (Y)
           (declare (type FIXNUM Y))
           (+ Y 1)))
    (FOO X)))
Предупреждения нет.

den73 ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.