LINUX.ORG.RU

Не баг, а фича

 


0

2

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

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

Что ещё есть?

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

★★★★★

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

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

Не знаю как ты делаешь, но у меня обычно следующая ситуация: изначально пишется тест, который, разумеется, не покрывает все случаи.

У тебя в голове куча рекламных агиток статикодебилов просто, вот в чем проблема. Конечно же, если у тебя какой-то сложный контракт на функцию, то его часто парой тестов не покрыть. Но штука в том, что когда мы ведем речь о примитивных системах (хаскель, сишка и т.п.), то никаких сложных контрактов в них выразить нельзя. Можно выразить «в функцию передали слона и она вернула слона». Так уж выходит, что практически все функции с ошибкой вида «вернули лошадь вместо ожидаемого слона» ловятся гарантированно при любом вызове на любых данных. Еще раз, капсом выделю, чтобы было яснее: ПРИ ЛЮБОМ И НА ЛЮБЫХ.

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

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

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

Пример типизированных макросов - TH, например.

Очень даже типизированный, :expr по твоему что? Там еще ident и tt есть, все как в TH.

lil ну так в расте и есть макросистема уровня общелиспа, хотя даже хуже

По крайней мере гигиену и паттернматчинг осилили.

Удачи тебе парсить ошибки типов в нагенеренном макросами говнокоде.

Уж лучше так, чем эти же ошибки ловить в рантайме.

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

Почему хуже? Ну и покажи примеры правильных макросов, правда интересно.

В Racket, например. Scala туда-сюда (потенциально, ограничения жабки сказываются, конечно). Немерле подавал надежды, но автор - поехавший.

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

Вторая - в том, что «giving people dynamic languages doesn't mean that they write dynamic programs», так что для _использования_ всех чудес динамической типизации нужно постараться.

Для использования статической, представь себе, тоже

Представляю. Но там система менее выразительна, так что стараний меньше.

В динамике для этого достаточно один раз запустить свеженаписанную функцию

Не достаточно. Нужно запустить ее с аргументами «достаточно разного» типа.

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

Очень даже типизированный, :expr по твоему что?

Ты не в курсе, что подразумевается под «типизированным макросом». Когда просто указано, что макрос ест экспрешен какого-то вида, то это не типизированные макросы, такие и в схемке есть (с матчингом в syntax-rules). Это когда указывается тип экспрешена, например Expr<Integer> - значит это не просто экспрешен, а экспрешен типа Integer. И тот факт что макрос возвращает корректно типизированный код проверяется не на этапе раскрытия макроса, а на этапе его определения. То есть если макрос может раскрыться неправильно, то тебе это укажет при определении макроса, даже если вызовов нет.

По крайней мере гигиену и паттернматчинг осилили.

Тут соглашусь, да, бывает и хуже.

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

Не достаточно.

Практически всегда достаточно.

Представляю. Но там система менее выразительна, так что стараний меньше.

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

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

Правильно, статической - нет, динамическая (динамически проверяемая) - есть.

Но выше же договорились, что динамической типизации не бывает, бывают «динамические проверки тегов».

то есть система типов в динамическом языке является одной из статических систем типов (является частным случаем статическим, включается в их множество).

В этой цитате обсуждается один из вариантов расширения статической системы типов, но я цитату привел, чтобы показать, что Пирс, например, ставит знак равенства межу «бестиповыми» и «динамический типизированными» системами.

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

Нет, не хочу. У меня, допустим, есть 10 ошибок, каждая ф-я может бросить некую комбинацию нескольких из них. Делать 2^10 типов?

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

Но я тебя понял и «проблема» действительно есть, но она будет проявляться только если твои функции возвращают существующие ошибки, определённые в разных модулях. В этом случае, если совсем не хочется определять свой тип ошибок, то можно воспользоваться трейтом Error и возвращать Result<i32, Box<Error>>. В этом случае, пользователю будет несколько менее удобно разбирать возникшую проблему.

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

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

Но выше же договорились, что динамической типизации не бывает, бывают «динамические проверки тегов».

Это где? Выше же сказано, что бывает.

В этой цитате обсуждается один из вариантов расширения статической системы типов, но я цитату привел, чтобы показать, что Пирс, например, ставит знак равенства межу «бестиповыми» и «динамический типизированными» системами.

Ставит, и? Кто тебе сказал, что «бестиповая» значит «системы типов не существует»? Ты, как и многие, путаешь систему типов (множество типов и набор правил, определяющих сопоставление типов термам) со способом тайпчека.

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

Нет. Угадаешь, почему?

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

Но какие-то подвижки есть, будут чаще применяться - будут и инструменты/практики.

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

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

Но их очень много. 2^10 потенциально.

Но я тебя понял и «проблема» действительно есть, но она будет проявляться только если твои функции возвращают существующие ошибки, определённые в разных модулях.

Почему?

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

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

Второе, конечно, тоже верно, но макросы в статике - это действительно адъ. Очень уж неудобно. Это значительно ограничивает количество юзкейсов.

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

Но то как ты перефразировал уже само по себе какбы намекает, что result = адъ и погибель.

Это намекает только, что арифметика с Result - плохая идея.

Я говорю, где это будет _в коде_.

Ну если мы говорим не о лиспах (хотя я бы с удовольствием посмотрел на кусок кода демонстрирующий как надо правильно), то у нас есть, по сути, два варианта: «коды возврата» (а Result - это продвинутый их вариант) и исключения. Последние, по идее, как раз отделяют логику, от обработки ошибки. Вот только, если обрабатывать надо буквально каждую строку, то получается хрень:

try
{
    foo();
}
catch(/* ... */)
{
    // ...
}
try
{
    bar();
}
catch(/* ... */)
{
    // ...
}
И сразу выясняется, что логика и обработка ошибок всё равно размазаны ровным слоем.

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

У тебя в голове куча рекламных агиток статикодебилов просто, вот в чем проблема.

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

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

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

Всё так, кроме того, что мне с типами сильно комфортнее. И «борьба» не видится мне чем-то сложным и отнимающим заметную часть времени.

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

Это намекает только, что арифметика с Result - плохая идея.

Ну замени + на любую двухаргументную ф-я и пример аналогичным получится.

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

Если у тебя foo/bar/etc функции, а не процедуры, такая ситуация невозможна. А значит и result тебе не поможет.

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

В Racket, например.

Ну это всё-таки «тоже лисп», в смысле там синтаксис смотрится более родным, в отличии раста. Опять же, гигиена и в расте есть. Кое-какое указание «типов» тоже.

Scala туда-сюда

До скалы, к сожалению, руки не дошли. Хотя пару раз хотел взяться.

Немерле подавал надежды, но автор - поехавший.

Почему? Да и автор там не один, вроде. Опять же, их jetbrains «приютили» - пилят что-то.

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

Мысль про тесты понятна, спорить не буду. Хотя что делать, если в разных ветках исполнения функции возвращается разный тип?

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

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

Почему?

Какие-то странные и невнятные цели у nemerle2, отдающие шизофренией.

Опять же, их jetbrains «приютили» - пилят что-то.

Ну, может, что-то и изменилось в лучшую сторону. Будем надеястья.

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

Почему?

В расте ошибки, зачастую, это как раз такие группы, а не разрозненные типы, например в карго:

pub enum Error {
    Curl(curl::ErrCode),
    NotOkResponse(http::Response),
    NonUtf8Body,
    Api(Vec<String>),
    Unauthorized,
    TokenMissing,
    Io(io::Error),
}
Соответственно, если тебе надо вернуть одну из таких ошибок, то никаких проблем нет. Они начинаются, если у тебя много «разных видов» ошибок, которые надо как-то объединять.

Но их очень много. 2^10 потенциально.

Вариантов ошибки из одной либы? Которые надо давать обрабатывать пользователю?

Ну и всегда нужно исходить из практического смысла - можно хоть вообще строку ошибки возвращать, если это устраивает. Ну или Box<Error> тоже простое решение. Или как в карго - иметь несколько отдельные ошибок плюс обёрнутые «общие» ошибки (io::Error).

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

Второе, конечно, тоже верно, но макросы в статике - это действительно адъ.

Ты ж сам про немерле упомянул - как мне показалось (хотя активно не щупал), получилось красивее, чем в расте.

Но вообще, чем именно статика тут мешает?

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

Ну замени + на любую двухаргументную ф-я и пример аналогичным получится.

Если оператор, то может быть. Если вообще, то с какой стати?

Если у тебя foo/bar/etc функции, а не процедуры, такая ситуация невозможна.

Почему это? Пусть они возвращают результат или бросают исключение. Тогда на С++ будет вообще «весело»:

int f; // Тут ещё и выводом типов в С++ не получится воспользоваться.
try
{
    f = foo();
}
catch(/* ... */)
{
    // ...
}

int b;
try
{
    b = bar();
}
catch(/* ... */)
{
    // ...
}

return f + b;
С резалтом всё-таки покрасивее будет.

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

и затраты требуются только на них (в отличии от типов).

Ну «затраты на типы» тоже штука спорная. Опять же, с типами при рефакторинге сломать логику сложнее, чем забыть обновить тест.

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

Какие-то странные и невнятные цели у nemerle2, отдающие шизофренией.

Вроде, с немерле2 всё нормально? Насколько я знаю, там немного в другом дело. Они ведь делают (на немерле1) «фреймворк для создания языков», а уже на нём будут делать немерле2. Звучит, конечно, забавно, но я думал, что идея «свой ДСЛ под каждую задачу» близка лисперам.

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

В заголовке сказано «баг». Надо было одно слово использовать.

Всё равно, разработчики первого лиспа - противники принципа «worse is better», поэтому сознательных недоработок у них быть не может. Могут быть только случайные, вроде синтаксиса вызова функций.

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

Так же как на низкоуровневом языке в общем писать труднее, чем на высокоуровневом.

Статическая типизация уже не является «низкоуровневым языком».

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

Перестань сочинять, очень даже типизированный: http://is.gd/Z0zXd6 .

Кстати, как в этот чудо-map массив передать или строку в качестве последовательности?

Пытаюсь написать [1,2,3,4], а он мне «итератор не определён»

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

Но вообще, чем именно статика тут мешает?

Если макросы типизированные - это ограничивает их выразительную силу и усложняет. Если макросы нетипизированные - исправление ошибок типов в экспанде сложных макросов превращается в адъ. Речь идет о том, что в этом случае макросы уже нельзя использовать повсеместно, как обычный инструмент, когда у тебя 90% кода - макросы (как в том же Racket).

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

Если оператор, то может быть. Если вообще, то с какой стати?

Ну будет у тебя f(a, b) вместо a+b, и try! один хрен превратит это в говно.

Почему это? Пусть они возвращают результат или бросают исключение

Ты же результат где-то используешь? Вот в твоем выше примере ты мог обойтись return foo()+bar(); засунув это в один единственный try-блок без изменения семантики.

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

Они ведь делают (на немерле1) «фреймворк для создания языков», а уже на нём будут делать немерле2.

Я о том и говорю, весьма много шизофрении в том, как они видят «фреймворк для создания языков», налицо непонимание задачи и сложностей, с которыми они неминуемо столкнутся, если попытаются сделать в том виде, в котором хотят. Но моей инфе год-два давности, вполне возможно что что-то поменялось в лучшую сторону в этом смысле.

Звучит, конечно, забавно, но я думал, что идея «свой ДСЛ под каждую задачу» близка лисперам.

Ну да, тот же Racket и есть такой фреймворк, и в таком качестве (быстрое запиливание десятков и сотен дслей) использовался при разработке Last Of Us, например. Тут дело в том, что разработчики немерле куда-то не туда свернули немного в своем видении результата.

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

Статическая типизация уже не является «низкоуровневым языком».

В статически типизированном языке де-факто два разных языка. Язык, на котором пишутся термы и язык, на котором пишутся типы. Так вот, второй обычно очень низкоуровневый.

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

Статическая типизация уже не является «низкоуровневым языком».

В статически типизированном языке де-факто два разных языка. Язык, на котором пишутся термы и язык, на котором пишутся типы.

Да, я именно об этом.

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

если у тебя какой-то сложный контракт на функцию, то его часто парой тестов не покрыть

сложный контракт на функцию

Может быть в этом проблема? Функция, которая делает всё сразу, и в которую нужно передать штук двадцать параметров?

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

Ты ж сам про немерле упомянул - как мне показалось (хотя активно не щупал), получилось красивее, чем в расте.

Но вообще, чем именно статика тут мешает?

Постоянные битвы с типизатором и невнятное АПИ компилятора утомляет при написании нетривиальных макросов. С дженериками тоже все замудрёно.

Вообще у Немерле слишком заумный типизатор - отсюда куча странностей и неудобств при написании макросов.

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

Пытаюсь написать [1,2,3,4], а он мне «итератор не определён»

Превратить в итератор. Для массивов надо добавить &, вот так - &[1,2,3,4], для строк - метод chars.

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

Если макросы типизированные - это ограничивает их выразительную силу и усложняет.

(Common) лисперы и так часто жалуются, что в ракете макросы сложнее, например. Но ведь ты согласен, что они не зря такие и более удобные. Можно дать оба инструмента - типизированные и нетипизированные. Ошибки в экспанде - ну да, наверное. Впрочем, в С++ с шаблонами не менее весело в этом плане. Так что тут люди привычные, ведь раст не лисп теснить собрался.

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

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

Ну будет у тебя f(a, b) вместо a+b, и try! один хрен превратит это в говно.

С какой стати? Возможно, я что-то не понимаю, давай на примере. Если нам надо просто ранний возврат, то да, с исключениями удобнее, но try! не то, чтобы совсем говно:

// Вернём ошибку или результат.
try!(f(a, b))
// Вернём результат или вылетит исключение.
return f(a, b);
А вот если ошибки надо обрабатывать сразу, то всё интереснее:
match f(a, b) {
    Ok(x) => return x,
    Err_1 => /*... */
    Err_2 => /*... */
}
try
{
    return f(a, b);
}
catch (const Err_1&)
{
    // ...
}
catch (const Err_2&)
{
    // ...
}

Вот в твоем выше примере ты мог обойтись return foo()+bar(); засунув это в один единственный try-блок

Если меня бы это устроило, то скорее всего и try/catch говорить не пришлось - пусть кто-то выше обрабатывает. Я именно про случай когда надо обрабатывать все ошибки и сразу.

Вообще не зря stl/boost исключения используют «опционально», позволяя обойтись почти везде, если хочется. Я достаточно работал с С++ кодом, где исключения использовались, но и там они бросались не на любой чих и во многих случаях ошибки обрабатывались сразу.

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

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

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

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

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

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

Вообще у Немерле слишком заумный типизатор - отсюда куча странностей и неудобств при написании макросов.

Ок, может быть, я-то на немерле не писал.

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

Судя по времени последнего коммита проект скорее мёртв чем жив.

Ну нет, они (относительно) регулярно постят новую инфу (на рсдн). Почему репозиторий не обновляют хз.

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

да может быть и один параметр

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

(Common) лисперы и так часто жалуются, что в ракете макросы сложнее, например. Но ведь ты согласен, что они не зря такие и более удобные. Можно дать оба инструмента - типизированные и нетипизированные.

Ну можно, конечно, но тут ситуация такая, что куда ни кинь - всюду клин. Какой вариант макросов не выбери, будет беда.

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

С этим, конечно, не согласиться нельзя.

Ошибки в экспанде - ну да, наверное. Впрочем, в С++ с шаблонами не менее весело в этом плане.

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

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

А вот если ошибки надо обрабатывать сразу, то всё интереснее:

Так разница чисто синтаксическая во втором случае. В первом же вариант с исключениями будет лучше (особенно если функциональный вызов не один, а нечто вроде f(g(x), h(y))), каждая из ф-й может чего-то кидать.

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

Но такого не будет.

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

Так разница чисто синтаксическая во втором случае.

Мусорa в коде с try/catch, в этом случае, намного больше.

В первом же вариант с исключениями будет лучше

Конечно. Повторюсь - я не против исключений, даже наоборот. Надеюсь, что в расте дойдут до их необходимости. Тем более, что они поверх имеющейся «паники» делаются несложно.

Но такого не будет.

Как сказать.

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

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

Слишком большое внимание уделяется синтаксическим изъебствам с такими-то ПЕГ-парсерами и прочей ересью. При этом под ковер заметен ряд важных проблем - модульность и фазирование, конкретное представление синтаксиса с биндингами, как будут работать macro-generated макросы, наличие библиотек, упрощающих использование некоторых общепринятых паттернов (это важно, потому что каждый раз велосипедить вещи руками в данном случае очень невесело). Никакой теоретический работы, как я понимаю, не идет вовсе (иначе бы были публикации с упрощенной семантикой экспанда, доказательством корректности и тому подобных вещей), что тоже смущает - а ведь построение корректной модели гигиенического экспандера, достаточно простого и с удобным апи, уже само по себе не такая тривиальная задача, даже без типов и возможностью полной поддержки рантайма (а у них со стороны рантайма поддержки не будет, что тоже смущает).

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

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

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