А вы думали, что сразу разберётесь в мегабайтах документации? Что бы найти нужное место требуется определённый опыт, и по работе с БД и по самой документации.
А вы целиком понимаете, как ваш код на вашем любимом императивном языке программирования исполняется на процессоре?
У меня там не возникало вопросов вроде определения шагов для вычисления ленивого списка. А когда что-то непонятно, то всегда есть довольно простые гипотезы о соответствии моего кода и скомпилированного. Пробуем гипотезу, меряем время/память, выясняем что подходит.
А вот в хаскеле с тем же ленивым списком, если предположить, что вместо списка создаётся структура, содержащая описание вычисления, то тогда получится, что на императивном языке я могу создать в десятки раз больше списков, потому что там нет таких затрат по памяти. То есть неочевидность здесь сложнее выявить и если гипотеза верна — явно имеем большой расход памяти.
Я его вынес вне цикла, и код ускорился условно с 260 мс до 255 мс.
То есть у вас претензии к потере 5мс из 260? Два процента. Это явно излишняя оптимизация (в большинстве случаев). Тратить время на это нехорошо, потому что есть более важные задачи (обычно).
А по ассемблеру — если уж совсем по мелочам важно всё отследить, то лучше ассемблерные вставки делать. Там контроль полный и опять ненужно ничего декомпилировать.
Ну это уже отписка. И нельзя сравнивать документацию по функциям с документацией по алгоритмам, используемым БД. Функции — это лишь малая часть документации по БД.
Например на официальной вики? Про то как работает непосредственно вычисление можно почитать по ссылке WFNF.
Посмотрел, но там же стандартные пояснения про тривиальные случаи. Это и так понятно. Мне же хотелось понять, как конкретно увязаны неизменность списка с ленивостью его заполнения. Из стандартного объяснения про сохранение вычисления и его реальную обработку «когда надо» непонятно как же обрабатывается ленивый список из функции Фибоначчи. То есть «реально надо», когда я прошу дать мне первый элемент, а потом «реально надо», когда прошу дать второй, и т.д. На котором шаге что считается? Где неизменность списка? Где смысл функции, возвращающей всегда один и тот же результат при неизменных аргументах? Как это всё увязывается в реальности?
В императивной программе точно так же надо смотреть на ассемблерные листинги и что там получилось.
В С есть так называемые intrinsics, которые напрямую транслируются в ассемблерный код. Оптимизация выполняется именно с их помощью. И при этом нет никакой необходимости смотреть «что там получилось» в ассемблерных листингах.
В языках с байткодом такая же ситуация, точнее там вообще нельзя посмотреть такой листинг, ну и в том числе поэтому и не смотрят.
А вот хаскель компилируется в машинный код и его тут недавно (была статья) декомпилировали именно для понимания, а что же он там делает.
Если это считать деталями алгоритма, то любой фпшный fmap это тоже «детали алгоритма», которые можно так же посмотреть.
А где вы смотрите детали? По БД есть очень хорошая документация, а вот по хаскелю — надо всё искать (если что-то сложнее основ). Вплоть до изучения кода компилятора.
Как работает ленивость? Автор статьи упомянул ленивое вычисление списка чисел Фибоначчи — как этот список получается (императивно)? Где искать?
Не вижу принципиальной разницы. Смотреть план запроса или в вывод гхц.
Но в обычной программе (императивной) вообще нет необходимости смотреть какие-то планы/выводы. Точнее в большинстве случаев достаточно просто подумать.
Как я понял, там применяется смесь из нейросетей с обработкой на основе различных эвристик. Как-то можно классифицировать эти эвристики? Например был шум на тему semantic web, что-то подобное применяется при переводе? По логике там ему самое место, но по факту там рулит статистика и нейросети. Ну и может что-то ещё.
А если автору не трудно (пусть в виде рекламы своей компании), было бы интересно почитать статью именно про технологическую часть.
И кстати, раз уж статья целится на поиск знатоков NLP, то интересно, а какие задачи они будут решать? Ведь основа давно задана, никакие вольности в виде исследований не позволительны (ограничение по деньгам), и по сути получается, что нужны просто знакомые с используемыми в предметной области терминологией и алгоритмами люди, что бы им поставить задачу, ну и они её пилили как в обычных программистских конторах. Может даже вообще есть смысл иметь лишь одного эксперта, а всё остальное поручать гораздо более дешёвым помощникам, которым просто спускается план действий, может с какими-то пояснениями, мол вот с этого сайта текст надо скормить этой приблуде, она посчитает статистику, потом результат сохранится сюда, а ты выберешь из статистики такие-то части и включишь в программу для Андроида (в её базу данных).
И опять кстати, программа для Андроида имеет всего 1000 установок и непонятно сколько из них активны. Как-то мало.
У нас написано SELECT * FROM A JOIN B ON A.Id = B.EntityId, а там то ли цикл, то ли хэшмапа создалась, то ли сортировка началась.
Цикл или сортировка — внутренние сложности алгоритма нахождения соответствия записей из А и В. Абстрактный же алгоритм очень прост. Но если мне надо, то я легко найду внутренние детали реализации. Вот в этом простота.
Не знаю, что там штурмовать: сказали в курсе, что есть функция a -> m b, и если у тебя есть f a то можешь получить f b. Показали пример со списком, опциональным значением и футурой, всё стало понятно.
У меня был очень простой вопрос — а зачем так? Вы же целую статью на эту тему написали, объясняя зачем. И там много комментариев со спорами.
Ну а поверхностно понять, что «на входе Х, на выходе У» — это конечно легко.
Когда нужно — быстренько выучивают все, что надо
В случае с БД — да, там всё просто. А в случае с хаскелем — я бы так не сказал. Для оптимизации хаскель-программ используют опции, указывающие компилятору, где и как что-то делать, что бы он не нафантазировал чего-то непредвиденного. Но это непредвиденное, и даже место, где оно возникнет, нужно же понимать. Вот это и есть сложность. Она выше сложности БД. В БД может поменяться алгоритм сортировки или джойна, но концептуально это ничего не меняет, а вот в хаскеле указания компилятору меняют смысл программы (с ленивой на неленивую, например).
И что это докажет?
Докажет разницу в сложности. Большинство знающих хаскель легко разберутся с SQL, но большинству знающих SQL придётся нелегко, когда нужно будет разбираться с хаскелем.
Немного холодного душа для тех, кто пишет «ах какая мотивирующая статья!».
Для занятия бизнесом нужно очень хотеть заработать. То есть в обмен на все остальные удовольствия вы должны научиться извлекать приятное из очень затратной по времени и скучной работы по выявлению интересов абстрактного клиента и выбору методов удовлетворения этих интересов на уровне, сравнимом с конкурентами. Ну и без гарантий, разумеется. Точнее даже с гарантией того, что даже просто на самоокупаемость выйти будет трудно (а многим — невозможно).
При этом, разумеется, вы должны забыть о разного рода копании в алгоритмах, создании сложных систем и т.д. Это всё должны делать другие, потому что у вас не будет на это времени. Поэтому ваш путь пройдёт через open-source и прочие готовые/покупные технологии. Сами вы ничего разрабатывать не будете. А если будете — потеряете бизнес.
Но, конечно, пока не попробуешь — не поймёшь, так что — попробуйте, проверьте, ну вдруг вы попадёте в волну (одну на миллион) и вам ничего из указанного делать не придётся. А если не попадёте — вас ждёт довольно скучная (на мой взгляд) работа. Но может вам она покажется ближе, чем мне.
мне кажется, все таки употреблять термин «константа» (лат. constans, родительный падеж constantis — постоянный, неизменный) в таком смысле не правильно. В таком случае main в хаскелле тоже константа. То есть любая программа — константа.
Main общается с монадами, которые прячут внешние эффекты, а значит main не может быть константой из-за этих самых эффектов.
Я понимаю ваше неоднозначное восприятие программы примерно так — раз программа что-то выполняет, значит она не постоянная/неизменная. Но здесь уже получится скорее философский разговор, ведь, например, наличие движения в мире для нас — константа. Движение есть, всё меняется, но это всё присутствует постоянно и неизменно повторяется.
Можно ещё сказать, что константа (условно) даёт постоянный адрес в памяти, где лежит тот самый список. То есть вот вам постоянство и неизменность. А то, что сам список меняется (хотя как конкретно он меняется — не очень понятно, может даже вообще не меняется, а сразу получается путём копирования из стека вычисления всего того, что насчитали к моменту востребования списка) — к константе не имеет отношения, ведь адрес же не меняется.
Но ведь и ваше «императивное» понимание в определенной мере иллюзорно
Да иллюзорно, конечно, я не спорю, ведь регистры процессора я точно себе не представляю во время написания алгоритма, но важно отличать доступную сложность абстракции от условно недоступной. Доступная — это когда мне легко понять, почему вдруг код не работает. А недоступная — это когда я «смотрю в книгу и вижу фигу», то есть долго думаю над произошедшим. Ну и поэтому я предпочитаю понятные абстракции. А понятность как раз и связана с моей возможностью проследить алгоритм, если потребуется, хоть до конкретного регистра процессора. На этом строится вся оптимизация. А если я не знаю, как оно там внутри вертится, то и оптимизировать не смогу.
При программировании в императивном стиле тоже сколько угодно случаев, когда понимание того, как сделать правильно/эффективно подводит.
В данном случае «сколько угодно» меньше, чем это же «сколько угодно», но в случае с функциональным стилем изложения алгоритма. Проверено миллионами программистов, почему-то не пожелавших изучать хаскель, но легко осваивающих какой-нибудь питон.
Абстракции бывают разные. Абстракция SQL, как минимум для меня, очень понятна и я хорошо понимаю, как она ложится на императивно выражаемые алгоритмы. А вот абстракции из хаскеля я не все до такой степени понимаю. Возможно, это следствие отсутствия длительного периода использования хаскеля, ведь я с ним познакомился, поигрался, да и забыл о нём.
Но тем не менее, вспоминая те времена, когда я впервые знакомился с SQL, я отлично помню, что базовые абстракции там дались мне сразу и легко, а вот ту же абстракцию монад мне пришлось штурмовать гораздо дольше.
Ну и если посмотреть на других, то по ним видно, что оптимизацию запросов SQL осиливают далеко не все, что означает и в данном случае наличие заметной сложности. Хотя монады из тех, кто хорошо освоил SQL, так же понимает меньшинство. Правда моя статистика, понятно, субъективна. Но можно спросить на любом форуме по базам данных — кто из вас вообще знает про хаскель? А кто понимает вот этот код… и привести что-то с монадами. Ответ, мне кажется, будет соответствовать моей статистике.
Хотя сам несколько зациклился — вроде нужны абстракции, но понимать хочу императивно, то есть без абстракций. Здесь видимо нужен компромисс. И этот компромисс проще находить в императивном выражении, когда уровень абстракций находится недалеко от уровня исполнения. А когда компилятор за меня додумывает массу деталей, вот тогда могут начаться пляски с бубном.
Под константой я понимаю выражение, определение значения которого не требует вычислений. В данном случае определение значения fibonachi вычислений требует и потому константой не является.
Я как раз и оперирую только абстрактными понятиями.
А откуда же берутся значения констант? Их кто-то обязательно вычисляет, самый минимум — переводит текст в число.
После декларации константы идёт функция, которая как раз и обязана вычислить значение константы. При этом слегка туманит ситуацию ленивость, но константе-то без разницы, ведь она просто показывает нам список, хоть и пополняемый по мере надобности. А ещё неизменность списка сложно воспринимается. Но это всё относится именно к области действия той функции, которая вычисляет значение константы.
Так в том и прикол, что если интересует только конечный результат, то вам этот порядок до фонаря.
Мне не до фонаря. Я предпочитаю понимать, именно императивно, что там происходит. Потому что если понимать поверхностно — всегда найдётся случай, когда «понимание» оказывается непониманием.
В ведь понимаете разницу между константой и функцией без аргументов?
Понимаю не совсем так, как вы.
Мы декларируем некий идентификатор и указываем, что он равен значению, возвращаемому функцией. Наверняка вы читали статейки на тему «eqational reasoning», там как раз именно с такой точки зрения всё рассматривается. И выходит очень логично и очень математично. Мне тоже нравится такой подход.
Ну а если рассматривать реализацию, то вы тоже не сможете сказать что-то определённое до того момента, пока не изучите исходный код компилятора. Поэтому остаётся пользоваться абстракциями. Вот я и предлагаю использовать математические абстракции. В частности — константы, функции, уравнения. Вы же предлагаете смотреть на процесс исполнения кода, который скрыт внутри кода компилятора. То есть вы предлагаете пользоваться неявным знанием, а я же предлагаю использовать строгие и понятные со школы определения.
С функциональной точки зрения программа — не последовательность вызовов функций, а одна большая функция, которую нужно не «вычислить» в привычном нам понимании, а «упростить» с учетом того, какие значения получили ее переменные.
Ну опять же получается более сложная конструкция. Есть императивное (привычное) понимание. Есть школьно-математическое (функции, уравнения, константы). И есть лямбда-исчисление. Вы предлагаете смотреть на код как на пример лямбда-исчисления, но как раз при этом возникает та самая сложность. И зачем она?
Если бы было некое принятое большинством определение ФП именно как реализации лямбда-исчисления, а соответственно, реализации компиляторов были бы ориентированы именно на такую интерпретацию, тогда, возможно, был бы смысл «говорить сложно». Но сама сложность здесь служит препятствием. Поэтому люди и придумывают интерпретации попроще. В том числе — математические, через уравнения, функции, константы.
Если вы о ленивости (как в статье, рассматривая её как часть лямбда-исчисления), то опять же, абстракция «частичного вычисления» внутри компилятора применима редко из-за сложности такого вычисления. А вот функция, у которой известен неполный набор аргументов — это гораздо более понятная абстракция, не требующая обязательных вычислений и простая в использовании. И я не уверен, что компиляторы идеально соответствуют именно принятой в лямбда-исчислении абстракции, а потому можно ошибиться, если начинать продумывать, до какого момента что-то там частично вычислено. И это тоже аргумент против вашей точки зрения.
Тем, что это не присвоение, а определение функции.
А чем тогда вас не устраивает определение константы? Её ведь тоже определяют.
Присвоения в ленивых языках как такового нет, поскольку это чистой воды побочный эффект.
Присвоение — это абстракция. В реальности же происходит обработка текста компилятором. Ну а далее внутри, в неочевидной последовательности, вполне себе присутствуют присвоения.
Хотя если придираться к определениям, то в данном случае, согласен, не присвоение, а определение константы с указанием её значения, вычисляемого функцией.
И в этой строчке она не вызывается.
Ну ладно, не в этой строчке. Так вас устроит? Или вы считаете, что функции вообще не вызываются?
А творческая работа по изучению монад, do notation и прочего использованного в вашем коде? Ну и само введение поддержки do notation на уровне компилятора есть прямой путь к императивному стилю, но в функциональном языке.
Плюс можно поспорить с деталями реализации, но я в данном случае просто не понял происходящего. Вы использовали какое-то чудо вроде мутабельной монады? Что это за M? Ну и по другим моментам мне нужно лезть в справочник и разбираться (давно уже перестал интересоваться хаскелем).
Из того, что очевидно — в императивном языке достаточно массивов или списков, а у вас ещё монады. Само определение функции там проще (только вход-выход). Ну и функции у М явно не «один в один» с императивными языками, что опять усложняет понимание (не знаю, правда, сколь широко подобные функции используются активно пишущими на хаскеле, но я их не встречал, когда его изучал).
Статья неплохая, но всё же присутствует однобокость. Качественный подход предполагает нечто более многостороннее. Вот примеры:
Функция fibonachi (а это именно функция) порождает бесконечный список чисел
Всё-таки это константа, хоть и имеющая значение, порождаемое функцией.
Разница в том, что в рамках функционального подхода подобная организация программы естественна и навязывается самим способом ее выполнения, в то время как в рамках императивного она требует дополнительной творческой работы
Здесь неопытнй читатель наверняка подумает, что функциональные языки «лучше», раз автор так уверенно говорит про «требует дополнительной творческой работы». Но на самом деле ФП тоже «требует дополнительной творческой работы» во всех случаях, когда нужно просто последовательно (в императивном стиле) изложить алгоритм. И здесь важно сказать явно — императивный и функциональный — это лишь стили. Да, в каких-то местах они требуют поддержки компилятора (то есть без костылей абстракции не работают), но тем не менее — это лишь стили изложения алгоритмов, ну а та часть, что «излагается» компилятором, успешно может быть реализована и на самом языке (в рамках стиля), но потребует больших приложенных усилий.
И если понять, что всё это — лишь разговор о стилях, то и само противостояние ФП — ИП сразу становится противостоянием «стилистов». Либо тех, кто не понимает, с чем имеет дело.
Тезис: Функциональная программа — программа, состоящая из чистых функций
Здесь лучше не приводить «тезисы», взятые из обсуждений одних «знатоков» с другими. Изначально ФП ориентировалось на лямбда-исчисление (по сути — прямая реализация), значит можно попробовать его определить именно через поддержку операций лямбда-исчисления. Но потом к нему добавили расширений, что несколько размыло определение. И хотя расширения логически вытекают из положенного в основу лямбда-исчисления, но всё же само лямбда-исчисление было ограничено (бесконечных теорий не бывает), а потому, не смотря на дальнейшие логические связи, возможно, стоит строить определение ФП именно на основе соответствия лямбда-исчислению. Но тогда определение станет непонятным большинству потенциальных пользователей продукта «язык программирования». Значит стоит выделить некую характерную часть лямбда-исчисления, как например в статье выделена частичная редукция, и как следует её «разжевать». Правда и тогда опять будут ссылки на то, что «из этого логически вытекает ленивость».
То есть в целом я бы просто поставил проблему — дать по возможности однозначное (не допускающее множества толкований и расширений) определение ФП. И, поработав над таким определением, ФП-сообщество вполне могло бы самостоятельно прийти к идее стиля, который отличает одну программу от другой лишь личными пристрастиями конкретного программиста.
На сколько я помню — везде вставляется признак strict mode.
Попробуйте сделать ленивый список и потом использовать strict mode. Я думаю случится что-то страшное (типа память кончится).
А вы думали, что сразу разберётесь в мегабайтах документации? Что бы найти нужное место требуется определённый опыт, и по работе с БД и по самой документации.
У меня там не возникало вопросов вроде определения шагов для вычисления ленивого списка. А когда что-то непонятно, то всегда есть довольно простые гипотезы о соответствии моего кода и скомпилированного. Пробуем гипотезу, меряем время/память, выясняем что подходит.
А вот в хаскеле с тем же ленивым списком, если предположить, что вместо списка создаётся структура, содержащая описание вычисления, то тогда получится, что на императивном языке я могу создать в десятки раз больше списков, потому что там нет таких затрат по памяти. То есть неочевидность здесь сложнее выявить и если гипотеза верна — явно имеем большой расход памяти.
То есть у вас претензии к потере 5мс из 260? Два процента. Это явно излишняя оптимизация (в большинстве случаев). Тратить время на это нехорошо, потому что есть более важные задачи (обычно).
А по ассемблеру — если уж совсем по мелочам важно всё отследить, то лучше ассемблерные вставки делать. Там контроль полный и опять ненужно ничего декомпилировать.
Ну это уже отписка. И нельзя сравнивать документацию по функциям с документацией по алгоритмам, используемым БД. Функции — это лишь малая часть документации по БД.
Посмотрел, но там же стандартные пояснения про тривиальные случаи. Это и так понятно. Мне же хотелось понять, как конкретно увязаны неизменность списка с ленивостью его заполнения. Из стандартного объяснения про сохранение вычисления и его реальную обработку «когда надо» непонятно как же обрабатывается ленивый список из функции Фибоначчи. То есть «реально надо», когда я прошу дать мне первый элемент, а потом «реально надо», когда прошу дать второй, и т.д. На котором шаге что считается? Где неизменность списка? Где смысл функции, возвращающей всегда один и тот же результат при неизменных аргументах? Как это всё увязывается в реальности?
В С есть так называемые intrinsics, которые напрямую транслируются в ассемблерный код. Оптимизация выполняется именно с их помощью. И при этом нет никакой необходимости смотреть «что там получилось» в ассемблерных листингах.
В языках с байткодом такая же ситуация, точнее там вообще нельзя посмотреть такой листинг, ну и в том числе поэтому и не смотрят.
А вот хаскель компилируется в машинный код и его тут недавно (была статья) декомпилировали именно для понимания, а что же он там делает.
А где вы смотрите детали? По БД есть очень хорошая документация, а вот по хаскелю — надо всё искать (если что-то сложнее основ). Вплоть до изучения кода компилятора.
Как работает ленивость? Автор статьи упомянул ленивое вычисление списка чисел Фибоначчи — как этот список получается (императивно)? Где искать?
Но в обычной программе (императивной) вообще нет необходимости смотреть какие-то планы/выводы. Точнее в большинстве случаев достаточно просто подумать.
Как я понял, там применяется смесь из нейросетей с обработкой на основе различных эвристик. Как-то можно классифицировать эти эвристики? Например был шум на тему semantic web, что-то подобное применяется при переводе? По логике там ему самое место, но по факту там рулит статистика и нейросети. Ну и может что-то ещё.
А если автору не трудно (пусть в виде рекламы своей компании), было бы интересно почитать статью именно про технологическую часть.
И кстати, раз уж статья целится на поиск знатоков NLP, то интересно, а какие задачи они будут решать? Ведь основа давно задана, никакие вольности в виде исследований не позволительны (ограничение по деньгам), и по сути получается, что нужны просто знакомые с используемыми в предметной области терминологией и алгоритмами люди, что бы им поставить задачу, ну и они её пилили как в обычных программистских конторах. Может даже вообще есть смысл иметь лишь одного эксперта, а всё остальное поручать гораздо более дешёвым помощникам, которым просто спускается план действий, может с какими-то пояснениями, мол вот с этого сайта текст надо скормить этой приблуде, она посчитает статистику, потом результат сохранится сюда, а ты выберешь из статистики такие-то части и включишь в программу для Андроида (в её базу данных).
И опять кстати, программа для Андроида имеет всего 1000 установок и непонятно сколько из них активны. Как-то мало.
Цикл или сортировка — внутренние сложности алгоритма нахождения соответствия записей из А и В. Абстрактный же алгоритм очень прост. Но если мне надо, то я легко найду внутренние детали реализации. Вот в этом простота.
У меня был очень простой вопрос — а зачем так? Вы же целую статью на эту тему написали, объясняя зачем. И там много комментариев со спорами.
Ну а поверхностно понять, что «на входе Х, на выходе У» — это конечно легко.
В случае с БД — да, там всё просто. А в случае с хаскелем — я бы так не сказал. Для оптимизации хаскель-программ используют опции, указывающие компилятору, где и как что-то делать, что бы он не нафантазировал чего-то непредвиденного. Но это непредвиденное, и даже место, где оно возникнет, нужно же понимать. Вот это и есть сложность. Она выше сложности БД. В БД может поменяться алгоритм сортировки или джойна, но концептуально это ничего не меняет, а вот в хаскеле указания компилятору меняют смысл программы (с ленивой на неленивую, например).
Докажет разницу в сложности. Большинство знающих хаскель легко разберутся с SQL, но большинству знающих SQL придётся нелегко, когда нужно будет разбираться с хаскелем.
Для занятия бизнесом нужно очень хотеть заработать. То есть в обмен на все остальные удовольствия вы должны научиться извлекать приятное из очень затратной по времени и скучной работы по выявлению интересов абстрактного клиента и выбору методов удовлетворения этих интересов на уровне, сравнимом с конкурентами. Ну и без гарантий, разумеется. Точнее даже с гарантией того, что даже просто на самоокупаемость выйти будет трудно (а многим — невозможно).
При этом, разумеется, вы должны забыть о разного рода копании в алгоритмах, создании сложных систем и т.д. Это всё должны делать другие, потому что у вас не будет на это времени. Поэтому ваш путь пройдёт через open-source и прочие готовые/покупные технологии. Сами вы ничего разрабатывать не будете. А если будете — потеряете бизнес.
Но, конечно, пока не попробуешь — не поймёшь, так что — попробуйте, проверьте, ну вдруг вы попадёте в волну (одну на миллион) и вам ничего из указанного делать не придётся. А если не попадёте — вас ждёт довольно скучная (на мой взгляд) работа. Но может вам она покажется ближе, чем мне.
Main общается с монадами, которые прячут внешние эффекты, а значит main не может быть константой из-за этих самых эффектов.
Я понимаю ваше неоднозначное восприятие программы примерно так — раз программа что-то выполняет, значит она не постоянная/неизменная. Но здесь уже получится скорее философский разговор, ведь, например, наличие движения в мире для нас — константа. Движение есть, всё меняется, но это всё присутствует постоянно и неизменно повторяется.
Можно ещё сказать, что константа (условно) даёт постоянный адрес в памяти, где лежит тот самый список. То есть вот вам постоянство и неизменность. А то, что сам список меняется (хотя как конкретно он меняется — не очень понятно, может даже вообще не меняется, а сразу получается путём копирования из стека вычисления всего того, что насчитали к моменту востребования списка) — к константе не имеет отношения, ведь адрес же не меняется.
Да иллюзорно, конечно, я не спорю, ведь регистры процессора я точно себе не представляю во время написания алгоритма, но важно отличать доступную сложность абстракции от условно недоступной. Доступная — это когда мне легко понять, почему вдруг код не работает. А недоступная — это когда я «смотрю в книгу и вижу фигу», то есть долго думаю над произошедшим. Ну и поэтому я предпочитаю понятные абстракции. А понятность как раз и связана с моей возможностью проследить алгоритм, если потребуется, хоть до конкретного регистра процессора. На этом строится вся оптимизация. А если я не знаю, как оно там внутри вертится, то и оптимизировать не смогу.
В данном случае «сколько угодно» меньше, чем это же «сколько угодно», но в случае с функциональным стилем изложения алгоритма. Проверено миллионами программистов, почему-то не пожелавших изучать хаскель, но легко осваивающих какой-нибудь питон.
Но тем не менее, вспоминая те времена, когда я впервые знакомился с SQL, я отлично помню, что базовые абстракции там дались мне сразу и легко, а вот ту же абстракцию монад мне пришлось штурмовать гораздо дольше.
Ну и если посмотреть на других, то по ним видно, что оптимизацию запросов SQL осиливают далеко не все, что означает и в данном случае наличие заметной сложности. Хотя монады из тех, кто хорошо освоил SQL, так же понимает меньшинство. Правда моя статистика, понятно, субъективна. Но можно спросить на любом форуме по базам данных — кто из вас вообще знает про хаскель? А кто понимает вот этот код… и привести что-то с монадами. Ответ, мне кажется, будет соответствовать моей статистике.
А откуда же берутся значения констант? Их кто-то обязательно вычисляет, самый минимум — переводит текст в число.
После декларации константы идёт функция, которая как раз и обязана вычислить значение константы. При этом слегка туманит ситуацию ленивость, но константе-то без разницы, ведь она просто показывает нам список, хоть и пополняемый по мере надобности. А ещё неизменность списка сложно воспринимается. Но это всё относится именно к области действия той функции, которая вычисляет значение константы.
Мне не до фонаря. Я предпочитаю понимать, именно императивно, что там происходит. Потому что если понимать поверхностно — всегда найдётся случай, когда «понимание» оказывается непониманием.
Понимаю не совсем так, как вы.
Мы декларируем некий идентификатор и указываем, что он равен значению, возвращаемому функцией. Наверняка вы читали статейки на тему «eqational reasoning», там как раз именно с такой точки зрения всё рассматривается. И выходит очень логично и очень математично. Мне тоже нравится такой подход.
Ну а если рассматривать реализацию, то вы тоже не сможете сказать что-то определённое до того момента, пока не изучите исходный код компилятора. Поэтому остаётся пользоваться абстракциями. Вот я и предлагаю использовать математические абстракции. В частности — константы, функции, уравнения. Вы же предлагаете смотреть на процесс исполнения кода, который скрыт внутри кода компилятора. То есть вы предлагаете пользоваться неявным знанием, а я же предлагаю использовать строгие и понятные со школы определения.
Ну опять же получается более сложная конструкция. Есть императивное (привычное) понимание. Есть школьно-математическое (функции, уравнения, константы). И есть лямбда-исчисление. Вы предлагаете смотреть на код как на пример лямбда-исчисления, но как раз при этом возникает та самая сложность. И зачем она?
Если бы было некое принятое большинством определение ФП именно как реализации лямбда-исчисления, а соответственно, реализации компиляторов были бы ориентированы именно на такую интерпретацию, тогда, возможно, был бы смысл «говорить сложно». Но сама сложность здесь служит препятствием. Поэтому люди и придумывают интерпретации попроще. В том числе — математические, через уравнения, функции, константы.
Если вы о ленивости (как в статье, рассматривая её как часть лямбда-исчисления), то опять же, абстракция «частичного вычисления» внутри компилятора применима редко из-за сложности такого вычисления. А вот функция, у которой известен неполный набор аргументов — это гораздо более понятная абстракция, не требующая обязательных вычислений и простая в использовании. И я не уверен, что компиляторы идеально соответствуют именно принятой в лямбда-исчислении абстракции, а потому можно ошибиться, если начинать продумывать, до какого момента что-то там частично вычислено. И это тоже аргумент против вашей точки зрения.
А чем тогда вас не устраивает определение константы? Её ведь тоже определяют.
Присвоение — это абстракция. В реальности же происходит обработка текста компилятором. Ну а далее внутри, в неочевидной последовательности, вполне себе присутствуют присвоения.
Хотя если придираться к определениям, то в данном случае, согласен, не присвоение, а определение константы с указанием её значения, вычисляемого функцией.
Ну ладно, не в этой строчке. Так вас устроит? Или вы считаете, что функции вообще не вызываются?
Плюс можно поспорить с деталями реализации, но я в данном случае просто не понял происходящего. Вы использовали какое-то чудо вроде мутабельной монады? Что это за M? Ну и по другим моментам мне нужно лезть в справочник и разбираться (давно уже перестал интересоваться хаскелем).
Из того, что очевидно — в императивном языке достаточно массивов или списков, а у вас ещё монады. Само определение функции там проще (только вход-выход). Ну и функции у М явно не «один в один» с императивными языками, что опять усложняет понимание (не знаю, правда, сколь широко подобные функции используются активно пишущими на хаскеле, но я их не встречал, когда его изучал).
Почему вы так считаете? И чем вас не устраивает присвоение значения константе путём вызова функции?
Определение «естественности образа» опять же получится размытым, поэтому и разделение концепций не выйдет.
Есть более базовые понятия, вроде рекурсии, вот они поддаются точному определению, а стиль изложения алгоритма — весьма зыбкая сущность.
Всё-таки это константа, хоть и имеющая значение, порождаемое функцией.
Здесь неопытнй читатель наверняка подумает, что функциональные языки «лучше», раз автор так уверенно говорит про «требует дополнительной творческой работы». Но на самом деле ФП тоже «требует дополнительной творческой работы» во всех случаях, когда нужно просто последовательно (в императивном стиле) изложить алгоритм. И здесь важно сказать явно — императивный и функциональный — это лишь стили. Да, в каких-то местах они требуют поддержки компилятора (то есть без костылей абстракции не работают), но тем не менее — это лишь стили изложения алгоритмов, ну а та часть, что «излагается» компилятором, успешно может быть реализована и на самом языке (в рамках стиля), но потребует больших приложенных усилий.
И если понять, что всё это — лишь разговор о стилях, то и само противостояние ФП — ИП сразу становится противостоянием «стилистов». Либо тех, кто не понимает, с чем имеет дело.
Здесь лучше не приводить «тезисы», взятые из обсуждений одних «знатоков» с другими. Изначально ФП ориентировалось на лямбда-исчисление (по сути — прямая реализация), значит можно попробовать его определить именно через поддержку операций лямбда-исчисления. Но потом к нему добавили расширений, что несколько размыло определение. И хотя расширения логически вытекают из положенного в основу лямбда-исчисления, но всё же само лямбда-исчисление было ограничено (бесконечных теорий не бывает), а потому, не смотря на дальнейшие логические связи, возможно, стоит строить определение ФП именно на основе соответствия лямбда-исчислению. Но тогда определение станет непонятным большинству потенциальных пользователей продукта «язык программирования». Значит стоит выделить некую характерную часть лямбда-исчисления, как например в статье выделена частичная редукция, и как следует её «разжевать». Правда и тогда опять будут ссылки на то, что «из этого логически вытекает ленивость».
То есть в целом я бы просто поставил проблему — дать по возможности однозначное (не допускающее множества толкований и расширений) определение ФП. И, поработав над таким определением, ФП-сообщество вполне могло бы самостоятельно прийти к идее стиля, который отличает одну программу от другой лишь личными пристрастиями конкретного программиста.