Рубрики
Без рубрики

Функциональный «контрольный поток» – написание программ без петель

Реконструировать В моем предыдущем посте о ключевых принципах функционального программирования я объяснил, как … с меткой Python, Scala, Functional.

Обучение 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 .

Полезная аналогия, иллюстрирующая концепцию функциональной композиции, делает масло тостовым в духовке с ломтиком хлеба и холодного масла. Есть две возможные операции:

  1. Товка в духовке (Операция f)
  2. Распространение масла на самую широкую поверхность (операция G)

Если мы подразумеем хлеб в духовке сначала и распространили холодное масло на самую широкую поверхность того, что выходит из духовки, мы получаем ломтик поджаренного хлеба с холодное масло распространено Отказ

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

И мы знаем, что «Раскрытие холодного масла»

Из этих примеров мы можем интуитивно вывод сделать, что порядок применения функций имеет значение в функциональной композиции.

Точно так же в разработке трубопроводов данных мы часто пишем преобразования данных, применяя функции до результатов других функций. Способность составлять функции поощрять рефакторинг повторных сегментов кода в функции для ремонтопригодность и повторное использование .

Функционирует как объекты первого класса

Основная идея в функциональном программировании: Функции являются значениями .

Эта функция подразумевает, что функция может быть [2,3]:

  1. назначен переменной
  2. пропущено как параметр другим функциям
  3. возвращается как значение из других функций

Для этого на работу функции должны быть первоклассными объектами (и хранятся в структурах данных) в среде выполнения – как цифры, строки и массивы. Первоклассные функции поддерживаются на всех функциональных языках, включая SCALA, а также некоторые интерпретированные языки, такие как Python.

Функции высшего порядка

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

Функция более высокого порядка имеет по меньшей мере одно из следующих свойств:

  1. Принимает функции в качестве параметров
  2. Возвращает функцию как значение

Пример функции более высокого порядка – карта Отказ

Когда мы смотрим на документацию для встроенной функции 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)   

При итерационном подходе два изменения состояния происходят при каждой итерации в цикле:

  1. в квадрате Переменная удерживания результата, возвращаемого из квадрат функция; а также
  2. Коллекция проведения результатов квадратной функции.

Чтобы выполнить ту же операцию, используя Функциональный подход (то есть без использования мусорных переменных), карта Функция может быть использована для «карты» каждого элемента в коллекции к новой коллекции с тем же количеством элементов, что и входной коллекцию – путем применения квадратной операции в каждый элемент и собирая результаты в новую коллекцию.

  • В 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

В обоих итерационных реализациях факториальной функции два изменения состояния происходят при каждой итерации в цикле:

  1. Факториальная переменная, хранящая текущий продукт; а также
  2. Число умножено.

Для реализации факториальной функции с использованием Функциональный подход Рекурсия полезна при разделении проблемы в подблемы одного и того же типа – в этом случае продукт 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.

Что дальше: Функции высшего порядка

В этом посте мы узнаем о:

  1. Функциональная композиция
  2. Функции более высокого порядка как ключевое значение функционального программирования
  3. Рекурсия как форма «функциональная итерация»

Мы нашли замену «если-ж» еще? Не совсем, но теперь мы знаем, как писать «петли» в функциональном программировании, используя функции более высокого порядка и рекурсию хвоста.

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

Хотите более закуривные статьи в моем учебном путешествии в качестве профессионала данных? Проверьте мой сайт в https://ongchinhwee.me !

использованная литература

  1. Функциональное программирование в Scala Paul Chiusano и Rúnar Bjarnason
  2. Функциональное программирование, упрощенное Алвин Александр
  3. Функциональное программирование Python STEVEN F. LOTT, 2-е издание
  4. Встроенные функции – Python 3.9.6 Документация
  5. Scala Standard Библиотека 2.13.6 – Scala.Collections. Потенциал
  6. Черта, указывающая на границу |. Коллекции |. Scala Документация
  7. TPOLECAT – точка без возврата
  8. Не используйте возвращение в Scala? – Вопрос – Scala пользователи
  9. Рекурсия хвоста – функциональное программирование в ocaml

Обучение SCALA как Python Programmer (3 часть серии)

Оригинал: “https://dev.to/hweecat/functional-control-flow-writing-programs-without-loops-3cod”