Обучение SCALA как Python Programmer (3 часть серии)
Реконструировать
В моем предыдущем посте на Основные принципы функционального программирования Я объяснил, как парадигма функциональной программирования отличается от императивного программирования, и обсудили, как концепции идемпотентности и избежания побочных эффектов связаны с свойством референтной прозрачности, которая обеспечивает уравновешенные рассуждения в функциональном программировании.
Перед тем, как мы погрузимся в некоторые особенности функционального программирования, давайте начнем с личного Anecdote во время моих первых 3 месяцев написания Scala Code.
Нет «если-else» в функциональном коде
Я писал чистую Scala функцию для пользовательской искры UDF, которая вычисляет корректировки доходов на основе пользовательской ярусной корректировки, выраженной в строке JSON. При попытке выразить деловую логику в чистом функциональном коде (поскольку это стиль кодирования команды), я получил довольно разочарованно с моим воспринимаемым падением производительности до такой степени, что я ввел «если я ввел логику в мой код» “справиться с работой”.
Давайте просто скажем, что узнал довольно жесткий урок во время обзора кода для этого конкретного запроса слияния.
” Нет, если-else в функциональном коде, это не обязательное программирование … Нет, нет, нет Elses. “
Без «если-else», как мы пишем «контрольный поток» в функциональном программировании?
Краткий ответ: Функциональная композиция Отказ
Долгий ответ: комбинация композиции функций и функциональных структур данных.
В качестве глубокого погружения на каждую функциональную конструкцию может быть довольно длинным, фокус этого поста заключается в том, чтобы обеспечить обзор функциональной композиции и того, как она позволяет более интуитивно понятным подходом к разработке трубопроводов данных.
Краткое введение в состав функционирования
Функциональная композиция
В математике, Функциональная композиция это операция, которая принимает две функции f и g в последовательности и образует композитную функцию h Такое, что h (x) (f (x)) – функция g применяется к результату применения функции F на общий вход х Отказ Математически эта операция может быть выражена как:
Где находится композитная функция.
Интуитивно, композитные карты функции х в Х к g (f (x)) в домене Z Для всех ценностей в домене X .
Полезная аналогия, иллюстрирующая концепцию функциональной композиции, делает масло тостовым в духовке с ломтиком хлеба и холодного масла. Есть две возможные операции:
- Товка в духовке (Операция f)
- Распространение масла на самую широкую поверхность (операция G)
Если мы подразумеем хлеб в духовке сначала и распространили холодное масло на самую широкую поверхность того, что выходит из духовки, мы получаем ломтик поджаренного хлеба с холодное масло распространено Отказ
Если мы распространили холодное масло на самую широкую поверхность хлеба сначала и подражайте хлеб с холодным маслом, распространяемся в духовке, мы получаем кусочек поджаренного хлеба с теплое масло распространено Отказ
И мы знаем, что «Раскрытие холодного масла»
Из этих примеров мы можем интуитивно вывод сделать, что порядок применения функций имеет значение в функциональной композиции.
Точно так же в разработке трубопроводов данных мы часто пишем преобразования данных, применяя функции до результатов других функций. Способность составлять функции поощрять рефакторинг повторных сегментов кода в функции для ремонтопригодность и повторное использование .
Функционирует как объекты первого класса
Основная идея в функциональном программировании: Функции являются значениями .
Эта функция подразумевает, что функция может быть [2,3]:
- назначен переменной
- пропущено как параметр другим функциям
- возвращается как значение из других функций
Для этого на работу функции должны быть первоклассными объектами (и хранятся в структурах данных) в среде выполнения – как цифры, строки и массивы. Первоклассные функции поддерживаются на всех функциональных языках, включая SCALA, а также некоторые интерпретированные языки, такие как Python.
Функции высшего порядка
Ключевое значение, возникающее в результате концепции функций в качестве первого класса объектов, заключается в том, что композиция функционала может быть естественным образом выражена как Функция высшего порядка .
Функция более высокого порядка имеет по меньшей мере одно из следующих свойств:
- Принимает функции в качестве параметров
- Возвращает функцию как значение
Пример функции более высокого порядка – карта
Отказ
Когда мы смотрим на документацию для встроенной функции Python карта
заявлено, что карта
Функция принимает в другую функцию и итерацию в качестве входных параметров и возвращает итератор, который дает результаты [4].
В Scala каждое из классов сбора в упаковке Scala.Collions
и его подсветки содержат карта
Метод, который определяется следующими функциональными подписями на STALADOC [5]:
def map[B](f: (A) => B): Iterable[B] // for collection classes def map[B](f: (A) => B): Iterator[B] // for iterators that access elements of a collection
Что означает функциональные подписи, в том, что карта
принимает функцию входного параметра F
и F
Преобразует общий вход типа А
к полученному значению типа B
.
Понять каждое значение в коллекции целых чисел, Итеративный подход Состоит в том, чтобы пройти каждый элемент в коллекции, квадрат элемент и добавит результат к сбору результатов, которые продолжаются длиной с каждой итерацией.
- В Python:
def square(x): return x * x def main(args): collection = [1,2,3,4,5] # initialize list to hold results squared_collection = [] # loop till the end of the collection for num in collection: # square the current number squared = square(num) # add the result to list squared_collection.append(squared) print(squared_collection)
При итерационном подходе два изменения состояния происходят при каждой итерации в цикле:
-
в квадрате
Переменная удерживания результата, возвращаемого изквадрат
функция; а также - Коллекция проведения результатов квадратной функции.
Чтобы выполнить ту же операцию, используя Функциональный подход (то есть без использования мусорных переменных), карта
Функция может быть использована для «карты» каждого элемента в коллекции к новой коллекции с тем же количеством элементов, что и входной коллекцию – путем применения квадратной операции в каждый элемент и собирая результаты в новую коллекцию.
- В Python:
def square(x): return x * x def main(args): collection = [1,2,3,4,5] squared = list(map(square, collection)) print(squared)
- В Scala:
object MapSquare { def square(x: Int): Int = { x * x } def main(args: Array[String]) { val collection = List[1,2,3,4,5] val squared = collection.map(square) println(squared) } }
В обеих реализациях карта
Функция принимает функцию ввода, которая применяется к каждому элементу в коллекции значений и возвращает новую коллекцию, содержащую результаты. Как карта
Имеет свойство принятия другой функции в качестве параметра, это функция более высокого порядка.
Несколько быстрых сторонных нот о различиях между реализациями Python и Scala:
- Python
карта
В.С. Скалакарта
: ИТЕРАЛЬНАЯ ФУНКЦИЯ, такая какСписок
Необходимо преобразовать итератор, возвращенный из Pythonкарта
функционируйте в итерателе. В Scala нет необходимости в явном преобразовании результата изкарта
Функция до неуверенного, как все методы вПотенциал
Черта определены в терминах абстрактного метода,Итератор
, который возвращает экземплярИтератор
Черта, которая дает элементы коллекции один на одну [6]. - Как значения возвращаются из функции: пока
вернуть
Ключевое слово используется в Python, чтобы вернуть результат функции,вернуть
Ключевое слово редко используется в Scala. Вместо этого будет оценена последняя строка в декларации функции, и получаемое значение возвращается при определении функции в Scala. На самом деле, используявернуть
Ключевое слово в Scala не является хорошей практикой для функционального программирования, поскольку он отказывается от текущих вычислений и не является передача прозрачно [7-8].
Анонимные функции
При использовании функций более высокого порядка часто удобно иметь возможность вызывать параметры функции ввода с функциональными литералами или Анонимные функции Без необходимости определять их как названные объекты функции, прежде чем они могут быть использованы в функции более высокого порядка.
В Python анонимные функции также известны как лямбда выражения Из-за их корней в лямбда-исчислении. Анонимная функция создана с лямбда
Ключевое слово и обертки одно выражение без использования деф
или вернуть
ключевые слова. Например, квадрат
Функция в предыдущем примере в Python может быть выражена как анонимная функция в карта
Функция, где выражение лямбда лямбда х: х * х
используется в качестве параметра ввода функции для карта
:
def main(args): collection = [1,2,3,4,5] squared = map(lambda x: x * x, collection) print(squared)
В Scala анонимная функция определяется встроенным с =>
Обозначение – где аргументы функции определены слева от =>
Стрелка и экспрессия функции определяется справа от =>
стрелка. Например, квадрат
Функция в предыдущем примере в Scala может быть выражена как анонимная функция с (x: int) => x * x
Синтаксис и используется в качестве параметра ввода функций для карта
:
object MapSquareAnonymous { def main(args: Array[String]) { val collection = List[1,2,3,4,5] val squared = collection.map((x: Int) => x * x) println(squared) } }
Ключевым преимуществом использования анонимных функций в функциях более высокого порядка является то, что одноразовые функции одноразового выражения не нужно явно обернуться в определении именованного функции, следовательно, Оптимизация строк кода и Улучшение ремонтопригодности кода Отказ
Рекурсия как форма «функциональная итерация»
Рекурсия является формой самоссылки Функциональная композиция – рекурсивная функция принимает результаты (меньшие экземпляры) сама и использует их в качестве входных данных к другому экземпляру. Чтобы предотвратить бесконечную петлю рекурсивных звонков, A Базовый чехол Требуется в качестве завершения условия для возврата результата без использования рекурсии.
Классический пример рекурсии – это факториальная функция, которая определяется как продукт всех положительных целых чисел, меньше или равных целого числа n :
Существует два возможных итеративных подхода к реализации факториальной функции: использование для
петля и используя в то время как
петля.
- В Python:
def factorial_for(n): # initialize variable to hold factorial fact = 1 # loop from n to 1 in decrements of 1 for num in range(n, 1, -1): # multiply current number with the current product fact = fact * num return fact def factorial_while(n): # initialize variable to hold factorial fact = 1 # loop till n reaches 1 while n >= 1: # multiply current number with the current product fact = fact * n # subtract the number by 1 n = n - 1 return fact
В обоих итерационных реализациях факториальной функции два изменения состояния происходят при каждой итерации в цикле:
- Факториальная переменная, хранящая текущий продукт; а также
- Число умножено.
Для реализации факториальной функции с использованием Функциональный подход Рекурсия полезна при разделении проблемы в подблемы одного и того же типа – в этом случае продукт n и (N-1) !).
Основной рекурсивный подход для факториальной функции выглядит так:
- В Python:
def factorial(n): # base case to return value if n <= 0: return 1 # recursive function call with another set of inputs return n * factorial(n-1)
- В Scala:
def factorial(n: Int): Long = { if (n <= 0) 1 else n * factorial(n-1) }
Для основного рекурсивного подхода факториал 5 оценивается следующим образом:
factorial(5) if (5 <= 0) 1 else 5 * factorial(5 - 1) 5 * factorial(4) // factorial(5) is added to call stack 5 * (4 * factorial(3)) // factorial(4) is added to call stack 5 * (4 * (3 * factorial(2))) // factorial(3) is added to call stack 5 * (4 * (3 * (2 * factorial(1)))) // factorial(2) is added to call stack 5 * (4 * (3 * (2 * (1 * factorial(0))))) // factorial(1) is added to call stack 5 * (4 * (3 * (2 * (1 * 1)))) // factorial(0) returns 1 to factorial(1) 5 * (4 * (3 * (2 * 1))) // factorial(1) return 1 * factorial(0) = 1 to factorial(2) 5 * (4 * (3 * 2)) // factorial(2) return 2 * factorial(1) = 2 to factorial(3) 5 * (4 * 6) // factorial(3) return 3 * factorial(2) = 6 to factorial(4) 5 * 24 // factorial(4) returns 4 * factorial(3) = 24 to factorial(5) 120 // factorial(5) returns 5 * factorial(4) = 120 to global execution context
Для n Оценка факториальной функции включает 6 рекурсивных вызовов на факториальную функцию, включая базовый случай.
В то время как основной рекурсивный подход выражает факториальную функцию более тесно с ее определением (и, более естественно) по сравнению с итеративным подходом, он также использует больше памяти, поскольку каждый вызов функции нажата в стек вызовов в виде кадра стека и выскочил стек вызова Когда функция вызова возвращает значение.
Для больших значений n Рекурсион становится все глубже с большим количеством функциональных вызовов к себе, и больше места необходимо выделить на стек вызова. Когда пространство, необходимое для хранения функций, превышает мощность для стека вызовов, A Переполнение стека происходит!
Хвостовой рекурсион и оптимизация хвостовой связи
Чтобы предотвратить бесконечную рекурсию от причинения переполнения стека и сбой программы, некоторые оптимизации должны быть внесены в рекурсивную функцию, чтобы уменьшить потребление кадров стека в стеке вызовов. Возможный подход к оптимизации рекурсивной функции – переписать его как хвост рекурсивный функция.
Рекурсивная функция хвоста вызывает рекурсивно и не выполняет никаких вычислений после возврата рекурсивного вызова. Вызов функции – это хвостовой звонок Когда это не делает ничего, кроме возврата значения вызова функции.
В функциональных языках программирования, такие как Scala, Оптимизация вызова хвоста Обычно входит в компилятор для выявления вызовов хвоста и компилирование рекурсионов к итеративным циклам, которые не потребляют кадры стека для каждой итерации. Фактически, кадр стека может быть использован повторно для функции рекурсии, так и функции, называемую в функции рекурсии [1].
При этой оптимизации производительность пространства для функции рекурсии может быть уменьшена с O (n) к O (1) – Из одной кадра стека на звонок в одну кадру стека для всех вызовов [8]. Таким образом, рекурсивная функция хвоста является формой «функциональной итерации» со сравнительными характеристиками на петле.
Например, факториальная функция может быть выражена в виде хвостового рекурсии в Scala:
def factorialTailRec(n: Int): Long = { def fact(n: Int, product: Long): Long = { if (n <= 0) product else fact(n-1, n * product) } fact(n, 1) }
В то время как оптимизация Call-Call автоматически выполняется во время компиляции в Scala, это не так для Python. Более того, существует предел рекурсиона в Python (значение по умолчанию составляет 1000) в качестве меры профилактики против переполнения стека C COLL для реализации CPYPHON.
Что дальше: Функции высшего порядка
В этом посте мы узнаем о:
- Функциональная композиция
- Функции более высокого порядка как ключевое значение функционального программирования
- Рекурсия как форма «функциональная итерация»
Мы нашли замену «если-ж» еще? Не совсем, но теперь мы знаем, как писать «петли» в функциональном программировании, используя функции более высокого порядка и рекурсию хвоста.
В следующем посте я рассмотрю больше на функциях более высокого порядка и как их можно использовать при разработке функциональных трубопроводов данных.
Хотите более закуривные статьи в моем учебном путешествии в качестве профессионала данных? Проверьте мой сайт в https://ongchinhwee.me !
использованная литература
- Функциональное программирование в Scala Paul Chiusano и Rúnar Bjarnason
- Функциональное программирование, упрощенное Алвин Александр
- Функциональное программирование Python STEVEN F. LOTT, 2-е издание
- Встроенные функции – Python 3.9.6 Документация
- Scala Standard Библиотека 2.13.6 – Scala.Collections. Потенциал
- Черта, указывающая на границу |. Коллекции |. Scala Документация
- TPOLECAT – точка без возврата
- Не используйте возвращение в Scala? – Вопрос – Scala пользователи
- Рекурсия хвоста – функциональное программирование в ocaml
Обучение SCALA как Python Programmer (3 часть серии)
Оригинал: “https://dev.to/hweecat/functional-control-flow-writing-programs-without-loops-3cod”