LINUX.ORG.RU

Не баг, а фича

 


0

2

Какие вещи в программирования изначально появились как эдакая недоработка, но потом приобрела `статус фичи`?

Вот как мне кажется (на достоверность не претендую) например лисп - ребятам просто лень было доделывать. Динамическая типизация - разумеется товарищи типа Гвидо и Мацумото знают о преимуществах статической - но её просто сложно сделать, `и так пойдёт`.

Что ещё есть?

Перемещено JB из talks

★★★★★

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

Ответ на: комментарий от fmdw

В нормальнух языках будут, например, ф-ции write_list, write_bytestring и т.д., отображающие аргумент в сишную строку

Там есть io:format. Суть в том, что если я знаю тип результата в данном частном случае, то зачем мне делать код для всех вариантов

ибо tcp про ваши lists и binaries ничего не знает.

В Erlang знает.

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

Приходится на эрланге писать? Понимаю, соболезную

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

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

Суть в том, что если я знаю тип результата в данном частном случае

В примере на пять строк — да, знаешь. В проекте на 15 здоровых взаимодействующих подсистем — вряд ли.

В Erlang знает.

Это его личные фрейдистские фантазии. TCP знает только про поток байтиков.

на котором написаны очень надёжные приложения.

Знаем, видели.

fmdw
()

Динамическая типизация - разумеется товарищи типа Гвидо и Мацумото знают о преимуществах статической - но её просто сложно сделать

Сделать её несложно. Её сложно сделать так, чтобы ей было приятно пользоваться, например сохраняя утиность. В го попытались, но так и не смогли впихнуть злосчастные генерики.

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

Для этого придуманы юнит-тесты.

Да, приходится их писать.

И статическая типизация не позволяет от них отказаться.

Но внушительно сократить их количество.

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

Приходится делать уродливые Success2, Zero2, ... это я и называю «камасутрой»

Есть полиморфные типы.

Debasher ★★★★★
() автор топика

отсутствие генериков в Go позволило упростить компелятор, ускорить компиляцию, избежать автоматической генерации большого объёма кода и выиграть килобайт в размере бинарника
ах да, ещё упростить сам язык и снизить порог вхождения

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

Или просто выбрать язык с более выразительной системой типов, если задача того требует. Например haskell. ☺

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

Меня пока от окамля штырит, оставлю хаскелль на потом

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

нельзя написать

Можно написать, для этого есть try!(): http://is.gd/fyJQqc .

заставляет обрабатывать ошибки даже там, где их уже быть не может

Если ошибок быть не может, у тебя и возвращаемый тип будет не Result<..>, а сразу значение. Если возвращаемый тип Result<..>, значит ошибки могут быть и ты обязан их отработать.

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

Не уловил это из исходного кода, вот:

case packet of 
    (StringPacket s) -> write(s)
    _ -> ()
И опять же, в твоем коде критическая связь межу «вот тут передали

» и «вот тут Packet будет строка» выражается только в виде комментария и никак не проверяется статический.

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

К слову, как ты собрался для своего calc писать calc(3) + calc(4), если он у тебя может вернуть :overflow и :zero? В лишпе определены операции :overflow + 10 или 20 + :zero?

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

С нетерпением жду, когда в Go 2.0 добавят дженерики, и го-фанбои начнут рассуждать про очевидную необходимость последних в любом нормальном ЯП.

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

как ты собрался для своего calc писать calc(3) + calc(4), если он у тебя может вернуть :overflow и :zero

zero он может вернуть только для x = 0, а overflow для достаточно большого x. calc(3) и calc(4) заведомо возвращают число

Если быть последовательным, то и операцию деления надо всегда заворачивать в match/try+catch. А также любую операцию чтения.

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

zero он может вернуть только для x = 0, а overflow для достаточно большого x. calc(3) и calc(4) заведомо возвращают число

В общем случае у тебя будут calc(x) и calc(y), и тебе придется вручную проверять(или забыть проверять, что более вероятно), что ни тот ни другой не вернул :zero или :overflow. Но даже в текущем варианте, твои рассуждения основываются на знании подробностей реализации вызываемой функции, которые 1) не всегда доступны 2) требуют времени для понимания(не всегда правильного) 3) могут изменяться независимо от твоего кода. Если твой коллега завтра решил, что 4 - это слишком много, или что для нечетных значений нужно возвращать новую ошибку :odd, и поменял calc, ты со своим calc(3) + calc(4) никак об этом не узнаешь, пока не запустишь код. Такой подход это просто следующий уровень стрельбы себе в ногу.

операцию деления надо всегда заворачивать в match/try+catch

Надо бы, но не прижилось.

А также любую операцию чтения.

Хм, ты не проверяешь ошибки операций ввода-вывода?

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

А вобще с грамотными языками/компиляторами и грамотным стилем написания код который странно идентирован просто не тайпчекнится.

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

Хм, ты не проверяешь ошибки операций ввода-вывода?

OCaml не заставляет их проверять и не даёт кода ошибки

http://caml.inria.fr/pub/docs/manual-ocaml/libref/Pervasives.html#VALinput_line

А вывод output_string даже непонятно какое исключение даст, если место на диске кончится или поток закроется.

Поэтому толку-то от его статической типизации? Только ложное чувство безопасности.

В общем случае у тебя будут calc(x) и calc(y), и тебе придется вручную проверять(или забыть проверять, что более вероятно), что ни тот ни другой не вернул :zero или :overflow

В общем случае они и будут проверяться. Как минимум на numberp.

Но даже в текущем варианте, твои рассуждения основываются на знании подробностей реализации вызываемой функции

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

Такой подход это просто следующий уровень стрельбы себе в ногу

Юнит-тесты и документация спасают.

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

почему скобочки/буковки влияющие на выполнение это норм, а пробелы это плохо.

Потому что скобочки/боковки видно, а пробелы и табы - нет.

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

OCaml не заставляет их проверять и не даёт кода ошибки
Поэтому толку-то от его статической типизации? Только ложное чувство безопасности.

Как ты предлагаешь во время компиляции ловить рантаймовые ошибки? А заставить проверять наличие ошибок можно через аналог Either. Или через фантомные типы. Или даже через зависимые типы. Или ещё кучей других способов.

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

И опять же, в твоем коде критическая связь межу «вот тут передали
» и «вот тут Packet будет строка» выражается только в виде > комментария и никак не проверяется статический.

То, о чем говорит monk, это зависимые типы. Ты можешь либо извратиться на менее выразительных системах, делая аналогичные вещи, либо извратиться на собственно зависимых типах - камасутра в обоих случаях. Динамика же позволяет работать с зависимыми типами напрямую без камасутры.

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

В общем случае у тебя будут calc(x) и calc(y), и тебе придется вручную проверять(или забыть проверять, что более вероятно), что ни тот ни другой не вернул :zero или :overflow.

Достаточно проверить один единственный раз, сверху по коду. С типами, да, проверять надо _каждый_ раз. Об этом тебе и говорят.

Если твой коллега завтра решил, что 4 - это слишком много, или что для нечетных значений нужно возвращать новую ошибку :odd, и поменял calc, ты со своим calc(3) + calc(4) никак об этом не узнаешь, пока не запустишь код. Такой подход это просто следующий уровень стрельбы себе в ногу.

Ты тоже не узнаешь, представь себе. Штука вдеь в том, что подавляющее большинство апологетов статики статикой пользоваться не умеют. Сперва срут в бложиках или на лоре про ГАРАНТИИ, а потом идут себе в уютный проектик и пишут какой-нибудь вырвиглаз вроде

type Name = String
смешно

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

Пробелы очень даже видно, по крайней мере не хуже чем скобочки. За исключением trailing пробелов, но они мало где влияют.

Естественно если смешивать идентацию пробелами с идентацией табами, и прочеми whitespace-like символами, то ни к чему хорошему это не приведет.

И вобще какой-нибудь умник может написать

foo
с кирилическими «o», но это ведь не повод избегать буквы 'о' в именах.

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

Я согласен, что такая чувствительность к пробелам как в ниме (опционально) где «2 + 2 * 3 /= 2+2 * 3» это идиотизм. Но не вижу ничего плохого в выделении блоков одной лишь идентацией.

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

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

Но не вижу ничего плохого в выделении блоков одной лишь идентацией.

Мы же про питон говорим сейчас, да?

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

Да легко. Вложенные do-блоки с одной монадой. Особенно если это какое-нибудь IO. Но вероятность не заметить такое действительно мала, и я о проблемах в пистоне говорил.

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

Да легко. Вложенные do-блоки с одной монадой.

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

myFunc = do
    foo
    when bar $ do
        baz
        qux

vs

myFunc = do
    foo
    when bar $ do
    baz
    qux

Конечно можно разидентировать последнюю строчку блока (qux), но мы наверное говорим про «забыл идентировать блок», а не про «я хожу по коду и меняю строчек». Да и что-бы забыть это нужно писать код с закрытыми глазами.

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

Очень многие пишут первое выражение на одной строке с do. Плюс, если у тебя пропали вообще все сдвиги, в случае с выделением блоков сдвигами можно попасть в неприятную ситуацию.

Опять же, я повторюсь, эта проблема скорее относится к пистону чем к хаскеллу, ибо у последнего есть тайпчекер. Я в довольно большом числе проектов на питоне встречал требование выделять границы блоков комментариями.

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

Как ты предлагаешь во время компиляции ловить рантаймовые ошибки?

Так ведь писали:

Не обобщай С на «языки со статической типизацией»: http://is.gd/v9WUrQ . При таком варианте возможные ошибки становятся частью сигнатуры метода, и вызывающий код вынужден так или иначе обработать возможность ошибок.

Вот только нигде так не сделано ни для ввода-вывода, ни для арифметики.

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

Или через фантомные типы. Или даже через зависимые типы.

А вот в языках, где есть такое, меня всегда интересовало, как определить тип Int32, например. Потому как весь внешний интерфейс (поля файлов, сетевых протоколов) привязан к битности. А в языке у меня только Integer и Natural. Что в задаче вычисления IP адреса не намного лучше, чем лисповый t.

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

Ещё про типы.

Есть функция

map result-type function &rest sequences+ => result

(map 'string #'(lambda (x y)
                  (char "01234567890ABCDEF" (mod (+ x y) 16)))
       '(1 2 3 4)
       '(10 9 8 7)) =>  "AAAA"

Какой у ней тип? Или такие функции писать нельзя (то есть, статическая типизация принципиально ограничивает гибкость)?

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

Я в довольно большом числе проектов на питоне встречал требование выделять границы блоков комментариями.

Жесть какая

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

Как минимум на numberp.

Ну и к чему тогда была ремарка про «мне нельзя написать calc(3) + calc(4)»? В нормальном коде ты явно будешь делать numberp (теряя при этом собственно ошибку), и бойлерплейта получится еще больше, чем с try!.

Такого рода подробности описаны в документации.

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

Юнит-тесты и документация спасают.

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

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

То, о чем говорит monk, это зависимые типы.

Ты что-то придумываешь, он ни слова не сказал про зависимые типы, мы тут про лишп и эрланг говорим.

Динамика же позволяет работать с зависимыми типами напрямую без камасутры.

Скорее, расскажи об этом ребятам из Agda и Idris, а то они мучаются со своей камасутрой, в то время как лоровский аноним открыл способ сделать зависимые типы через динамику. А еще лучше открой TaPL и прочитай на первой странице, про то, что в динамических языках типов в математическом понимании нет - есть прилепляемые к значениям в рантайме теги.

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

Достаточно проверить один единственный раз, сверху по коду. С типами, да, проверять надо _каждый_ раз. Об этом тебе и говорят.

monk уже сознался, что в нормальном коде ему нужно явно проверять значения на numberp перед использованием. С типами ты просто делаешь pattern matching и получаешь значение, которое уже никогда не надо проверять. Сравни

fn calc2(x : isize, y : isize) -> Result<isize, CalcError> {
    Ok(try!(calc(x)) + try!(calc(y)))
}
c
(defun calc2(x y)
    (let ((a (calc x))
            (b (calc y)))
        (if (numberp a)
            (if (numberp b)
                (+ a b)
                b)
            a)))

Ты тоже не узнаешь, представь себе.

Если коллега решит изменить максимальное значение, для меня ничего не изменится, т.к. я уже буду отрабатывать ошибку Overflow, даже в коде calc(3) + calc(4). Если коллега решит добавить новый тип ошибки в CalcError, компилятор выдаст ошибку в моем коде, т.к. не все варианты enum-а будут отрабатываться.

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

к чему тогда была ремарка про «мне нельзя написать calc(3) + calc(4)»

К тому, что здесь заведомо нет ошибки. А статический язык её заставит обрабатывать. Всё равно, что в Java явно писать throws DivisionByZero на любую операцию деления.

бойлерплейта получится еще больше, чем с try

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

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

Стараюсь. Если ошибка является возможным результатом, то обязательно.

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

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

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

Звёздочка как указатель, как разыменование указателя, а ещё доступ к полям структур через точку и стрелочку. Необязательно виртуальные деструкторы.

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

Какой у ней тип?

Ты еще не открыл для себя параметрический полиморфизм? https://hackage.haskell.org/package/base-4.8.0.0/docs/Prelude.html#v:map

map :: (a -> b) -> [a] -> [b]
Или для твоего случая https://hackage.haskell.org/package/base-4.8.0.0/docs/Prelude.html#v:zipWith
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] 

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

Я думаю, он про синтаксис вызова функции (func arg1 arg2 arg3) вместо func[arg1; arg2; arg3].

Нет.

А, ну тогда Вы ошибаетесь. Динамическая типизация в LISP появилась не как бага, которую было лень исправлять, а совершенно сознательно. Примером баги, ставшей фичей, может послужить как раз синтаксис вызова функций.

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

К тому, что здесь заведомо нет ошибки.

Но мы же уже определились, что это 1) в реальном коде ты все равно будешь обрабатывать ошибки 2) фраза «здесь заведомо нет ошибок» наивна до крайности и полагается на знания, которых в общем случае у тебя нет.

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

try! в Rust это не try в динамический языках - он не бросает исключений и не разворачивает стек, он просто досрочно выходит из функции, прокидывая ошибку из вызываемой функции на уровень выше. Ты все так же должен где-то проверить на ошибку, т.к. ты все так же возвращаешь Result<Value, Error> а не Value. Чтобы избавиться от Result<..> нужно обработать ошибку.

Стараюсь. Если ошибка является возможным результатом, то обязательно.

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

Если же она внутренняя для проекта, то найти все места её вызова — не проблема.

Ясно-понятно.

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

Смешно как хейтеры всегда придираются к каким-то мелочам, игнорируя настоящие проблемы. Скобочки Лиспа тут эталон, но и Эрлангу часто попадает.

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

Это была критика приведённого примера, где monk указывал на преимущества динамики, а не Эрланга, который критиковать можно очень долго.

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

Ok(try!(calc(x)) + try!(calc(y)))

Это уже красивее.

Хотя не понятно, что в try!. Если просто сброс, то в лиспе так же (+ (unwind-protect (calc x)) (unwind-protect (calc y))).

Или как у тебя наружу ошибка вылазит через вызов «+»?

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

map :: (a -> b) -> [a] ->

тут только для однопозиционной функции и только список

zipWith :: (a -> b -> c) -> [a] -> -> [c]

только для двухпозиционной и опять только список.

Мне для нормальной функции тип напиши.

(map 'string 
     (lambda (x) (char "01234567890ABCDEF" x))
                 '(1 3 4)) 
=> "134"

(map 'list 
     (lambda (x) (char "01234567890ABCDEF" x))
                 '(1 3 4)) 
=> '(#\1 #\3 #\4)

(map 'vector 
     (lambda (x) (char "01234567890ABCDEF" x))
                 '(1 3 4)) 
=> #(#\1 #\3 #\4)

(map 'list 
     #'list
     '(1 2 3)
     '(1 3 4)
     '(3 4 5))
=> '((1 2 3)
     (1 3 4)
     (3 4 5))
monk ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.