Pull to refresh

Comments 27

А чем єта не чистая? func cube(x: Int) -> Int {
return square(x) * x
}
если в square нет подвоха, то она чистая
Не square может быть не чистой, соответственно cude тоже не всегда возвращает одно и то же значение
Она использует внешний square, который может быть переопределн кем-то где угодно.
Но она определена в етом же файле
Но имея только определение cube, вы не можете сказать чистая она или нет. В случая остальных — это возможно, так как они используют свои внутренние функции.

Чистая: для отрицательных возвращается NaN

Иван, есть неточности в определениях:


1) Функция первого класса — из вашего определения следует, что это какие-то особые функции, обладающие особыми свойствами, что неверно:


во-первых, нет такого самостоятельного понятия — "функция первого класса" — это всегда свойство языка программирования, которым некоторые из них обладают: "функции являются объектами первого класса".


во-вторых, свойство "объект первого класса" в рамках языка может быть присуще или не присуще любому из типов сущностей языка целиком — переменным, константам, функциям, классам, интерфейсам, пространствам имен и тп; то есть, если в языке функции являются объектами первого класса, то сразу все, а не какие-то конкретно.


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


в-четвертых, пропущено одно из ключевых универсальных свойств объектов первого класса — такие объекты могут быть созданы во время выполнения программы


в-пятых, приведен некорректный пример, но это сложно понять из-за предыдущего пункта — в языке C функции не могут быть созданы во время выполнения программы — только во время компиляции, поэтому в C они не являются объектами первого класса

let sum = numbers.reduce(0, combine: { $0 + $1 }) // 55

Я бы на вашем месте лямбду в классическом виде написал. Не всем очевидна эта фича свифта что
{ $0 + $1 } равносильно записи (a, b) -> a + b
А то так можно вспомнить что в свифте вообще вот так можно написать:
let sum = numbers.reduce(0, combine: +)
Дополнение про чистые функции.

1. Чистые функции называются «чистыми», потому что являются функциями в математическом смысле этого термина. А именно: функция — это отображение одного множества (декартова произведения множеств допустимых значений параметров) на другое (множество возможных значений функции). От «программистских» функций они отличаются тем, что а) детерминированы и б) не имеют побочных эффектов.

2. Детерминированность означает, что функция всегда возвращает одно и то же значение для заданного набора параметров. Примеры недетерминированных функций: random(), date(), read(). Если вы хотите сделать функцию с рандомом детерминированной, вам нужно передавать значение рандома как дополнительный параметр этой функции, а не вызывать random() внутри её тела.

3. Отсутствие побочных эффектов означает, что функция не изменяет состояние внешних объектов (своих параметров, глобальных переменных, контекста, состояния системы и т.д.). Т.е. ничего никуда не выводит, не пишет логи, не выделяет динамическую память, и не использует оператор присваивания к внешним объектам. Более того, при помощи макроопределений, рекурсии и какой-то матери можно вообще отменить оператор присваивания как таковой, который, собственно, и отличает математику от информатики. В каком-нибудь радикально функциональном языке выражение x = y + z будет означать не «записать в ячейку x значение суммы», а то, что x это сокращение для выражения y + z. Отсюда же, кстати, растут ноги про «неизменяемое состояние».

4. Чистые функции оторваны от физического времени. Это значит, что их можно вычислять в любой удобный момент времени между теми моментами, когда а) становятся известны значения всех параметров вызова; и б) реально потребуется значение результата. В частности:
— если значения всех параметров вызова заданы константами в коде программы, функцию можно вычислить на этапе компиляции, а в скомпилированном коде она соптимизируется до вида {return 42;}
— если значения параметров становятся известны заметно раньше, чем требуется результат, функцию можно запустить «сверхэнергично», т.е. заранее, например, на другом ядре.
— если функция вызывается с одними и теми же значениями параметров, её можно вычислить один раз и записать вычисленное значение в кэш. Затем возвращать значение из кэша.
— если неизвестно, когда точно потребуется результат функции, её можно вообще не вычислять, пока не припрёт (ленивые вычисления).
В сумме эти 4 метода имеют огромный потенциал для ускорения вычислений.

5. Именно широкие возможности оптимизации кода, а не то, что чистая функция «обычно, является показателем хорошо написанного кода», является их киллер-фичей.
Например, те, кто писал на Си, знает, насколько неприятно в этом языке работать со строками. Необходимо не просто разбивать все операции на элементарные (три строки вы одной операцией не сконкатенируете), а ещё постоянно следить за памятью. Ошибки переполнения буфера при строковых операциях в Си — безусловно самая дорогая в смысле ущерба от последствий. Тут и взломы всего интернета, и падения ракет, и вообще что угодно.
Так вот, во многих более новых языках строки объявлены неизменяемыми, либо, как минимум, библиотека спроектирована так, чтобы её можно было реализовать с неявно неизменяемыми строками (copy on write).
Что это даёт? Компилятор собирает все строковые константы в исходном тексте и ещё на этапе компиляции выполняет все операции, которые над ними потребуются (strlen, strconcat, strpos, substr и т.д. — они все как раз чистые). Но главное, он выделяет память под все строки ещё на этапе компиляции.
Спасибо! Особенно порадовали функции высшего порядка. Из теории систем знаю, что это имеет название мета-уровень или эмерджентный уровень. Очень важная вещь при развитии проекта, если правильно их использовать.
Вторая (концепция опциональности) — заставляет нас думать о возможных крайних случаях и ошибках, которые могут прийти извне, и обрабатывать их корректно.
В Haskell Вы наоборот не думаете о крайних случаях и не определяете функций, которые принимают Maybe (если следуете концепциям языка). Это особенность как концепции опциональности, так и монад в целом… Вы пишете функции, которые принимают чистые значения и возвращают монадические, обо всём остальном заботится >>=, сам по себе или внутри do-блока.

Ещё в определении sum есть лишняя строка:
sum (x:[]) = x -- one element

В разделе «Пример использования каррирования» у Вас показано не каррирование, а то, что функции являются объектами первого рода, т.е. функция может вернуть функцию.
Каррирование — это когда функция принимает N аргументов, мы ей передаём M параметров и она превращается в функцию, принимающую N-M аргументов, где N >= M && M > 0. Swift скорее всего не поддерживает такое из коробки.
Возможно, Вы сможете определить свою функцию carry(fn, arg), которая будет принимать произвольную функцию и первый параметр для неё, и возвращать её в каррированном виде.
Позвольте, то, что вы написали, тоже не каррирование. Это частичное применение.

Каррирование – это когда функция принимает N аргументов, а мы из неё делаем функцию, которая принимает первый аргумент и возвращает функцию, которая принимает второй аргумент и возвращает функцию, которая … и возвращает функцию, которая принимает N-й аргумент и возвращает значение исходной функции.
Согласен, путаница вышла. Хотя для языков, которые не поддерживают ни то ни другое прозрачно, лучше частичное применение реализовать, а то f(a)(b)(c)(d) уже сложно читать, а что-то типа carry(carry(carry(carry(f, a), b), c), d)() и подавно.
Функциональщина стала модной, с этим нельзя не согласиться. Думаю лет через 5-10 все перестанут с ней носиться и будут так же поливать грязью как сейчас ООП.
А что не так с ООП, по Вашему мнению? можно в личку, что бы не минусовали, у пользователя на профиле есть конвертик для писем
Все с ним нормально, просто раньше все носились с ООП как со священной коровой, пихая его везде где надо и не надо, не понимая что это просто инструмент, и как у любого инструмента у него есть границы применимости. Есть кейсы где этот инструмент подходит, а есть кейсы где не подходит. Сейчас такая же ситуация происходит с функциональщиной.
А более конкретные есть примеры? у меня есть одно наблюдение, где методология дает сбой, но это идет против ветра, затрагивает веру
Приведите лучше свое наблюдение
В ООП и других областях программирования действует мышление, которое можно назвать обобщением. Бывают фразы, типа «все сущности», «все договора», «все операции». При внимательном рассмотрении базовый класс или интерфейс зачастую готов обрабатывать сущности только на момент написания ТЗ. Потом появляются то исключения из правил, то новые типы сущностей, которые никак не влезают в базовый класс. К примеру, на каком-то этапе в организации были клиенты физ-лица. А потом программист который делал интерфейс «Client» уволился, а организация начала обслуживать юр.лица. Последующие программисты будут иметь дело с абстракциами, которые созданы другими и пытаться разгадать что конкретно имелось в виду. Появляются смысловые люфты между программой и предметной областью.
> Берем Range от одного до биллиона
> let bigRange = 1...1_000_000_000_000 // from one to one billion

Биллион по-русски называется миллиард, а объявлен у вас вообще триллион.
Если посмотреть на то, что человек, который явно понимает ФП, написал статью, но судя по коментам — не совсем правильно понимает базовые понятия ФП, то становится понятно что функциональное программирование — довольно «путанная штука».

Но дело даже не в этом, а в том — зачем оно? Какая от него польза?
Этого я из статьи не понял.

Да, местами ясно, что где--то может помочь — но насколько это будет существенная помощь, чтобы «плавить мозги»?

Трудно понять, чем код вида:
1) Получим массив элементов.
2) Определим, есть ли «наш элемент» в данном массиве?

Лучше функционального подхода:
1) Получим функцию.
2) Определим, вызвав полученную функцию (с параметром — «нашим элементом»), есть ли «наш элемент» в каком-то множестве?

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

Но насколько это так уж важно?

Если посмотреть на то, что человек, который явно понимает ФП, написал статью, но судя по коментам — не совсем правильно понимает базовые понятия ФП, то становится понятно что функциональное программирование — довольно «путанная штука».
Swift и уж тем более C — не являются функциональным ЯП. Haskell автор не знает на достаточном уровне. Я его и сам не знаю толком, но в статье совсем уж детские грабли в Haskell-коде.

Что до путанности, посмотрите статьи про ООП и вообще офигеете. Абсолютно ни у кого нет чёткого понимания, что входит в это понятие, а что нет. И по каждому ассоциированному термину влёгкую можно начать холивар. ФП в этом плане отлично формализовано, что конечно не страхует от того, что кто-то может что-то напутать. Человеческий фактор не отменить, зато всегда можно глянуть точное определение.

1) Получим массив элементов.
2) Определим, есть ли «наш элемент» в данном массиве?

Это тоже функциональный подход.

Императивный подход:

1) Получим указатель на массив элементов и целевой элемент
2) Определим длину массива
3) В цикле от 0 до длина-1 будем сравнивать i-ый элемент массива с целевым.
4) Если элемент совпал, то return true
5) return false
Source > Что до путанности, посмотрите статьи про ООП и вообще офигеете. Абсолютно ни у кого нет чёткого понимания, что входит в это понятие, а что нет. И по каждому ассоциированному термину влёгкую можно начать холивар.

Возможно и так, но обычно все «инстинктивно» ООП понимают, что есть объекты, имеющие свойства (видимые или скрытые) которые (объекты) либо обмениваются сообщениями, либо эти объекты можно «дёрнуть» за их (объектов) методы. — То есть — «всё как в жизни». Типа того. А ещё есть иерархия объектов по наследованию — опять таки — как в жизни.

Source > ФП в этом плане отлично формализовано, что конечно не страхует от того, что кто-то может что-то напутать. Человеческий фактор не отменить, зато всегда можно глянуть точное определение.

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

Source > Императивный подход:…
Это уж слишком «низкий» подход у вас.

1) Получим массив элементов.
2) Определим, есть ли «наш элемент» в данном массиве?

Иначе, немного пояснив с ООП:
1) Получим объект(!) типа Массив элементов.
2) Определим, есть ли «наш элемент» в данном объекте вызвав («дёрнув») метод этого объекта.

Так что это чистейший ООП. Имхо, конечно, имхо.

То есть — «всё как в жизни». Типа того. А ещё есть иерархия объектов по наследованию — опять таки — как в жизни.

Ага, именно "типа того". А по факту ничего общего с тем, как в жизни :-)
Да ещё и у каждого своё инстинктивное понимание. Некоторые вообще взаимоисключающие. Впрочем не будем про ООП, на Хабре уже есть много холиваров на эту тему, из которых понятно, что ничего не понятно и общепринятых определений нет :-)


Я всегда считал (читал) что «функция высшего порядка» — это «функция возвращающая функцию».

Там 2 критерия в определении:


  • принимает одну или более функций в качестве аргумента
  • возвращает функцию в качестве результата

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


Это уж слишком «низкий» подход у вас.

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


1) Получим объект(!) типа Массив элементов.
2) Определим, есть ли «наш элемент» в данном объекте вызвав («дёрнув») метод этого объекта.

Ну если уж про ООП, то надо ещё про паттерн Iterator добавить. А то Вы подменяете реализацию алгоритма на её использование. А использование вообще не сильно отличается:


Процедурный стиль:
in_array(arr, obj)


ООП:
arr.include?(obj)


ФП:
Enum.member?(arr, obj)

Sign up to leave a comment.