Используемый генератором стэк уже может не быть виден в основной программе, и лишь указатель на объект-генератор будет жить и передаваться дальше по программе, создавая таким образом два изолированных стэка. Например:
def gen():
for i in range(5):
yield i
raise Exception('test')
def get_gen():
return gen()
def run_gen(obj):
print(list(obj))
run_gen(get_gen())
дает вывод:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in run_gen
File "<stdin>", line 4, in gen
Exception: test
Здесь get_gen отсутствует в стэке исключения, хотя она есть в стэке генератора. При этом, исключение было проброшено в последнюю вызвавшую next() функцию, независимо от того, ждет ли вызывающий исключение или нет. В частности, до PEP 479 это могло приводить к тому, что выход из менеджера контекста никогда не происходил — это было вполне штатным поведением питона. Что есть вполне ожидаемо, поскольку комбинация генераторов, менеджеров контекста, и исключений дает взрывоопасную смесь.
Для виртуальной машины всё просто: исключение надо обрабатывать так, как написано в спецификации языка.
Для библиотеки, являющейся платформой для асинхронности, тоже всё просто: все исключения надо ловить, никаких "вдруг не поймаем"
И как дальше работать после пойманных исключений? Они зачем-то были сгенерированы? Если предложение состоит в том, чтобы отказываться от исключений и обрабатывать ошибочные ситуации альтернативными механизмами, вроде явного возврата ошибки — я только за. Но Гвидо не согласится с моей позицией.
Да, меня предупреждали, что нужно набирать большие сообщения в отдельном текстовом редакторе — но я не послушал, так что теперь ответ будет коротким
критически переосмыслить ядро языка. что не является востребованным или является устаревшим — убрать. ни каких договоренностей, или часть синтаксиса и полноценно поддерживается
Получится новый язык. GDScript и Nim именно так и были сделаны.
основной аргумент Гвидо, против внедрения многопоточности был, как я помню: "невозможно внедрить поддержку многопоточности так, чтобы это не отразилось на скорости однопоточных программ". но, если нельзя сделать одну универсальную программу, может стоит задуматься о двух?
Мы работаем над этим. Гвидо на самом деле говорил о том, что нельзя заменить GIL на множество мелких блокировок, но он забыл сказать, что его интерператор — говно, и для сохранения совместимости со всеми фичами до последней (которой даже сам Гвидо не помнит уже) придется поставить настолько много блокировок, что скорость выполнения недопустимо уменьшится, поскольку блокировки придется ставить на каждый объект и его классы — что бессмысленно с первого взгляда, но если получше присмотерться к реализации, то печальность ситуации становится ясной.
если взяться за реализацию полноценной параллельности/конкурентности, то нужно понять, что это будет, отдельная библиотека, часть синтаксиса, нечто среднее
Пока что библиотека. Сначала сделаю для обычного питона, потом можно будет думать о добавлении быстрого аналога питона. Прецедент двух языков имеется — PyPy. CPython тоже на двух языках писан, просто второй язык (Си) не имеет никакого отношения к питону.
можно попробовать замахнуться на распараллеливание, например, циклов, а не только запуск функций в отдельном потоке
В рамках CPython для этого нет никаких шансов. GDScript, Cython, Numba вполне дают возможность параллелиться.
если подытожить сказанное выше: проект php-ng успешно вылился в обновление языка и это способствовало его развитию и появлению новых возможностей, нужны люди, способные осилить python-ng
Тут еще после тройки у людей тлеют пуканы.
Прошу прощения за много букафф, всех с Новым годом
5 тысяч символов против 35 тысяч в статье и 300 тысяч в обсуждениях — нет, не много.
ABC создавался как язык для обучения. Питон слизал с него солидную долю идей и реализации. Например, парсеры ABC и питона очень похожи, хоть и парсят разные слова.
Я пишу про достаточно сложную программу с побочными эффектами на хаскеле. Обычно программы на хаскеле неизбежно скатывают к упрощению побочных эффектов, и в итоге к ограничению функциональности.
Не поделитесь источником ваших наблюдений?
Мне показалось, что у нас тут никаких противоречий не возникало. Да, разделяют эффекты. Есть примеры хаскелевых программ со сложным состоянием? Нету. Вывод по поводу ограничения функциональности? Это уже я проецирую на те приложения, которые я писал — так получилось, что я очень много писал программ со сложным состоянием (GUI, сеть, БД, файлы) в которых есть мало возможностей просто «сделать отдельный слой», поскольку абстракции не бесплатны, и сидеть абстрагировать десяток разных состояний, а потом интегрировать эти абстракции — это весьма неблагодарная работа.
И какое сложное состояние, которое надо одновременно обрабатывать из разных потоков, имеет nginx?
Я тут не совсем понял, про какие потоки речь. Типа я сказал про то, что процесс nginx многопоточен? Он однопоточен, но одновременно делает прогресс по нескольким независимым задачам, состояние которых, однако, связано намертво механизмом ожидания прогресса, который минимизирует бесполезное использование ресурсов системы. Точно такой же механизм не так давно реализовали в RTS, поскольку в самом хаскеле его реализовать тяжело, а без него ввод-вывод не масштабируется: Mio: A high-performance multicore IO manager for GHC
Так типы-то вам вообще зачем тогда? Сделайте следующий шаг, пишите быстро, чисто, без багов.
Без багов не получится. И поддерживать тяжело будет. Зато быстро и чисто.
Я пишу про достаточно сложную программу с побочными эффектами на хаскеле. Обычно программы на хаскеле неизбежно скатывают к упрощению побочных эффектов, и в итоге к ограничению функциональности.
У вас есть отдельный слой для БД, отдельный слой для парсинга, всё отделено, изолировано на уровне типов, хорошо тестируемо, и так далее
Если это полное описание слоев, то это очень простая система — даже веб сервер, вроде nginx, имеет сложнее состояние, потому что разные состояния обрабатываются одновременно и асинхронно, их нельзя разделить. В программах с более сложным состоянием, вроде GUI, отделение еще более проблематично.
Ведь заниматься архитектурой — это плохо, надо писать быстрее, чтобы код был полапшеобразнее и понеподдерживаемее.
Может быть, я скажу секрет, но можно писать быстро, чисто, без лапши. В большинстве случаев этого хватит, в остальных можно зарефакторить.
На самом деле году в 2016-м в этом виде, а bang patterns там были сколько я себя помню
Bang patterns появились в 6.6, 2007 год. 1991 год — первый релиз GHC. Не, ну че, в отличие от питона он хотя бы развивается, исправляет старые ошибки.
Обычные — это какие? Проверка индексов при индексации? Это не операции хаскеля
Или взять бессмысленное мертворожденное поделие TypeScript. Ой вей. Типы. В js. Дайте две. Зачем? Возьмите Elm, если вам нужны типы, но отстаньте, ради всех святых, от js
TypeScript получил распространение по принципу «лучше чем ничего». Elm, Dart, CoffeeScript, ClojureScript, ReasonML — про них почти никто не знает, потому что в этих языках программист оказывается в чистом поле, без готовых решений, без готовых библиотек, не в состоянии взаимодействовать с кодом на JS без написания большой прослойки.
А есть хотя бы 20 тыс строк императивного кода на хаскеле? Даже с учетом некой предполагаемой экономии строк.
Это точно тот же контроль за корректностью, но уровнем выше. Гарантируемое компилятором отсутствие сетевого взаимодействия, IO, модификации глобального или не очень стейта, чтения из того, что не было объявлено явно как параметр функции, и так далее
Да, в самом деле, зачем какому-нибудь серверу сетевое взаимодействие в своих функциях? Как я уже писал, такая архитектура вынуждает программиста прогибаться, занимаясь архитектурой вместо написания кода, и по мере роста числа побочных эффектов ситуация усугубляется. На самом деле придумано это с простой и сугубо академической целью: сделать язык, который задавал бы соотношения между сущностями, но не порядок выполнения. Соответственно, компилятор должен обладать исчерпывающей информацией о том, что, после чего, в каком контексте вызывать, а фиксированный порядок допустим только на самом высоком уровне иерархии вызовов, аки главный цикл.
Но беда в том, что программисту обычно нужно просто вызвать операции в строгом порядке, а гибкое приложение может быть когда-нибудь будет сделано позже. Идеология хаскеля обращает эти акценты: сначала мы делаем гибкую систему, а потом придумываем, как заставить ее сделать что-то полезное. Я вот не знаю второго такого языка, который бы с ходу давал возможность расширения записи последовательного выполнения команд — эта необходимость у программиста возникает очень редко, но в хаскеле, наоборот, от нее нельзя избавиться.
Ну вот я открыл ipython… А, тьфу ты, у меня его на машине нет. Ну вот я нагуглил справку по ipython. Тут тоже делают какой-то особый язык?
У IPython есть язык, дополнительный к питону, а не подменяющий питон.
Это в каком-то смысле моя ошибка. Можно обойтись без единого bang pattern, просто написав в начале {-# LANGUAGE Strict #-} (но мне лень сделать единственный коммит, да).
Да ладна? Они наконец сделали отключение ленивости в 2019 году. В кои-то века разрабы хаскеля услышали мои молитвы. Осталось только сделать отключаемые монады, и на языке можно будет начинать писать.
Они настолько же unsafe, насколько в C++ являются unsafe вещи типа operator[] у вектора или строки. Просто проверки диапазонов нет.
В узких местах обычные операции хаскеля сильно тормозят выполнение. Да, C++ unsafe by default.
Интересно, что если бы в хаскеле была ещё более мощная система типов, то ансейф бы там не был нужен, можно было бы статически доказать, что индекс всегда корректен.
Мне кажется, что нынешняя система типов хаскеля достаточно сложна, чтобы отпугнуть большинство кодеров. Для ST довольно сложно доказывать корректность индексов, если вспомнить про поддержку многопоточности в хаскеле.
Потому, что они интерпретируемые, а Хаскель — компилируемый, очевидно. Корректный вопрос звучал бы так: «а почему в плюсах / джаве таких проблем с реплом нет?» — oh, wait..
Претензия засчитывается, а по поводу крестов: https://root.cern.ch/cling
50 тысяч строк супротив 4-10 тыс строк GHCi. Я просто пытался найти хотя бы одну софтину, которая могла бы показать возможность писания императивного софта на хаскеле.
Нет, там дело не в чистых и грязных функциях. Там дело в совмещении разных функций, живущих в разных монадах.
И это тоже.
Настолько же, насколько тайпчекер его затрудняет
Да, настолько же. Но проверка типов оправдана преимуществами контроля корректности программы. Слой из mtl не оправдан.
Это синтаксис репла, а не языка. В репле всегда будут команды, которые не имеют смысл в контексте языка. Наличие, скажем, :reload не создаёт же новый язык?
Почему в питоне и лиспе таких проблем с REPL нет? Несмотря на то, что в них тоже фактическое объявление функции/класса в программе нельзя выполнить, не закончив блока описания.
Ну вот я могу по себе судить, считая, что у меня примерно одинаковой кривизны руки что в плюсах, что в хаскеле
И я по себе сужу — просто на крестах писать не умею. И я не замечал никакой катастрофической разницы в кол-ве строк.
В коде по ссылке нет ничего от рейнджей или ленивости. Это предельно тупой и императивный код
Да, действительно. Я слабо знаю кресты, и тригернулся не по делу. Да, прога на хаскеле ненамного сложнее. Хоть и сложнее, все-таки, из-за необходимости bang-ов и unsafe операций.
Это почему?
somethingPure :: Env -> Int
somethingPure env = runReader env foo2
Всё чисто и сухо.
Я так понимаю, имелось в виду «somethingPure env = runReader foo2 env».
Я долго разбирался с mtl, и наконец понял, в чем дело. Они сделали композицию монадных функций для того, чтобы использовать одни и те же инструменты и для чистых, и для грязных функций. То есть, по сути это признание, что идеи разделения чистых и нечистых функций мешают, поскольку имеют различную запись. По сути хаскель пытаются сделать похожим на ML, но с одним важным отличием — возникает промежуточный слой абстракции, который затрудняет написание кода. То есть, вместо «Int -> Int» я должен писать «Reader Int Int», вместо «foo2 env» писать «runReader env foo2», и так далее.
На фоне популяризациия bang notation я могу сделать вывод, что проблемы хаскеля вполне осознаются сообществом, но почему-то некоторые люди продолжают сидеть с покерфейсом, будто ничего не случилось.
К счастью, в хаскеле с композабельностью всё очень хорошо, потому все нужные «ку» давно за вас изолированы в стандартной библиотеке.
Тут вопрос стоит о том, что проще: пользоваться компонуемостью хаскеля, или же взять язык, в которым всё уже есть и те же операции записываются значительно проще.
Пишу сервера на хаскеле, ЧЯДНТ?
Пишу на хаскеле даже очень IO-bound-вещи типа «прожевать лог и посчитать количество случаев, когда время между последовательными записями в логе было меньше микросекунды»
Ничего не могу сказать вслепую. Желательно опираться на опенсорс в таких случаях.
ghci — это кусок ghc, если что
Да, он использует код GHC, но есть вполне четко выделенный код GHCi, в котором в том числе описан отдельный язык, отличающийся от хаскеля.
если вы сделаете :{<Enter>, то перед вами будет многострочный ввод, где можно вводить многострочные биндинги и их группы, и которые достаточно завершить тем же :}. Других важных отличий я сходу не припомню (кроме того, что репл внезапно выполняется в контексте IO со всеми вытекающими)
Да, факт выполнения интерпретируемых команд в контексте IO много кому не нравился, в итоге сделали некоторые поблажки. Да, смайлики «:{» и «:}» помогают, хоть и создают новый язык. Да, больше отличий я не помню — список вполне исчерпывающий.
там есть механизм загрузки модулей из файлов, в конце концов, почему бы не использовать его
К этому я и пришел. Пишу код в текстовом редакторе, загружаю потом из этого файла.
То, что на хаскеле делается в N строк, на плюсах вполне может делаться в 10N строк, а на С — в 20-50N строк (или не делаться вообще, а решаться только кодогенерацией)
Число строк является не совсем удобным мерилом, но лучше ничего не придумали. Нужно допускать много условия для сравнения, но сравнение вполне возможно. Практика показывает, что разница между языками не превышает трех раз: https://habr.com/ru/post/456638/ — Сравнение одинакового проекта в Rust, Haskell, C++, Python, Scala и OCaml
Дальнейший диапазон вызван кривотой рук и объективным усложнением программы.
Вот прям из соседнего треда, давайте сравним тыц и тыц. Я бы не сказал, что одно как-то прицнипиально стремнее другого (и это мне ещё было лень выделять отдельную функцию для цикла, чтобы не дублировать управляющую логику)
Это сравнение lazy-функционального кода на C++ с кодом на хаскеле аналогичного стиля. Императивный код на C/C++ выглядит намного лучше ренжей, почему я и упоминал quicksort.
Помимо того, что FSM удобные инструменты, которые можно реализовать в любом языке, включая Хаскель, есть две парадигмы программирования, построенные на них примерно целиком: автоматное программирование и data driven programming
Ни автоматное программирование, ни data-driven не ограничиваются конечными автоматами (finite state machine, FSM).
А проблема-то тут в чём? Кажется, вы спорите сам с собой...
При желании можно весь компьютер разбить на отдельные ячейки в памяти и назвать их конечными автоматами, как бы не была коротка их жизнь. Но в таком виде их никто не разрабатывает. Разговор изначально был о том, что программисты много где пишут конечные автоматы. Но на самом деле они почти всегда пишут именно бесконечные автоматы — просто находятся люди, которые ищут в их программах конечные автоматы, как последователи фрейдизма ищут во всех мотивах секс.
Но проблема в том, что питон и гора библиотек к нему построены на классах. Если мы говорим "классы в питоноподобном языке не нужны", то мы можем смело сливать питон и все готовые решения, как бесполезное наследие, и тогда статья была ни о чем (а она была про обновление питона с сохранением совместимости).
И cython, и numba быстрее
Они не могут выполнять питон быстрее. Они могут выполнять непитон быстрее, но по выполнению питона они в большинстве случаев медленнее PyPy.
В целом же иммутабельность дает два ощутимых плюса: разработчик не встрянет в то, что вызываемый код полностью переписал окружение
Работа в стиле неизменяемых значений в качестве фундамента полностью переворачивает программу, и имеет мало отношения к разговору о питоне и прочих императивных языках. Этот подход имеет свои плюсы и минусы, и языки без изменяемых данных в итоге оптимизируются для исполнения в изменяемые данные. Напомню мой тезис «в таких случаях есть компиляторы, которые способны самостоятельно выявлять неизменяемые значения».
сборщик мусора (если есть) превращается в параллельный процесс, которому не нужно останавливать все вокруг, чтобы отработать
Не знаю ни одного языка, в котором бы сборщик мусора гарантировал отсутствие остановок. V8, Erlang, JVM — все они требуют остановки выполнения, но есть различия в масштабах остановки. Разве что, можно упомянуть, что в Erlang независимые процессы при сборке мусора останавливаются независимо друг от друга.
Например, я не могу сделать «let line = getLine ++ "\n"», хоть эта операция будет элементарна на любом имеративном языке. Программист не работает с контейнерами — он работает со значениями. Контейнеры — это вторичные сущности. В хаскеле в императивном коде для работы со значениями нужно сделать три раза «ку» даже для самой элементарной операции, вроде «взять значение»: он сначала должен сформировать контейнер, а потом его раскрыть.
Это разные функции с разными типами, могущие жить в разных монадах, и никаких упомянутых вами проблем, что всё должно в одной монаде быть, нет
Да, foo2 полиморфна — нужное ограничение подставляется по месту. Но уже по имеющемуся описанию типов foo2 нельзя будет использовать в чистой функции.
Если мне нужно будет встроить монаду в чистые функции, то у меня будут большие проблемы.
Если мне нужно поменять переменную, объявленную как const, то у меня будут большие проблемы.
Если мне нужно обратиться к инстансу класса Base, как к инстансу класса Derived, если он им не является, то у меня будут большие проблемы
Это выкидывает хаскель из огромного слоя софта, как то пользовательские интерфейсы, сервера, системщина. Что можно вспомнить в качестве контраргумента?
— Darcs? Он появился раньше Git, но Git с легкостью догнал и перегнал Darcs, а потом и mercurial, и bazaar, и до сих пор Darcs не может пойти дальше академического «proof of concept».
— XMonad на полторы тысячи строк? Детский сад. Даже при всей потенциальной способности скукоживать большой код в маленький, нельзя в полторы тысячи строк засунуть любой большой алгоритм.
— GHCi? 4-10 тыс строк (в зависимости от того, что включать в состав). Это единственный хаскелевый софт, которым я пользовался, и у меня к нему есть большие претензии по поводу того, что язык GHCi серьезно отличается от хаскеля, и корректная программа хаскеля не может выполниться вводом через командную строку GHCi.
Я не спорю, что есть несколько вариантов обойти это, как хитрыми объявлениями снаружи, так и грязным хаком unsafePerformIO, но так или иначе
Это не надо обходить
Да, это будет тяготить всю жизнь, нужно просто привыкнуть.
Действительно, есть алгоритмы, которые не очень хорошо ложатся на иммутабельную парадигму (труёвый in-place quicksort, например). Но даже они спокойно реализуются в каком-нибудь ST
Mergesort прекрасно ложится на хаскель, и облагает характеристиками производительности, похожими на quicksort. Но в какой-то момент возникает необходимость таки реализовать императивщину — и нет, quicksort ни разу не спокойно реализуется на каком-нибудь ST, потому что программа получается большая и трудночитаемая, значительно больше и неудобнее аналогичного quicksort на императивных языках.
Они не возвращают MonadIO (это вообще констрейнт). Они возвращают значение, лежащее в некоем «контейнере» m
Тип, класс, ограничение — не важно, важно, что к значению из контейнера m (который на самом деле не контейнер) нельзя обратиться напрямую. MonadIO — это надкласс Monad, потому можно полноправно называть его монадой.
Для foo2 нужно, чтобы m реализовывал какой-то там MonadReader (MonadIO там даже не упоминается)
Не требуется формально, но требуется фактически в рамках foo3, поскольку результат foo2 является результатом foo3. MonadReader так-то работает уже над другими монадами.
monadicFunction = do
res1 <- act1
let pureRes = pureFunc res1
act2 pureRes
и радуетесь себе жизни.
let снимает выполнение с конвеера, то есть, прибегает к методу вложенных функций для сохранения подчинения «монады вызывают чистые функции». Если мне нужно будет встроить монаду в чистые функции, то у меня будут большие проблемы. Я не спорю, что есть несколько вариантов обойти это, как хитрыми объявлениями снаружи, так и грязным хаком unsafePerformIO, но так или иначе — мне нужно будет серьезно запариться, чтобы сделать элементарный алгоритм.
Цель как раз в возможности рассуждать о коде и его эффектах локально
Вот именно. Не писать работающие программы, а рассуждать о коде. Я всеми руками за локальность — я против исключительно двух конкретных свойств хаскеля.
И это позволяет реализовать продакшен-левел-компилятор с кучей фич от системы типов до оптимизации. Вы не находите, что это, гм, весьма хороший результат?
Да, я нахожу, что это удивительный результат для хаскеля, признаю. Должен заметить, однако, что это слабо противоречит моей позиции, потому что в GHC мало побочных эффектов, с которыми начинаются проблемы в виде монад и ленивости.
Понятия не имею, что они там делали, я на их код не смотрел. Однако все серьёзные девелоперы, синьоры, почти прям элита нашей ветви
Ну вот и мне тяжело рассуждать, когда я знаю, что парсер пишется очень быстро, однако же, команда сеньор-элите понадобился целый месяц на выкатку парсера. Где-то здесь сломан телефон.
Аналогичная проблема у вас будет на любом языке, если вы будете делать ленивый ввод/вывод, порядок вызова функций тут ни при чём
Да! Об этом я и пишу. Но дело в том, что в хаскеле, в отличие от многих других языков, эта проблема по умолчанию актуальна — в нем нужно прилагать необычные усилия для реализации обычных алгоритмов.
Тип возвращаемого значения там вообще ни при чём.
Конечно, я имею в виду формальный тип, поскольку запись функций в хаскеле имеет мало общего с реальным выполнением и значениями в памяти.
Нет. Я вполне могу иметь
foo1 :: MonadIO m => Int -> m ()
foo2 :: (MonadReader r m, Has Env r) => m Int
foo3 :: (MonadIO m, MonadReader r m, Has Env r) => m ()
Я с mtl не знаком, потому не понял. Насколько я вижу, здесь и foo1, и foo2, и foo3 возвращают MonadIO.
Не надо там ничего помнить, там даже синтаксис разный. do-нотация для монад, обычные байндинги для всего остального
Ну, да. Я написал чистые функции, я не могу их просто так использовать в монадах. Или я написал функции для монад, и теперь я не могу просто комбинировать их вне монады. Такие условия заставляют организовывать программу в «монады вызывают чистые функции», что, конечно, можно нарушить при помощи какого-нибудь unsafePerformIO, но тогда зачем такую структуру было городить изначально? Как и в случае с ленивостью, это чистота языка, абстрактные цели взамен практического результата — в итоге эти две особенности гарантируют, что хаскель не окажет значимого влияния на прикладное кодописание.
Гм, интересно, сколько тысяч строк кода в ghc?
400 тыс строк хаскеля всего, из них 300 тыс строк в компиляторе; 75 тыс строк на C/C--, в основном базовые библиотеки и RTS.
Соседняя команда из пяти человек что-то на плюсах пилила месяц, выдала неработающий прототип парсера на плюсах на тридцать тыщ строк и сказала, что на завершение проекта им нужно ещё 9 месяцев
Про TCP прочитал ваш текст несколько раз, так и не увидел проблему. Таблица состояний протокола известна и весьма понятна. Автомат — штука простая и легковесная.
Обозначьте проблему
Коротко могу описать проблему так: протокол TCP можно реализовать на бесконечном числе конечных автоматов (соответственно числу соединений); конечным автоматом можно представить состояние единственного соединения на протяжении его жизни.
Я говорил про классические регулярки, которыми все пользуются для типовых задач. Про экзотические случаи я ничего не говорил
Ну да, каноничные регулярные выражения по своим определениям влазят в конечный автомат, спорить не буду.
Не, это поломанный поиск пути, он не имеет к автоматам никакого отношения
Он может застрять на изначально проходимом участке, потому что окружение изменилось, а он не смог отреагировать, потому что у него ограниченная логика из-за ограниченности конечного автомата.
Автомат должен только определять, в каком режиме находится моб: патрулирует, отдыхает, преследует, бой с одним игроком, бой с несколькими игроками, бой на пороге смерти. Сами алгоритмы автомат же не реализует, скриптование в любом случае нужно
Когда большая и сложная система анализа и принятия решения будет реализована, то, вполне возможно, что где-то там внутри можно будет найти конечный автомат. Я бы сказал, что в составе сложных программ ситуация временного выполнения функции конечного автомата отдельной частью весьма распространена. За каноничными примерами далеко не нужно ходить: стэковая машина, автомат с магизанной памятью, машина Тьюринга, в конце-концов — это устройства, которые в своем составе содержат конечный автомат, к которому добавлено бесконечное состояние.
OpenAI — это тот, который всех в доту нагибал?
Да, это тот, который показывали только в условиях полного превосходства над человеком, при больших ограничениях на возможности последних.
Какие люди? Что будут делать? Вы с ем сейчас разговаривали-то? При чем тут вообще и так иммутабельные фактически везде integers?
Очевидно, те люди, которых устраивает концепция формализации полной неизменяемости. Одна из первых вещей, которую делают програмисты, работающие с неизменяемыми значениями — это изменяют их, создают производные значения. В их коде на самом деле намного больше констант, чем явных объявлений констант, и вышеприведенный мной код на самом деле должен был бы содержать
но программисту обычно есть чем заниматься, кроме как жанглировать модификаторами изменяемости, особенно если речь идет о неявных промежуточных значениях. В таких случаях есть компиляторы, которые способны самостоятельно выявлять неизменяемые значения.
У программиста есть простые цели: изменять значения, когда нужно их изменять, и не изменять их, когда нужно их не изменять. Вчерашняя константа завтра может оказаться переменной. В случае с питоном программист не может быть полностью уверен в том, что написанный им код не изменит случайно связанное значение. Предлагаю угадать, что сделают эти строчки кода:
l = data[0]
l += (2, )
Чур не подсматривать в остальной код — в нём есть только самые примитивные типы и операции питона.
В дополнение к этому примеру я предлагаю переписать строчки Zen of Python с «Explicit is better than implicit. Simple is better than complex» на новый, более приближенный к реалиям мотив: «Some things are implicit and ambiguous — simply learn by trial and guess».
Используемый генератором стэк уже может не быть виден в основной программе, и лишь указатель на объект-генератор будет жить и передаваться дальше по программе, создавая таким образом два изолированных стэка. Например:
дает вывод:
Здесь get_gen отсутствует в стэке исключения, хотя она есть в стэке генератора. При этом, исключение было проброшено в последнюю вызвавшую next() функцию, независимо от того, ждет ли вызывающий исключение или нет. В частности, до PEP 479 это могло приводить к тому, что выход из менеджера контекста никогда не происходил — это было вполне штатным поведением питона. Что есть вполне ожидаемо, поскольку комбинация генераторов, менеджеров контекста, и исключений дает взрывоопасную смесь.
Я бы напомнил, что у питона не было никакой спецификации — её уже писали и дорабатывали пост-фактум. В частности, можно обратить внимание на саму спецификацию обработки исключений в генераторах, поскольку она таки менялась и имела проблемы:
https://www.python.org/dev/peps/pep-0342/#specification-exceptions-and-cleanup
https://www.python.org/dev/peps/pep-0479/#examples-of-breakage
И как дальше работать после пойманных исключений? Они зачем-то были сгенерированы? Если предложение состоит в том, чтобы отказываться от исключений и обрабатывать ошибочные ситуации альтернативными механизмами, вроде явного возврата ошибки — я только за. Но Гвидо не согласится с моей позицией.
Да, меня предупреждали, что нужно набирать большие сообщения в отдельном текстовом редакторе — но я не послушал, так что теперь ответ будет коротким
Получится новый язык. GDScript и Nim именно так и были сделаны.
Мы работаем над этим. Гвидо на самом деле говорил о том, что нельзя заменить GIL на множество мелких блокировок, но он забыл сказать, что его интерператор — говно, и для сохранения совместимости со всеми фичами до последней (которой даже сам Гвидо не помнит уже) придется поставить настолько много блокировок, что скорость выполнения недопустимо уменьшится, поскольку блокировки придется ставить на каждый объект и его классы — что бессмысленно с первого взгляда, но если получше присмотерться к реализации, то печальность ситуации становится ясной.
Пока что библиотека. Сначала сделаю для обычного питона, потом можно будет думать о добавлении быстрого аналога питона. Прецедент двух языков имеется — PyPy. CPython тоже на двух языках писан, просто второй язык (Си) не имеет никакого отношения к питону.
В рамках CPython для этого нет никаких шансов. GDScript, Cython, Numba вполне дают возможность параллелиться.
Тут еще после тройки у людей тлеют пуканы.
5 тысяч символов против 35 тысяч в статье и 300 тысяч в обсуждениях — нет, не много.
Я тут не совсем понял, про какие потоки речь. Типа я сказал про то, что процесс nginx многопоточен? Он однопоточен, но одновременно делает прогресс по нескольким независимым задачам, состояние которых, однако, связано намертво механизмом ожидания прогресса, который минимизирует бесполезное использование ресурсов системы. Точно такой же механизм не так давно реализовали в RTS, поскольку в самом хаскеле его реализовать тяжело, а без него ввод-вывод не масштабируется:
Mio: A high-performance multicore IO manager for GHC
Без багов не получится. И поддерживать тяжело будет. Зато быстро и чисто.
Я пишу про достаточно сложную программу с побочными эффектами на хаскеле. Обычно программы на хаскеле неизбежно скатывают к упрощению побочных эффектов, и в итоге к ограничению функциональности.
Если это полное описание слоев, то это очень простая система — даже веб сервер, вроде nginx, имеет сложнее состояние, потому что разные состояния обрабатываются одновременно и асинхронно, их нельзя разделить. В программах с более сложным состоянием, вроде GUI, отделение еще более проблематично.
Может быть, я скажу секрет, но можно писать быстро, чисто, без лапши. В большинстве случаев этого хватит, в остальных можно зарефакторить.
Bang patterns появились в 6.6, 2007 год. 1991 год — первый релиз GHC. Не, ну че, в отличие от питона он хотя бы развивается, исправляет старые ошибки.
Да, это операции стандарных массивов:
https://hackage.haskell.org/package/array-0.5.4.0/docs/src/Data.Array.Base.html
TypeScript получил распространение по принципу «лучше чем ничего». Elm, Dart, CoffeeScript, ClojureScript, ReasonML — про них почти никто не знает, потому что в этих языках программист оказывается в чистом поле, без готовых решений, без готовых библиотек, не в состоянии взаимодействовать с кодом на JS без написания большой прослойки.
Да, в самом деле, зачем какому-нибудь серверу сетевое взаимодействие в своих функциях? Как я уже писал, такая архитектура вынуждает программиста прогибаться, занимаясь архитектурой вместо написания кода, и по мере роста числа побочных эффектов ситуация усугубляется. На самом деле придумано это с простой и сугубо академической целью: сделать язык, который задавал бы соотношения между сущностями, но не порядок выполнения. Соответственно, компилятор должен обладать исчерпывающей информацией о том, что, после чего, в каком контексте вызывать, а фиксированный порядок допустим только на самом высоком уровне иерархии вызовов, аки главный цикл.
Но беда в том, что программисту обычно нужно просто вызвать операции в строгом порядке, а гибкое приложение может быть когда-нибудь будет сделано позже. Идеология хаскеля обращает эти акценты: сначала мы делаем гибкую систему, а потом придумываем, как заставить ее сделать что-то полезное. Я вот не знаю второго такого языка, который бы с ходу давал возможность расширения записи последовательного выполнения команд — эта необходимость у программиста возникает очень редко, но в хаскеле, наоборот, от нее нельзя избавиться.
У IPython есть язык, дополнительный к питону, а не подменяющий питон.
Да ладна? Они наконец сделали отключение ленивости в 2019 году. В кои-то века разрабы хаскеля услышали мои молитвы. Осталось только сделать отключаемые монады, и на языке можно будет начинать писать.
В узких местах обычные операции хаскеля сильно тормозят выполнение. Да, C++ unsafe by default.
Мне кажется, что нынешняя система типов хаскеля достаточно сложна, чтобы отпугнуть большинство кодеров. Для ST довольно сложно доказывать корректность индексов, если вспомнить про поддержку многопоточности в хаскеле.
Претензия засчитывается, а по поводу крестов:
https://root.cern.ch/cling
50 тысяч строк супротив 4-10 тыс строк GHCi. Я просто пытался найти хотя бы одну софтину, которая могла бы показать возможность писания императивного софта на хаскеле.
И это тоже.
Да, настолько же. Но проверка типов оправдана преимуществами контроля корректности программы. Слой из mtl не оправдан.
Почему в питоне и лиспе таких проблем с REPL нет? Несмотря на то, что в них тоже фактическое объявление функции/класса в программе нельзя выполнить, не закончив блока описания.
И я по себе сужу — просто на крестах писать не умею. И я не замечал никакой катастрофической разницы в кол-ве строк.
Да, действительно. Я слабо знаю кресты, и тригернулся не по делу. Да, прога на хаскеле ненамного сложнее. Хоть и сложнее, все-таки, из-за необходимости bang-ов и unsafe операций.
Я так понимаю, имелось в виду «somethingPure env = runReader foo2 env».
Я долго разбирался с mtl, и наконец понял, в чем дело. Они сделали композицию монадных функций для того, чтобы использовать одни и те же инструменты и для чистых, и для грязных функций. То есть, по сути это признание, что идеи разделения чистых и нечистых функций мешают, поскольку имеют различную запись. По сути хаскель пытаются сделать похожим на ML, но с одним важным отличием — возникает промежуточный слой абстракции, который затрудняет написание кода. То есть, вместо «Int -> Int» я должен писать «Reader Int Int», вместо «foo2 env» писать «runReader env foo2», и так далее.
На фоне популяризациия bang notation я могу сделать вывод, что проблемы хаскеля вполне осознаются сообществом, но почему-то некоторые люди продолжают сидеть с покерфейсом, будто ничего не случилось.
Тут вопрос стоит о том, что проще: пользоваться компонуемостью хаскеля, или же взять язык, в которым всё уже есть и те же операции записываются значительно проще.
Ничего не могу сказать вслепую. Желательно опираться на опенсорс в таких случаях.
Да, он использует код GHC, но есть вполне четко выделенный код GHCi, в котором в том числе описан отдельный язык, отличающийся от хаскеля.
Да, факт выполнения интерпретируемых команд в контексте IO много кому не нравился, в итоге сделали некоторые поблажки. Да, смайлики «:{» и «:}» помогают, хоть и создают новый язык. Да, больше отличий я не помню — список вполне исчерпывающий.
К этому я и пришел. Пишу код в текстовом редакторе, загружаю потом из этого файла.
Число строк является не совсем удобным мерилом, но лучше ничего не придумали. Нужно допускать много условия для сравнения, но сравнение вполне возможно. Практика показывает, что разница между языками не превышает трех раз:
https://habr.com/ru/post/456638/ — Сравнение одинакового проекта в Rust, Haskell, C++, Python, Scala и OCaml
Дальнейший диапазон вызван кривотой рук и объективным усложнением программы.
Это сравнение lazy-функционального кода на C++ с кодом на хаскеле аналогичного стиля. Императивный код на C/C++ выглядит намного лучше ренжей, почему я и упоминал quicksort.
Ни автоматное программирование, ни data-driven не ограничиваются конечными автоматами (finite state machine, FSM).
При желании можно весь компьютер разбить на отдельные ячейки в памяти и назвать их конечными автоматами, как бы не была коротка их жизнь. Но в таком виде их никто не разрабатывает. Разговор изначально был о том, что программисты много где пишут конечные автоматы. Но на самом деле они почти всегда пишут именно бесконечные автоматы — просто находятся люди, которые ищут в их программах конечные автоматы, как последователи фрейдизма ищут во всех мотивах секс.
Но проблема в том, что питон и гора библиотек к нему построены на классах. Если мы говорим "классы в питоноподобном языке не нужны", то мы можем смело сливать питон и все готовые решения, как бесполезное наследие, и тогда статья была ни о чем (а она была про обновление питона с сохранением совместимости).
Они не могут выполнять питон быстрее. Они могут выполнять непитон быстрее, но по выполнению питона они в большинстве случаев медленнее PyPy.
Работа в стиле неизменяемых значений в качестве фундамента полностью переворачивает программу, и имеет мало отношения к разговору о питоне и прочих императивных языках. Этот подход имеет свои плюсы и минусы, и языки без изменяемых данных в итоге оптимизируются для исполнения в изменяемые данные. Напомню мой тезис «в таких случаях есть компиляторы, которые способны самостоятельно выявлять неизменяемые значения».
Не знаю ни одного языка, в котором бы сборщик мусора гарантировал отсутствие остановок. V8, Erlang, JVM — все они требуют остановки выполнения, но есть различия в масштабах остановки. Разве что, можно упомянуть, что в Erlang независимые процессы при сборке мусора останавливаются независимо друг от друга.
Да, foo2 полиморфна — нужное ограничение подставляется по месту. Но уже по имеющемуся описанию типов foo2 нельзя будет использовать в чистой функции.
Это выкидывает хаскель из огромного слоя софта, как то пользовательские интерфейсы, сервера, системщина. Что можно вспомнить в качестве контраргумента?
— Darcs? Он появился раньше Git, но Git с легкостью догнал и перегнал Darcs, а потом и mercurial, и bazaar, и до сих пор Darcs не может пойти дальше академического «proof of concept».
— XMonad на полторы тысячи строк? Детский сад. Даже при всей потенциальной способности скукоживать большой код в маленький, нельзя в полторы тысячи строк засунуть любой большой алгоритм.
— GHCi? 4-10 тыс строк (в зависимости от того, что включать в состав). Это единственный хаскелевый софт, которым я пользовался, и у меня к нему есть большие претензии по поводу того, что язык GHCi серьезно отличается от хаскеля, и корректная программа хаскеля не может выполниться вводом через командную строку GHCi.
Да, это будет тяготить всю жизнь, нужно просто привыкнуть.
Mergesort прекрасно ложится на хаскель, и облагает характеристиками производительности, похожими на quicksort. Но в какой-то момент возникает необходимость таки реализовать императивщину — и нет, quicksort ни разу не спокойно реализуется на каком-нибудь ST, потому что программа получается большая и трудночитаемая, значительно больше и неудобнее аналогичного quicksort на императивных языках.
Тип, класс, ограничение — не важно, важно, что к значению из контейнера m (который на самом деле не контейнер) нельзя обратиться напрямую. MonadIO — это надкласс Monad, потому можно полноправно называть его монадой.
Не требуется формально, но требуется фактически в рамках foo3, поскольку результат foo2 является результатом foo3. MonadReader так-то работает уже над другими монадами.
let снимает выполнение с конвеера, то есть, прибегает к методу вложенных функций для сохранения подчинения «монады вызывают чистые функции». Если мне нужно будет встроить монаду в чистые функции, то у меня будут большие проблемы. Я не спорю, что есть несколько вариантов обойти это, как хитрыми объявлениями снаружи, так и грязным хаком unsafePerformIO, но так или иначе — мне нужно будет серьезно запариться, чтобы сделать элементарный алгоритм.
Вот именно. Не писать работающие программы, а рассуждать о коде. Я всеми руками за локальность — я против исключительно двух конкретных свойств хаскеля.
Да, я нахожу, что это удивительный результат для хаскеля, признаю. Должен заметить, однако, что это слабо противоречит моей позиции, потому что в GHC мало побочных эффектов, с которыми начинаются проблемы в виде монад и ленивости.
Да! Об этом я и пишу. Но дело в том, что в хаскеле, в отличие от многих других языков, эта проблема по умолчанию актуальна — в нем нужно прилагать необычные усилия для реализации обычных алгоритмов.
Конечно, я имею в виду формальный тип, поскольку запись функций в хаскеле имеет мало общего с реальным выполнением и значениями в памяти.
Я с mtl не знаком, потому не понял. Насколько я вижу, здесь и foo1, и foo2, и foo3 возвращают MonadIO.
Ну, да. Я написал чистые функции, я не могу их просто так использовать в монадах. Или я написал функции для монад, и теперь я не могу просто комбинировать их вне монады. Такие условия заставляют организовывать программу в «монады вызывают чистые функции», что, конечно, можно нарушить при помощи какого-нибудь unsafePerformIO, но тогда зачем такую структуру было городить изначально? Как и в случае с ленивостью, это чистота языка, абстрактные цели взамен практического результата — в итоге эти две особенности гарантируют, что хаскель не окажет значимого влияния на прикладное кодописание.
400 тыс строк хаскеля всего, из них 300 тыс строк в компиляторе; 75 тыс строк на C/C--, в основном базовые библиотеки и RTS.
Эм-м-м… Рекурсивный спуск пишется отвратительно быстро (я сам писал), а в 30 тыс строк можно уместить компилятор какого-нибудь Си. В TCC, например, 15 тыс строк:
http://download.savannah.gnu.org/releases/tinycc/
Может, они писали что-то в таком духе:
https://habr.com/ru/post/481782/#comment_21061710
Коротко могу описать проблему так: протокол TCP можно реализовать на бесконечном числе конечных автоматов (соответственно числу соединений); конечным автоматом можно представить состояние единственного соединения на протяжении его жизни.
Ну да, каноничные регулярные выражения по своим определениям влазят в конечный автомат, спорить не буду.
Он может застрять на изначально проходимом участке, потому что окружение изменилось, а он не смог отреагировать, потому что у него ограниченная логика из-за ограниченности конечного автомата.
Когда большая и сложная система анализа и принятия решения будет реализована, то, вполне возможно, что где-то там внутри можно будет найти конечный автомат. Я бы сказал, что в составе сложных программ ситуация временного выполнения функции конечного автомата отдельной частью весьма распространена. За каноничными примерами далеко не нужно ходить: стэковая машина, автомат с магизанной памятью, машина Тьюринга, в конце-концов — это устройства, которые в своем составе содержат конечный автомат, к которому добавлено бесконечное состояние.
Да, это тот, который показывали только в условиях полного превосходства над человеком, при больших ограничениях на возможности последних.
Очевидно, те люди, которых устраивает концепция формализации полной неизменяемости. Одна из первых вещей, которую делают програмисты, работающие с неизменяемыми значениями — это изменяют их, создают производные значения. В их коде на самом деле намного больше констант, чем явных объявлений констант, и вышеприведенный мной код на самом деле должен был бы содержать
но программисту обычно есть чем заниматься, кроме как жанглировать модификаторами изменяемости, особенно если речь идет о неявных промежуточных значениях. В таких случаях есть компиляторы, которые способны самостоятельно выявлять неизменяемые значения.
У программиста есть простые цели: изменять значения, когда нужно их изменять, и не изменять их, когда нужно их не изменять. Вчерашняя константа завтра может оказаться переменной. В случае с питоном программист не может быть полностью уверен в том, что написанный им код не изменит случайно связанное значение. Предлагаю угадать, что сделают эти строчки кода:
Чур не подсматривать в остальной код — в нём есть только самые примитивные типы и операции питона.
В дополнение к этому примеру я предлагаю переписать строчки Zen of Python с «Explicit is better than implicit. Simple is better than complex» на новый, более приближенный к реалиям мотив: «Some things are implicit and ambiguous — simply learn by trial and guess».