Помню, когда я впервые узнал о существовании Haskell. Фактически это было примерно в то время, когда Apple впервые выпустила Swift. Взволнованный перспективой изучения совершенно нового языка и участия в совершенно новом языковом сообществе, я нырнул прямо в него - и тут же врезался в твердый бетон Optionals. Я внезапно столкнулся с последствиями безопасности типов, о которых никогда раньше не задумывался. Что должно произойти, если вы попытаетесь получить доступ к несуществующему значению? Должен ли компилятор выдать ошибку? Предупреждение? Должна ли ваша программа компилироваться, но выдавать исключение во время выполнения? Обязан ли программист обрабатывать ошибки или компилятор отказываться выдавать небезопасный код? Один вопрос, который мне не приходил в голову, заключался в том, должны ли сами разработчики языка делать что-то синтаксически, чтобы предотвратить ошибки. В Swift варианты были предложенным решением головоломки, связанной с обработкой значений, которые могли не существовать. Например, при доступе к значению из словаря Swift возвращает не значение для данного ключа, а необязательное значение, которое включает, как часть типа значения, вероятность того, что значение на самом деле не существует. Чтобы получить доступ к необработанному значению, вы должны либо принудительно развернуть его, либо использовать выражение if let для проверки возможности nil:

Для новичка в Swift с трудом передать словами, насколько это раздражало. В более ранних версиях языка вы могли быстро получить if let пирамиды гибели, если бы у вас было несколько опций для развертывания. Эта проблема была несколько смягчена в Swift 1.2 и, наконец, решена с добавлением в Swift 2.0 инструкции защиты. Но к тому моменту я уже отошел от Swift. Интересно, откуда взялись такие концепции, как необязательные опции, я рано открыл для себя нечто, называемое «функциональное программирование», и язык под названием «Haskell», который все называли «сложным». Поскольку я всегда заинтересован в том, чтобы приблизиться к источнику идей и довольно упрям ​​в изучении вещей, которые люди считают сложными, я подумал, что это может только улучшить мое понимание Swift, если я тоже выучу Haskell. Чего я не ожидал, так это того, что меня полностью погрузит в мир FP, что я полностью потерял интерес к Swift.

Дело не в том, что Swift неинтересный язык. Я определенно предпочитаю его Objective-C, который мне никогда не удавалось глубоко изучить (я виню свое раннее знакомство с C ++). Просто после знакомства с Haskell и функциональным программированием в его естественной среде обитания я обнаружил, что Swift в конечном итоге гораздо менее увлекателен, чем я думал, мог или должен был быть. На мой взгляд, Swift слишком старается использовать оба варианта: он сочетает в себе безопасность типов такого языка, как Haskell, с синтаксической изюминкой динамических языков сценариев, таких как Python и Ruby. Я считаю, что именно это последнее качество заставляет людей относиться к определенным языкам программирования как к «забавам». Что касается эволюции языка, Swift - это еще одна итерация семейства ALGOL / C. Будет ли это имитировать тенденцию C ++ включать как можно больше функций, независимо от того, противоречат ли они друг другу? Конечно, он поддерживает Unicode, поэтому вы можете присваивать значения единорогам и куче какашек, но в остальном это не кажется мне значительным достижением.

С другой стороны, изучение Haskell было похоже на вход в портал в другую вселенную, в которой действуют совершенно другие законы физики. Внезапно я не смог переназначить переменные (что?); или создавать объекты для представления моих данных (Что?); или даже выполнять базовые операции ввода-вывода, такие как печать на экране, очевидным и простым способом (ЧТО?). Однако я мог написать самый элегантный, краткий и правильный код в моей жизни. Конечно, сначала все это выглядит как суп из алгебраических алфавитов. Но как только вы преодолеете общее отсутствие фигурных скобок и круглых скобок, как только вы научитесь думать в терминах «почему», а не «как» делать что-то, как только вы примете неудачливость языков, которые моделируют операции машины вместо В процессе решения проблем вы начинаете осознавать достоинства таких вещей, как функциональная чистота, композиция и ссылочная прозрачность, и перестаете бояться абстракций, представленных заумными терминами, такими как функтор и монада.

В качестве средства преодоления препятствий на пути к изучению Haskell, которые вполне реальны, я решил повторно реализовать некоторые его функции в JavaScript. С дополнениями ES2015 к языку, которые я также хотел изучить, это казалось более достижимой целью, которую я мог бы достичь с меньшим синтаксическим взломом, чем это было возможно раньше. Более того, я уже был знаком с JavaScript (в отличие от Swift), и, поскольку он имеет функции более высокого порядка и довольно расслабленный подход к безопасности типов, я думал, что смогу достаточно точно интерпретировать код Haskell и без необходимости удовлетворять требованиям. языка (например, Swift) с более придирчивым компилятором. Другими словами, слабые стороны JavaScript как языка сделали его идеальной средой для экспериментов с тем, что я считаю более сильной парадигмой программирования.

Когда я начал этот амбициозный проект, я быстро понял, что не могу сразу же начать писать интересные функции. Если я хотел, чтобы мой код JavaScript хотя бы приближался к элегантности Haskell, мне нужно было сначала кое-что сделать. Функции Haskell автоматически каррируются и отчасти по этой причине могут быть составлены. Чтобы заложить ту же основу для своего кода, я начал это приключение с написания двух служебных функций, partial и $. Функция partial действует как своего рода каррированный API, абстрагируя шаблонный код, который в противном случае потребовался бы для частичного применения функции JavaScript:

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

Функция $ работает как оператор композиции Haskell:

Вся эта функция (под видом инфиксного оператора) - это взять две функции, f и g и составить их, что означает возврат новая функция, которая применяет f к результату g, примененного к аргументу x. Вот моя версия JavaScript:

Поскольку точка уже используется в JavaScript, я решил представить свою собственную сопоставимую операцию с помощью bling - по крайней мере, это дань уважения собственному правоассоциативному оператору привязки Haskell, также $. Обратите внимание, что он работает аналогично частичному, поскольку при отсутствии аргумента x он возвращает новую функцию, которая его ожидает, и, учитывая x , он просто оценивает состав f и g. Также: однострочный!

Заложив этот фундамент, я смог начать строительство остальной части здания проекта. Я хочу представить здесь лишь несколько очень простых примеров того, что возможно с помощью функционального программирования, показывая некоторые простые для чтения функции Haskell вместе с моими собственными версиями JavaScript. В Haskell логические операторы - это такие же функции, как и любые другие. Они могут быть определены как инфиксные функции для ясности (и согласованности с другими языками), но это всего лишь синтаксический сахар, и это даже не обязательно. Вот как в Haskell определяются основные логические операторы AND, OR и NOT:

Первая строка каждой функции называется «сигнатурой функции». Подпись выражает типы параметров функции и возвращаемых значений. Стрелки указывают функциональные приложения. Для простоты последний тип можно рассматривать как фактический тип возвращаемого значения функции, а остальные - как аргументы. Например, && принимает два аргумента, логическое и другое логическое, и возвращает логическое значение. Эта часть должна быть простой. Однако на самом деле происходит то, что && принимает единственный логический аргумент и возвращает новую функцию, которая частично применяется к этому аргументу. Эта новая функция также принимает единственный логический аргумент. Когда он применяется к этому аргументу, функция в целом применяется полностью, а результат сводится к логическому значению True или False. Тело функции выполняет сопоставление с образцом, чтобы определить это окончательное значение. Если первый аргумент - True, окончательное значение будет таким же, как значение x. Однако если первый аргумент False, то не имеет значения, какой второй аргумент, потому что в этом случае функция всегда будет оценивать значение False. Подчеркивание означает, что второе значение не имеет значения. В этом отношении шаблоны в других функциях должны быть очевидными. Нет необходимости в дополнительном синтаксисе, таком как выражения if let и ключевые слова guard!

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

Важные части этих функций, по две строки в каждой, идентичны своим аналогам в Haskell. Однако основная часть из них отдана проверке типов (которую я, возможно, мог бы не упомянуть в этих примерах) и возможности частичного применения. В будущем я, возможно, захочу заменить довольно громоздкую проверку типов на что-то невидимое, построенное с использованием новых API Proxy и Symbol. Возможно, я даже смогу сделать то же самое для частичного приложения, перехватывая вызовы функций с помощью Proxy и выполняя все вещи Haskell-y за кулисами. А пока я считаю полезным увидеть, как эти вещи можно реализовать с помощью менее сложного кода, а также решить проблемы адаптации строго типизированной парадигмы к слабо типизированному языку.

В последних двух примерах я покажу частичное приложение и композицию функций вместе. Haskell определяет две функции, even и odd в терминах друг друга:

Каждая функция принимает значение типа a, которое должно быть целым числом, и возвращает логическое значение. Еще раз обратите внимание на шаблоны: если n - четное целое число, то even n возвращает True. В этой функции вы также можете увидеть функцию остатка или по модулю rem, используемую как инфиксный оператор, что и обозначают обратные кавычки. Для odd вы, очевидно, можете написать что-то вроде n `rem` 2› 0, но вместо этого even состоит из not функция сверху. Кажется, такой маленький фрагмент кода, но почему бы не использовать его повторно? Если значение четное, то оно не нечетное. Наоборот. Такое повторное использование кода, обеспечиваемое композицией, является фундаментальным для философии функционального программирования, даже если размер кода кажется тривиальным. В идеале большинство функций должно быть как можно меньше. Чтобы создать более крупную операцию, соедините более мелкие вместе. Теперь давайте посмотрим, как это выглядит в JavaScript, как написано мной:

Хотя мне пришлось реализовать их как две отдельные функции, смысл снова остался прежним. Обратите внимание, что моя функция odd использует функцию композиции $, которую я определил выше. Также обратите внимание на использование частичного нанесения. Функция $ принимает not и возвращает новую функцию, которая применяется not к even, которая также возвращает новую функцию ожидает значение a. Если вы привыкли использовать аргументы, которые счастливо живут вместе в списках, разделенных запятыми, в скобках, этот формат, несомненно, покажется вам странным. Но он работает над преобразованием функций высшего порядка JavaScript в еще более мощные каррированные функции, которые так же легко передавать, как и сами необработанные значения. Однако вы также можете заметить, что эти две функции также фундаментально отличаются от своих эквивалентов в Haskell. На самом деле они в корне сломаны. Посмотрите, сможете ли вы понять, почему, а затем подумайте о последствиях написания четкого, краткого, чистого функционального кода на языке, который не предназначен для его поддержки.

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