Pull to refresh

Comments 185

На постсоветском пространстве идея "изобрести велосипед" (выдумать свой ЯП) обоснованно не пользуется популярностью ни у одиночек, ни у компаний. Из серьёзных проектов вспомнить можно разве что Kotlin, который, конечно, обвинений в велосипедостроении не избежал.

А рантайм и компилятор 1С написаны на 1С?

Следуя такой логике, даже машинного кода нет, есть только математика…

Ложки нет, а язык 1С есть, но по обозначенным критериям он не подходит на роль "велосипеда аля Kotlin от жителей постсоветского пространства".

Это как то связано с тем, на чем написан компилятор 1С? Если нет, то ваш ответ, я полагаю, адресован автору коментария выше.
1С — это все же «предметно-ориентированный» ЯП
UFO just landed and posted this here
В APL по большей части без них обходятся.
UFO just landed and posted this here

Недавно влюбился в Clojure. Функциональный язык для продуктивной веб-разработки.

UFO just landed and posted this here
Совершенно верно, поэтому большая часть статьи бессмысленна.
Когда начинал читать, ожидал увидеть совершенно другие аргументы.
А я, когда начинал читать, уже представлял себе Камеди Клаб. Как раз в стиле «Суслик с-ка личность».
Абсолютно согласен, помесь академического бреда, баек и заблуждений юного программиста не имеющих отношения к реальному положению дел. Пугалки в стиле «юный программист не может избрать правильный дао для решения задачи, потому что некоторые языки мультипарадигменны».
Проблема с исключениями в наших языках программирования — это то, что они по сути являются замаскированным GOTO. А мы уже выяснили, что использование GOTO — это плохо.
С таким же успехом и циклы, условия, вызовы функций можно назвать замаскированными GOTO.
goto кстати вообще нельзя ассоциировать с исключениями, т.к. goto в общем случае обязан знать куда именно он переходит, а где будет поймано исключение программа знает далеко не всегда
Не обязан. Есть косвенные формы goto с вычисляемым адресом назначения.
Но программа то знает, как (куда) она пойдет выполняться дальше, после прерывания.
нет. Пример: библиотека кидает исключение. Откуда ей знать, где программа будет это исключение перехватывать? И будет ли вообще?
С библиотеками вы правы, я имел в виду более частный случай, когда программист пишет обработку исключений сам.
Хорошей практикой признаётся, когда функции/методы низшего уровня ничего не знаю о том, кто и откуда их вызывает вообще и, в частности как будут обрабатываться исключения. Инкапсуляция, изоляция и всё такое. Аналогом goto («программа то знает, как (куда) она пойдет выполняться дальше, после прерывания» можно считать только когда throw находятся в одной области видимости c try/catch/…
Просто с языка сорвали (поставил плюс). Я когда открыл, ожидал, что будет относительно интересная статья, но потом автор просто убил. После этой фразы дальше читать не хватило душевных сил. Надо сказать, что (как программисту на Python-е) особенно смешно (горько), т.к. каждый день исключения обрабатываю. Ожидал, что автор так потом и циклы приговорит)) Если развить идею, то можно вообще остановится на простейших операциях сложения, вычитания, ввода и вывода — остальное ведь избыточно и запутывающе. Ещё согласен с potan, что GOTO тоже совсем не плох, и всё хорошо в меру (дело в контексте).
А я когда читал, всё ждал, когда уже автор озвучит, что идеальный язык — это Lisp. Вроде всю статью его описывал, но так и не назвал )))

Так ведь в (Common) лиспе есть и что-то типа goto (tagbody) и аналог исключений и мутабельные переменные и сравнение значения ссылок и наследование и т.д.

Когда слышу от людей, что GOTO это плохо — понимаю, что дальше с ними говорить о программированиине не имеет смысла для себя. Автор видимо никогда не писал автоматы. А они как раз таки используются во многих парсерах.
UFO just landed and posted this here

На goto получается изящнее. Хотя, я давненько не писал автоматов.

Надо отметить, что против GOTO высказывались в то время, когда этот оператор мог отправить вас внутрь цикла или даже в другую функцию. И я легко могу представить, какой это был ад =) goto для выхода из сложного цикла или goto cleanup; зачастую выглядит лучше и понятнее, чем другие способы. Все хорошо в меру и к месту.
goto для выхода из сложного цикла
Согласен, дело не в goto, а в способе его применения, в том же JS есть break метка для выхода из глубоких циклов.
против GOTO высказывались в то время, когда этот оператор мог отправить вас внутрь цикла или даже в другую функцию

Это ещё фигня, программа вообще могла сама себя изменять в памяти! Править свой же собственный код! И это считалось хорошим искусным панорамированием Карл!
spolier: Память была не резиновая, и это позволяло здорово экономить память.
Это для тех, кто еще помнит, чем отличались.соm и .exe файлы.))
Это ещё фигня, программа вообще могла сама себя изменять в памяти! Править свой же собственный код! И это считалось хорошим искусным панорамированием Карл!

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

Сомнительно, что излишними. Название интерфейса позволяет разделять одинаково именуемые операции с одинаковой сигнатурой, но разной семантикой.
Просто используй указатели на функции. Ты же любишь указатели на функции?
Не совсем понятно, что делать если у объкта вызывается несколько различных интерфейсных методов — передавать их все в качестве аргументов в функцию, на мой взгляд, это не очень то и удобно.
UFO just landed and posted this here
Кстати, да. Если автор постоянно упоминает, что его кандидаты на улучшение языка, путём выбрасывания фич, остаются тьюринг-полными, то идеалом в таких условиях окажется машина тьюринга. Т.к. из неё уже ничего нельзя выкинуть, сохранив полноту. :)
UFO just landed and posted this here
А вы думаете, чего он стал таким популярным в эзотерике. :)
К сожалению, полной по Тьюрингу будет только машина Тьюринга с лентой бесконечной длины, а в нашей вселенной атомов чуть меньше, так что печаль-беда. Идеального ЯП по их критериям не существует.
Ничего подобного, в машине Тьюринга огромное количество ячеек памяти, которые можно изменить и вся ее работа состоит из сплошных GOTO.

Следуя логике статьи конечным идеальным ЯП будет unlambda.
Там даже от лямбда-абстракции отказались!
Да что греха таить, даже от переменных/констант!
В Машине Тьюринга в некотором виде GOTO есть — переход в новое состояние автомата. И мутабельное состояние ленты.
Тогда уж лямбда-исчисление Черча.

Кажется, для оспаривания подобных заявлений придумали термин "turing tar-pit", чтобы не спешили выдавать "простоту" за простоту.

UFO just landed and posted this here
Очередной наброс? А автор своей статьёй гребёт все задачи гребёнкой не самого удачного энтерпрайза не самого удачного ЯП. Ну ок.
Мне кажется рефлексию лучше называть рефлексией. Я не сразу понял, что отражение — это оно и есть.

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

Мое мнение:
GOTO — если язык поддерживает оптимизацию хвостовых вызовов, то GOTO не нужен совсем. Если не поддерживает, некоторые вещи приходится делать некрасиво и менее эффективно. Хотя такие вещи обычно для человека-программиста не характерны, они могут возникать при генерации кода — мне разработчики суперкомпилятора Java (грубо говоря оптимизатора, выдающего код на том же языке) показывали пример реализации конечного автомата через таблицы, который не получалось перевести в код без GOTO не использую трюков с дополнительными переменными.
Исключения — лично мне больше нравится подход Erlang и Rust, где фатальные ошибки можно обработать только на границе процесса, а нефатальные надо реализовать с помощью кодов. В общем то наиболее полезны две стратегии обработки ошибок — повторить операцию или записать в лог и свалиться. Если их удобно поддержать в языке, использовать исключения придется только в каких-то экзотических случаях и для них можно сделать сложный синтаксис, что бы не повадно было.
Числовые типы нужны. Хотя они могут быть параметризованным вариантом одного типа, особенно если есть поддержка зависимых типов (как минимум refined types). Во первых, для эффективности — точность вычислений в разных местах программы может требоваться очень разная. Во вторых, некоторые операции не для всех типов осмыслены — я бы не хотел, что бы компилятор позволял обращаться к массиву по индексу с плавающей точкой, да и синус целого числа выглядит странно. В третьих, нужна возможность реализовать свои типы, похожие на числовые — комплексные, дуальные, матрицы и тп.
Сравнение ссылок — в императивном языке оно необходимо. Два равных по значению в данный момент объекта могут перестать быть равными через какое-то время и программист должен иметь возможность это контролировать. В декларативном языке, без мутабельных сущностей они действительно только путают.
Наследование — очень интересный вопрос. С одной стороны инструмент очень мощный, что бы от него отказываться, с другой очень сложный, и, что хуже, сложность его мало кто понимает (большинство программистов не слышали не только про топосы, но даже про принцип подстановки Лискофф).
Интерфейсы — как минимум в виде классов типов нужны. Роль часто предполагает несколько операций.
Рефлексия — у Страуструппа в главе «Правильное и неправильное использование RTTI» приведено несколько способов неправильного и ни одного правильного. Тем не менее при реализации интерпретаторов она полезна. Я бы хотел уметь ей управлять с помощью аннотаций или deriving.
Циклические зависимости — как парашют, нужны редко, но плохо, когда их не окажется, если понадобятся. Вообще, меня очень расстраивает страх многих перед рекурсией, я бы хотел жить в обществе с более высокой математической культурой.
… Rust, где фатальные ошибки можно обработать только на границе процесса, а нефатальные надо реализовать с помощью кодов.

С 1.9 это уже не совсем так: https://doc.rust-lang.org/std/panic/fn.catch_unwind.html, хотя идиоматичный подход остался тем же.

Можно глупый вопрос? А эта стена текста вообще имеет смысл? Изначальный пост не несёт достаточной полезной нагрузки для того, чтобы с ним спорить. И это просто разбивается об одну единственную фразу «компьютер устроен так».
На самом нижнем уровне у нас адреса и goto. Нам это не нравится и мы обарачиваем это всё в ассемблер, Си и так далее. Чем больше мы делаем обёрток, тем медленнее и проще для нашего понимания становится код. До тех пор, пока он не становится слишком большим и медлительным, а программисты настолько запутываются во всех хитросплетениях и условностях, что становятся нигилистами, отрицающими пользу каких бы то ни было инструментов как таковых.

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

По статье в целом: один численный тип на всех — это дичь. Прощай производительность в мультимедиа задачах.
Мне кажется рефлексию лучше называть рефлексией

Ну Вы эту мысль до авторов Википедии донесите. А пока используем термин как есть.

С каких пор Википедия считается АИ?

Полностью согласен, но остальные ещё хуже

А как на счёт АИ, на которые википедия сылается?

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

По делу — запросто. Цепляешь АИ и правишь. Мне почему-то удаётся.

Изменение значений переменных
В языке С# (да и в Java, и в Javascript) нет ничего для предотвращения этой проблемы.

Как раз для этого случая в C# используются ref и out. В Java да, это доставляет массу неудобств
Если передаёшь объект, то неизвестно изменятся ли его поля или нет, и ref и out никак не помогают в этом.
Мне кажется, это вопрос корректного дизайна метода. Во-первых, если полям прямо нельзя меняться — так запретите их менять напрямую и обращайтесь эксплицитно через геттеры/сеттеры, от пальбы в ногу убережёт. Во-вторых, я не знаю, что делает метод Map() в примере, но приходит в голову, что что-то считает на основе переданного ему объекта (иначе бы он назывался map_with_recalculation()). Такому методу правда надо менять исходный объект? Мне кажется, городить немутабельность вообще всего по этому поводу несколько оверкилл.
А мне кажется что голове программиста есть чем заняться, когда с этой задачей вполне может справиться компилятор. Особенно если этот проект перешёл по наследству с неизвестными соглашениями по коду и писал его какой-нить испанец не знакомый с английским языком.
Если поля прям нельзя менять, то они и будут static/readonly/final, но мне надо быть уверенным (не знаю уж для чего) что передав об'ект в этот метод обратно он мне вернётся в неизменном виде и опять же сеттеры мне тут не особо помогают. В SQL если надо чтоб значение из процедуры вернулось в переменную, то при вызове у такого параметра будет явно стоять OUTPUT.

В D есть модификатор доступа const, который запрещает любые косвенные изменения.


void bar( const A a ) {
    auto b = a.b;
    b.foo = 2; // Error: cannot modify const expression b.foo
}

А наоборот? Т.е., создали объект, в конструкторе есть const-параметр, его захватили во внутреннее поле, затем снаружи тот же объект пытаемся поменять.

Для этого уже есть модификатор immutable.

Можете показать пример?

https://dpaste.dzfl.pl/d288eabaee72


class A {
    immutable B b;
    this( immutable B b ) {
        this.b = b;
    }
}

class B {
    auto foo = 1;
}

void main(){
    auto a = new A( new B );
    a.b.foo = 2; // Error: cannot modify immutable expression a.b.foo
}

Это compile-time или run-time? Если compile, то пометка immutable должна быть вверх по стеку вплоть до места создания?

Да, разумееется. В данном случае компилятор автоматически создаёт неизменяемый В исходя из контекста.

Т.е. как только B где-то использовано как immutable, весь тип становится immutable? Или только конкретный экземпляр, использованный в конкретном пути выполнения?

Это отдельный производный тип immutable(B). Например, string — это алиас для immutable(char[]).

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

Не сможем, типы-то разные. В этом случае нужно создать свою иммутабельную копию:


class A
{
    immutable B b;
    this( B b )
    {
        this.b = new immutable B( b.foo );
    }
}

class B
{
    int foo;
    pure this( int foo )
    {
        this.foo = foo;
    }
}

void main()
{
    auto b = new B( 1 );
    auto a = new A( b );
    b.foo = 2;
    writeln( a.b.foo ); // 1
}
В Java для предотвращения изменения переменных есть final.
каким образом final спасет от изменения полей внутри переданного объекта?
UFO just landed and posted this here
final запрещает перезапись перезапись переменной. Т.е. создается ссылка на объект и больше не меняется. Вот только состояние объекта может меняться как угодно.
Можно конечно все поля класса пометить как final :)
Для этого существуют иммутабельные объекты с final полями. А так же Collections.unmodifiable*();
Много спорного, как сюда попали, к примеру, нулевые указатели, мне не понятно, это не «лишняя возможность», это излишнее ослабление строгой типизации. В общем здравых мысле хватает, но в общем и целом — попытка подвести под единый KISS-принцип очень разнородных вещей. Кстати, путей упрощения два, оставить некий метамеханизм, упростив сам язык (lisp — эталон) и этот путь чреват в продакшене… В общем чтобы далеко не ходить, дам ссылку на другой сегодняшний пост: https://habrahabr.ru/post/306564/ либо действительно упростить язык ограничив возможности. Но эволюция всё может изменить — простенький интерпретатор шаблонов HTML-страничек может превратится в большой объектно-ориентированный язык с элементами мультипарадигмальности.

Go — вроде-бы пример успешного применения второго подхода, но время покажет, чем станет этот язык лет через пять (про reflection и метапрограммирование в прадакшене уже говорят https://habrahabr.ru/post/306304/ )
Автор делает реверансы в сторону функциональных языков программирование (именно такой язык получится, если от среднестатистической java отпилить «лишние возможности»), но забывает рассказать о минусах функциональных языков. Статья очевидный наброс.
ФП GOTO, отказывается от изменяемых переменных и сравнения ссылок. Остальное с ФП не особо конфликтует, а циклические зависимости даже скорей хорошо уживаются.
В функциональной парадигме эти «особенности» изменены до неузнаваемости, те же исключения или нулевые указатели как пример.
Из перечисленных особенностей, отсутствие харакретно только для трех. (Указатели не считаем, они вообще мало где есть, но есть вариант Scheme с ними.)
А для одного даже более характерно присутствие.
На каждый потерянный способ сделать ошибку приходится 10 потерянных способов оптимизации
С оптимизациями справится компилятор. И лучше ограничить возможность, что бы ему не мешать. Например, иммутабельные величины проще перевести во внутреннее представление single assigment.
Даже банальное знание того, что на входе однобайтовые целые числа вместо восьмибайтных double может дать очень хороший прирост производительности в тех или иных алгоритмах. Подсказка о том, какой контейнер лучше выбрать, может сэкономить прилично времени на сортировках/вставках/копированиях/размещении на стеке. Знание, какие поля разделены между потоками, а какие нет сэкономит на мьютексах. Итого экономия на спичках ускорит код в несколько раз
Компилятор знает что на входе, и способен задействовать те или иные оптимизации.
Теоретически, выбор контейнера тоже можно отдать на откуп компилятору, ознакомленному с результатами профилирования. Для иммутабельных контейнеров его тип может быть выбран в библиотеке на момент создания — так сделано, например, в Scala.
Разделяемость между потоками может быть отражена в типах переменных (частично это реализовано в Rust) и компилятор будет ставить мьютексы там, где надо.
а откуда компилятору заведомо знать, что прилетит программе по сети, из файла или библиотеки? Что до профилирования — разрабатывая библиотеку, вы можете предполагать, что пользователь будет на одну операцию сортировки делать 1000 операций вставки/удаления, а вот компилятор то знать не будет, если ему явно не сказать. А если всё указывать явно, то это уже прямая противоположность языку из статьи

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

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

а может и не понять. Интерфейс разных контейнеров же, например, должен быть примерно одинаковым.
Т.е. вместо «uint_8», например, программисту предлагается разобрать «код проверки входных данных»? Гуммано…
В общем и целом статья строго философская, на тему «если бы слоны были бабочками». «Под капотом» у всего этого один фиг процессор с таки адресами памяти и набором примитивных операций. Вся «иммутабельность» и т.д. и т.п. — абстракция. При этом, в том виде, что предложен в статье, абстракция не имеет под собой основы и нарочь оторвана от реальности. Это все просто мат. теория.
Собственно, поиск по красно-черному дереву не имеет никакого математического обоснования. С точки зрения чистой математики поиск в дереве имеет бОльшую линейную сложность, чем элементарный перебор в лоб. Отсюда и проблема. В статье — красиво. На практике — это «горизонт», который, как мы помним, «удаляется по мере приближения».
Приведите статью в которой утверждается, что O(log n) больше, чем O(n).
А я и не говорил, что O(log n) больше, чем O(n).
Я говорил, что на уровне «чистой» математики, собственно, само понятие «сложности» алгоритма — достаточно странная штука.
Для «чистого» математика выборка из массива чисел выглядит как «для всех X бОльших Y». А вот трудоемкость поиска этих самых X, бОльших этого самого Y — это уже вопрос скорее прикладной математики, применительно к конкретно имеющейся системе. Т.е. я больше о том, что в отрыве от конкретной модели, с которой мы работаем, налагающей свои вполне определенные ограничения, расчет трудоемкости и прочего, мягко говоря, не имеет смысла.
С точки зрения математика result = result0 + 4 = result1 * 18 = 24 * 5 — вполне себе нормальная цепочка. Для программиста инстанцирование промежуточных result'ов — боль и разразаривание ресурса.
Даже в случае сортировки, выборки и т.д. Если программист уверен в том, что размер списка значений не превысит, допустим, 4 элементов, оптимальный алгоритм сортировки — пузырек, оптимальный поиск — линейный перебор.Поэтому призывы «а давайте абстрагируемся от всех деталей и выпилим любую возможность в них углубиться, чтобы не вызывать соблазна» мне лично кажутся злом.
Как мне статически узнать вероятности условных переходов? Я узнаю их по результатам профилировки (или по предполагаемой модели использования), и ручками оптимизирую код для наиболее вероятных случаев.

Компилятор мне в лучшем случаем даёт директивы типа hot/cold.
JIT-компиляторы тоже так делают.
К тому же ни кто не мешает компилятору использовать результаты профилирования. Я уверен, он сможет разобраться в них лучше человека.
Скажу коротко — молоток.
Казалось бы идеальный инструмент — проще и функциональней дальше некуда.
А смотри же — и убить может.
То же самое будет и с идеальным языком программирования.
Кто-нибудь все равно поделит на ноль.
В современных высокоуровневых ЯПах разве это вызовет фатальное падение программы? Ниже C#, VS2013, DotNet 4.5
double a;
double b;
b = 0;
a = 2 / b;
MessageBox.Show(Convert.ToString(a));
Результат вывода: «бесконечность»
деление на ноль здесь в качестве примера
Сравнение ссылок очень спорный момент. Как иначе понять этот тот же самый объект или его копия.
А, да, если ссылки на одну и ту же область памяти, то не факт что об'екты равны (если они разного типа).
1. А ничего, что описанное в разделе Указатели — прямая причина описанного в Изменение значений переменных?
2. Если две переменных указывают на два разных блока памяти (пускай даже заполненных идентичными данными) — они не считаются равными. Это не интуитивно и порождает баги. Очень, очень субъективно.
3. Из-за существования полных по Тьюрингу языков программирования без нулевых указателей… мы знаем, что можем выразить любую валидную программу без данной концепции, а значит её устранение уберёт огромный пласт ошибок. И добавит серьезной головной боли тем, кто пытается подогнать существующие шаблоны проектирования под этот ЯП, что выльется в падение скорости разработки просто на ровном месте.
4. Согласно моему опыту, вы можете смоделировать всё, что угодно с помощью интерфейсов с одним методом, что автоматически означает возможность смоделировать то же самое и без самих интерфейсов. Отличная фраза. Вы можете, согласно моему опыту.
5. С ресурсами современного компьютера мы можем позволить себе язык программирования, дающий ровно один числовой тип. А современные компьютеры уже умеют вещественные числа произвольной точности, да со скоростью целых?
6. Однако, одно из последствий применения SOLID состоит в том, что вы должны предпочитать интерфейсы, обозначающие какую-то одну роль, а не интерфейсы, включающие наборы методов. Логический вывод из этого — каждый интерфейс должен иметь ровно один метод.
Вообще я так понимаю, парень открыл для себя функциональщину, и его так поперло, что он решил немедленно всех вокруг под это причесать.
А в чем проблема вместо нулевого указателя использовать Option (или его аналоги, которые есть почти во всех языках) там, где они могли бы возникнуть? Какой шаблон разработки при этом порвется?
наверное потому, что проверка указателя против нуля с последующим разыменованием куда быстрее, чем проверка флага и двойное разыменование указателя?
Внутри Option может быть реализован как нулевой указатель. Да и такая разница в производительности для больше современных задач не принципиальна.
Может быть. А может и не быть. Вдруг он реализован как пара буль/значение? А вдруг в языке чуть ли не каждый класс — указатель на данные в куче, и, т.к. делать указатель на указатель бессмысленно, optional вырождается в экземпляр того же класса, но с другим интерфейсом? А в друг в языке все переменные — variant поля, поддерживающие null, и optional'а нет за ненадобностью? А вдруг компилятор вообще решает отдельно хранить флаги и данные?
UFO just landed and posted this here
Что такое GC? (не знаком с Хаскелем)
UFO just landed and posted this here
Option это тот же null, но явно требующий проверки на валидность. Теоретически вы правы, популярные шаблоны замена не порвет, но емнип, Option на практике как раз опциональный, и о нем частенько забывают. А в совсем безнулловом Rust Option/Result/самописный enum является абстракцией над тем, что нужно получить, и эти абстракции не всегда так легко отдают инкапсулированное в нужном виде. Конечно, это частный случай, но набирающий популярность.
Если голова сто (ну или хотя бы пятидесяти) умовая, то проблем вообще нет. А так — пишу на Хаскеле функцию (любую, но допустим это будет интерпретатор) некоторого языка, написал, все работает. А теперь надо предусмотреть (вообще-то это по-хорошему надо не «теперь». а с самого начала было предусматривать, но голова не стоумовая да и будущее было неведомо) возможность различных некорректных ситуаций — ошибок парсинга, несоответствие арности реализуемых операций и т.п. И начинается оборачивание ВСЕХ функций в Maybe. И здравствуйте монады, аппликативные функторы и т.п. ВСЕ ломается, уже ничего ни с чем не комбинируется, вместо мапов нужны сиквенсы и т.п. А в той же Джаве — воткнул кейс по == null — и полное счастье и гармония (хотя написание на Джаве почему-то оставляет ощущение пребывания в 19 веке).
Вы прям описали мою войну с Dependency Injection в Rust. Надо одновременно и Option, и Arc, и RefCell, и Box, и чтоб все это залезало в enum в зависимости от запрошенного реализуемого traitа. А в самом пуле надо либо кастить все это завернутое друг в друга барахло, либо дружить несовместимые одно с другим времена жизни.
UFO just landed and posted this here
Хорошо, из строки в мэйби АСТ, а его уже вычислять «нормально». Ой, а тут у нас деление на ноль при вычислении АСТ! А здесь логарифм/корень от отрицательного числа… А там команда — вывести результат в файл, а файла нет/недоступен для записи…
Как вы все знаете, на стороне Добра выступает Закон Мура, а на стороне Зла Закон Вирта. Автор однозначно воюет на стороне Зла. Давайте создадим язык, о котором мечтает автор и откатимся на несколько десятилетий назад по производительности систем…

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

Callback'и забыли — ИМХО, скоро славу GOTO повторят.
Фиг вам, а не отменить continuation passing style.
Да бросьте. В каком-нибудь геймдеве вам правда меньше нравится defender.get_damage(attacker.spawn_damage_object()), чем attacker.damage(defender) и потрясающее удовольствие тащить во втором случае из объекта защитника всю (потенциально сложную и громоздкую) оборону и обрабатывать все возможные логики защиты у всех, кого когда-либо кто-либо может атаковать? Не говоря уже про on_event, например.
В ваших мечтах. Самая страшная вещь в том, что ни callback'и, ни goto никуда не ушли. Вам их просто обернули в сигналослоты или сообщения, в исключения, в различные слоновьи паттерны, но сами-то механизмы никуда не делись. Компиляторы разварачивают весь умный код в них, и далеко не всегда он это делает удачно, очень часто ему не хватает времени\сил\ума на это.

А что в итоге? В итоге время компиляции и прожорливость приложения вырастает в разы в угоду понижения уровня вхождения в профессиональное программирование. Для ряда задач это хорошо, те же сайты-визитки или мелкие утилиты для бухгалтеров писать кому-то нужно. Но когда человек с подобным опытом неэффективного программирования попадает в энтерпрайз, джава сначала выжирает всю память, а потом пытается запуститься на остатках, юнити рисует всю сцену по 100500 раз за кадр вместо использования предрендерренной текстурки, а интернет эксплорер 7… Впрочем, не будем издеваться над инвалидами.
Очень не просто с «Сравнением ссылок». В С вы можете легко описать внутренне симметричный (выделенной головы нет) циклический список, по которому и ходить удобно, и проитерировать элементарно, (запоминаем текущий указатель и идём по кругу, пока снова на него не наткнёмся). А вот в нашем блестящем Хаскелле нативный циклический список нельзя взять и полностью проитерировать, — потому что циклический список не отличим от бесконечного, хоть и ленивого. Отсюда сложности описания графов на Хаскелле.
Покоробило, что такие большие иллюстрации сделаны «от балды», то есть, там где зелёный круг не должен быть полностью прикрыт, он закрыт полностью, там где он ещё больше должен торчать — никаких изменений, с фиолетовым тоже не всё в порядке. Неужели три круга тяжело расставить правильно?
С ресурсами современного компьютера мы можем позволить себе язык программирования, дающий ровно один числовой тип.

Какие такие ресурсы? Ваш современный компьютер умеет считать целые и float одинаковым образом? Или сразу всё впихнуть в BigDecimal? Производительности не хватит, поверьте. Даже отсутствие unsigned в Java иногда является ощутимой проблемой.

Когда метод Map возвращает значение — был ли параметр rendition модифицирован?

Вот для этого в С++ есть const. Честно говоря, мне до сих пор неясно, почему эту замечательную фичу не перенесли в более поздние языки.
А разве const в C++ относительно переменных принципиально отличается от const в C# и final в Java? (это я без иронии).
Речь идет, естественно, не о переменных, а о сonst методах и const аргументах функций. Да, в C++ есть способы поломать это и выстрелить себе в ногу, но в более новых языках можно было это пофиксить.
Вообще статья странная, еще чуть чуть и можно скатится до «Зачем в алфавите столько букв, так ведь недолго и ошибку сделать».
У меня тоже ассоциация была с языком.

В наскальной живописи можно было сделать ошибку и нужно было искать новую скалу.

Потом появились языки высокого уровня, но они все не совершенны, ни английский, ни китайский, ни даже русский. Мы имеем много проблем. Я думаю, что нужно начать с того, чтобы убрать грамматику! Тогда мы сразу будем делать на 60% ошибок меньше. Потом пунктуацию, с ней тоже большинство не дружит, а если еще и семантику уберем, то получим вообще идеальный язык!
Даешь новый язык без пережитков старины!!!
В таких спорах всегда вспоминают известный диалект русского языка, отличающийся кратким словарным запасом, развитыми средствами комбинирования и абстракции, истинным полиморфизмом и огромной выразительной мощью
UFO just landed and posted this here
В конце концов мы сделаем мыслепреступление попросту невозможным — для него не останется слов.

Оруэлл уже всё придумал до нас.
Как человек, занимающийся высокопроизводительными вычислениями и параллельным программированием, где нужно максимально выжимать возможности железа, всегда с большим удивлением смотрю на идеи вроде «убрать множественные числовые типы». Конечно, давайте хранить индексы массивов, какие-нибудь типы элементов и т.д. в double :) отличная идея! увеличим требуемый объем оперативной памяти и дополнительно нагрузим каналы передачи данных. И это я всё к тому, что, видимо, зеленый кружок-то побольше должен быть. Конечно, веб-программирования сейчас больше, но ведь и другое на стоит забывать
Для рекламы хватает того, что соавтор Го — Томпсон, а один из соавторов руководства — Керниган. Демиурги.
Поставивший минус не в курсе кто такие Томпсон и Керниган? Или у него свое мнение о том, кто может повести за собой индустрию и заменить наконец псевдо-ассемблер, которым является С, на более прогрессивный язык?
Приветствуем, ardente!

Поздравляем! Теперь вам доступен значок «Отхабренный».

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

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

Сравнение ссылок

Cравнение по значению — это всегда очень медленно.
Так же есть довольно скользкий момент: иногда в объектах есть поля, которые не должны участвовать в сравнении. Например, какие-то служебные данные.
Справедливости ради, идея все-таки сравнивать не по содержимому области памяти, а именно по значению — как определишь оператор == для типа, такие поля и данные и таким именно образом он и будет сравнивать.
По идее для сравнения по значению сначала нужно определить что есть значение. Грубо, написать/переопределить метод value() (привет, js)
Если не переопределять такой оператор, то сравнение будет происходить по диапазону памяти или будет ошибка компиляции?
В первом случае количество ошибок не станет меньше, они просто будут другими. Во втором же случае для любого сравнения нужно будет писать код (тонны бессмысленного кода).
тонны бессмысленного кода

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

Числовые типы

Множество различных видов целого и вещественного чисел — это для динамических языков уже не нужная особенность.

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

Хорошим решением было бы выделить всего 2 класса: Int и Float, которые под капотом были бы представлены массивами. Возможно к этому когда-то придут, но сейчас живем с тем, что есть.
В питоне примерно так и есть — целое и дробное, насчёт тонкостей имплементации врать не буду. Но это питон, он по определению не рассчитан на то, чтобы выжимать каждый байт.
Вот только не Int, а какой-нибудь Decimal co scale по дефолту равным 0. Просто потому что есть копейки, центы, тиыны…
Описанная концепция идеального языка — очень хороша!
Но эта концепция имеет недостаток — она беспощадна к вычислительным ресурсам.

Имутабильность не уменьшит количество ошибок, она переведет их в другую плоскость. И в добавок заставит GC сгореть на работе.
Отсутствие деления целых и вещественных типов по размеру потребует использовать полиномиальные типы данных, а это медленно и очень много памяти. Уровень потребления памяти будет во много раз выше, чем у той же Java, которую за это обычно не любят.
UFO just landed and posted this here
*Поплакал и пошёл дальше писать на asm-е очередную числодробилку. С указателями, переходами и множественными числовыми типами.
Когда писал на асме — вообще думал, что у меня один тип: байт.
Быть может, тогда у тебя только один тип и был.
А как же дополнительный код, для отрицательных чисел? Даже короткий jmp был как вперёд, так и назад.
Это был намёк на разработку под процессоры КР580ВМ80А/Zilog Z80 и их технические ограничения
Ну вообще ещё было DW как минимум и 16-бит арифметика типа DAD B, с использованием в качестве аккумулятора пары HL, и в стэк поместить только int16 можно было. С другой стороны, говорить о типах совсем не приходится: байт — просто минимально адресуемая ячейка ОЗУ, а интерпретация данных в этой ячейке производилась исключительно по исполняемой команде, без какого-либо контроля что туда было положено.
Так и я о Z80, но тогда и двоично-десетичные комманды можно вспомнить и выгрузку в стек 16бит за раз. Как по мне, так если есть узкоспециализированные комманды. то есть разные типы и никто не обещал контроля и логики, если выход одних подавать на вход других, это же асм.
Не бывает флоатов. И вообще ничего, кроме байтов. А бывают наши фантазии как эти байты трактовать. То ли как целые положительные числа, то ли как отрицательные в дополнительном коде, то ли как битовые маски для поразрядных логических операций. И всякие ieee — знак/порядок/мантисса туда же. Все со всем можно делать, любые операции — никаких типов и никакого контроля их (потому что их нет). Про асм речь, есличо.
Я был полностью согласен с этой точкой зрения, но после того, как начал интенсивно использовать SIMD-ы для работы с флоатами, сделал исключение для упрощения восприятия. Хотя это не мешает мне руками ковыряться в значениях флоатов, когда есть такая необходимость. :)
Вы про скольки битное машинное слово?
Пытался понять, что мне не нравится в этой аналогии.

Мне не нравится, что множества валидных (и не валидных) программ конечны.

Если мы сделаем эти множества бесконечными, то картинка сильно изменится. Дано: бесконечное множество валидных программ и бесконечное множество невалидных программ. Мы создаём язык програмирования, который исключает 999 из 1000 невалидных программ.

Ура! У нас образовалось бесконечное множество валидных программ и бесконечное множество невалидных программ.

Мы можем повторять эту операцию сколько угодно (конечное) число раз — соотношение валидных и невалидных программ от этого валидным не станет.
Ну на самом-то деле множества конечны.
Для любой данной программы Pi можно сделать эквивалентную программу Pi+1 добавив nop в начале. Таким образом, для любого множества программ существует множество, превосходящее по размеру данное. В силу индукции, множество получается бесконечным. ∎
UFO just landed and posted this here
Программа, состоящая из зетабайта nop'ов реализуется даже на спектруме — генератор nop'ов и гринлет, их выполняющий.

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

Более того, даже в конечной памяти мы можем говорить о практической бесконечности: если у нас средний размер утилизации бинарного алфабита инструкциями составляет 50%, то двухгигабайтный бинарник (что не так уж много в свете динамических библиотек) даст нам 21073741824 вариантов программ, что примерно 10322122546, что в практических целях может считаться бесконечностью.
Лучшим подходом может быть использование некоторого композитного типа, собирающего в себе информацию об успехе выполнения некоторого блока кода, или ошибках в нём.

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


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

Правда? Нет, серьезно? Как вы будете рационально рассуждать о потреблении памяти программой, написанной на таком языке?


Создайте языке без нулевых указателей и вам никогда не придётся задумываться о генерации или обработке ошибок доступа по нулевому указателю.

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


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

А-га. Вы знаете, да, как различаются в производительности изменяемый и неизменяемый словарь?


Если две переменных указывают на два разных блока памяти (пускай даже заполненных идентичными данными) — они не считаются равными. Это не интуитивно и порождает баги.

Кому это не интуитивно?


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

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


Однако, одно из последствий применения SOLID состоит в том, что вы должны предпочитать интерфейсы, обозначающие какую-то одну роль, а не интерфейсы, включающие наборы методов. Логический вывод из этого — каждый интерфейс должен иметь ровно один метод.

Нет, это вывод не логичен. Роль не обязательно ограничивается отдельной операцией.


Если вы когда-нибудь занимались мета-программирование на .NET или Java, вы, скорее всего, знаете, что такое отражение. Это набор API и возможностей языка программирования или платформы, позволяющий вам извлекать информацию о коде и выполнять его.

Нет, reflection (по крайней мере, в .net) не является возможностью языка, это сугубо возможность платформы. Поменяйте CLR так, чтобы программы стали данными — ничего не изменится.


Но на самом деле, все сравнительно просто: Симан большой любитель функциональных языков и type-driven development. Это интересный подход, но он применим далеко не везде.

Кому это не интуитивно?

Судя по всему — абсолютным новичкам. По моему опыту люди, которые пытались войти в ИТ и при этом не имели какой-либо подготовки, часто очень долго не могли понять как два объекта одинаковые по содержанию не равны между собою по ==.

Описанная автором система программирования — это некий язык, который можно будет подсунуть даже обезьяне и она на нем сможет написать валидную программу. И этому примату тяжело будет понять различие между по-ссылке и по-значению.
Просто людям не объясняли разницу между объектом и ссылкой на него, что в мэйнстрим языках к объекту-то и доступ получить затруднительно, что объекты где-то под копотом создаются, а new возвращает лишь ссылку на объект.
Сравнение содержания объектов и проверка на то, что оба указывают на одну область памяти — это так-то две разные операции, и они (в общем случае) ортогональны. А ежели кому не по силам это понять — да, обезьяна.
Тоже неверно. В языке без нулевых указателей просто появятся другие специальные значения, которые будут приводить к другим ошибкам, но такого же рода. В реальности нужен язык с контрактами и статической их проверкой (null-абилити — это всего лишь частный случай контракта).


Явное указание в типе о том, что значения может не быть (Option) — это и есть решение. Но чтобы оно работало и код гарантированно был null-безопасным, надо убрать поддержку null из ЯП.

Недостаточно убрать поддержку null.


Во-первых, в половине случаев вы просто замените NRE на ArgumentException, или что у нас там Option.get кидает. Стало лучше? Не особо.


Во-вторых, вот вы избавились от null. Что там возвращает String.IndexOf для ненайденного значения? Правильно, -1. Что будет, если написать smth.Substring(smth.IndexOf(Delimiter))? Правильно, потенциально тот же ArgumentException. Что-то изменилось по сравнению с тем, что repository.Find(x) возвращает null, и repository.Find(x).Process() упадет с NRE? Фундаментально — нет, вы все так же получаете run-time ошибку.


Поэтому да, с одной стороны убирание (или явная типизация) null делает программы немножко более очевидными. Но с другой стороны — языку и языковым средствам (в частности, статическим анализаторам контрактов) надо сильно эволюционировать, чтобы это было по-настоящему мощным (а не привело к банальной замене repository.Find(x).Process() на repository.Find(x).Value.Process()).

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

null размывает контракт, делая любое значение потенциально отсутствующим. Option определяет контракт явно, и компиятору не нужно никакого адского статического анализа, чтобы понять, что кто-то делает Option.get без проверки.

С NPE больше всего проблем в больших системах, когда где-то кем-то провалидированные данные с null'ами внутри, успешно пройдя транзитом через несколько границ подсистем и слоев абстракциии, используются в отрыве от их исходного контракта. Option в этом плане сложнее протащить через всю систему насквозь, его место — на границе, а внутри будут циркулировать значения без всяких сюрпризов, о чем явно будет свидетельствовать их тип.
Option определяет контракт явно, и компиятору не нужно никакого адского статического анализа, чтобы понять, что кто-то делает Option.get без проверки.

Так и с null его не нужно. Проблема-то ровно в обратном. Смотрите:


smth |> Option.get

Надо выдавать предупреждение? Наверное, да. А теперь?


if (smth |> Option.isNone) throw;
//40 loc
smth |> Option.get

Вот и с null dereferencing то же самое. Понятно, что с явными null и не-null типами жить немножко легче, но это не фундаментальное изменение.


А главное, что для того, чтобы работали options, нужна сквозная поддержка монад, иначе код становится неудобоваримым. А где монады — там и ФП, и к нему все и приходит в итоге (я уже говорил, что Симан любит ФП).


С NPE больше всего проблем в больших системах, когда где-то кем-то провалидированные данные с null'ами внутри, успешно пройдя транзитом через несколько границ подсистем и слоев абстракциии, используются в отрыве от их исходного контракта.

Статический анализ справляется.

В Javascript есть только один числовой тип, что является отличной идеей. Жаль только, что это неправильный числовой тип
Интересно. А какой с точки зрения Крокфорда правильный тип? Целое или там значение с тегом, или вообще объект?

Изменение значений переменных
Запрет на изменение переменных в пределах текущей области видимости. Но…
Тогда прощай ООП и привет ФП, как я понимаю? Просто такую фичу я только в ФП встречал.
> А какой с точки зрения Крокфорда правильный тип?
Волшебный, размером с char и размерностью с гугол.
> Тогда прощай ООП и привет ФП, как я понимаю?
А разве это не было очевидно изначально? Причём, часть претензий автора вызвана именно что усталостью от неверного или излишнего использования обыкновенных ООПшных и императивных инструментов. И этим грешат не только неопытные программисты, но и вполне весомые дядьки, у которых просто замылился глаз. То, что много чего на чистом ФП описать просто невозможно, статья плавно обходит, не о том она.
> сегодня мы пришли к пониманию того, что Дейкстра был прав

[5 абзацев ни о чем]

> мы уже выяснили, что использование GOTO — это плохо.

А ручки-то вот они.
У меня такое чувство, что комментарии и реплики информативные самой стати. Хотя автор поднял очень актуальную тему.
развенчивающая миф о простоте Хаскелла
сильно!
Картинки больше вводят в заблуждение (потому что ощущение, что соотношения площадей с потолка), хотя основная мысль понятна. Попытка подойти к предмету неправильна в корне. Рассматриваются, назовем так, enterprise-фичи языков с точки зрения (полубытовой?) математики. Эти фичи появились не для того чтобы стрелять в ногу, а для того чтобы программисты могли переиспользовать и дополнять однажды написанное. Я может и соглашусь, что ООП (и энтерпрайз в частности) зашел чересчур далеко в этом. Имхо идеален как раз Javascript.

Я вот могу взять какую-нибудь цитату Энштейна (из гугла) — он «чуть больше программист», чем Экзюпери, например
Господь Бог вычисляет дифференциалы эмпирически
и начать колоть всё программирование как орех. Зачем вообще точные результаты и мат. модели, надо просто положить в железо нечеткие и нейроконтроллеры, а потом их комбинировать, настраивать и обучать — вот и всё программирование, никаких циклов-шмыклов. Ну это я конечно всё утрирую, но сам подход… наукообразный какой-то, ни туда ни сюда.

Вобщем неправильно так делать. Как-будто на автоматных компьютерах нельзя вот в ногу себе выстрелить. Холивар из разряда «дисциплина против дураков»…
Бритва Оккама для языков программирования, по сути. «Не следует множить сущее без необходимости» («Не следует привлекать новые сущности без крайней на то необходимости»).
Спасибо за хорошую статью. Материал отлично структурирован. Правда, к сожалению, в настоящее время во многом преобладают Open Source платформы, где от избыточности никуда не денешься, т.к. там существует несколько различных реализаций одних и тех же функций. Одни хорошие, другие не очень. Хотя это уже избыточность несколько иного характера (высокоуровневая ), но тем не менее это так же является проблемой для разработки и поддержки по.
Sign up to leave a comment.