Функциональная композиция (информатика)
В информатике — композиция функций это действие или механизм объединения простых функций для создания более сложных. Как и обычная композиция функций в математике , результат каждой функции передается как аргумент следующей, а результат последней является результатом целой.
Программисты часто применяют функции к результатам других функций, и почти все языки программирования допускают это. В некоторых случаях композиция функций интересна как отдельная функция, которую можно использовать позже. Такую функцию всегда можно определить, но языки с первоклассными функциями упрощают эту задачу.
Возможность легко компоновать функции поощряет факторизацию (разбиение) функций для удобства сопровождения и повторного использования кода . В более общем смысле, большие системы могут быть построены путем составления целых программ.
В узком смысле композиция функций применяется к функциям, которые работают с конечным объемом данных, при этом каждый шаг последовательно обрабатывает их перед передачей следующему. Функции, которые работают с потенциально бесконечными данными ( потоком или другими кодовыми данными ), известны как фильтры и вместо этого подключаются в конвейер , который аналогичен композиции функций и может выполняться одновременно .
Составление вызовов функций [ править ]
Например, предположим, что у нас есть две функции f и g , как в z = f ( y ) и y = g ( x ) . Их составление означает, что мы сначала вычисляем y = g ( x ) , а затем используем y для вычисления z = f ( y ) . Вот пример на языке C :
float x, y, z;
// ...
y = g(x);
z = f(y);
Шаги можно объединить, если не давать имя промежуточному результату:
z = f(g(x));
Несмотря на различия в длине, эти две реализации вычисляют один и тот же результат. Вторая реализация требует только одной строки кода и в просторечии называется «сложно составленной» формой. Читабельность и, следовательно, удобство сопровождения является одним из преимуществ сложных форм, поскольку они требуют меньше строк кода, что минимизирует «поверхность» программы. [1] ДеМарко и Листер эмпирически подтверждают обратную зависимость между площадью поверхности и ремонтопригодностью. [2] С другой стороны, возможно злоупотребление сложными формами. Вложенность слишком большого количества функций может иметь противоположный эффект, делая код менее удобным в сопровождении.
В языке, основанном на стеке , функциональная композиция еще более естественна: она выполняется путем конкатенации и обычно является основным методом проектирования программы. Приведенный выше пример на Форте :
g f
Который возьмет все, что было в стеке раньше, примените g, затем f и оставьте результат в стеке. см. в постфиксной композиции Соответствующие математические обозначения .
Именование состава функций [ править ]
Теперь предположим, что комбинация вызова f() по результату g() часто оказывается полезной, и мы хотим назвать ее foo(), чтобы использовать ее как самостоятельную функцию.
В большинстве языков мы можем определить новую функцию, реализуемую композицией. Пример на языке C :
float foo(float x) {
return f(g(x));
}
(длинная форма с промежуточными элементами также подойдет.) Пример на Форте :
: foo g f ;
В таких языках, как C , единственный способ создать новую функцию — это определить ее в исходном коде программы, а это означает, что функции не могут быть составлены во время выполнения . оценка произвольного состава предопределенных Однако возможна функций:
#include <stdio.h>
typedef int FXN(int);
int f(int x) { return x+1; }
int g(int x) { return x*2; }
int h(int x) { return x-3; }
int eval(FXN *fs[], int size, int x)
{
for (int i=0; i<size; i++) x = (*fs[i])(x);
return x;
}
int main()
{
// ((6+1)*2)-3 = 11
FXN *arr[] = {f,g,h};
printf("%d\n", eval(arr, 3, 6));
// ((6-3)*2)+1 = 7
arr[2] = f; arr[0] = h;
printf("%d\n", eval(arr, 3, 6));
}
Первоклассный состав [ править ]
В языках функционального программирования композиция функций может быть естественным образом выражена как более высокого порядка функция или оператор . На других языках программирования вы можете написать свои собственные механизмы для выполнения композиции функций.
Хаскелл [ править ]
В Haskell пример foo = f ∘ g приведенный выше выглядит следующим образом:
foo = f . g
используя встроенный оператор композиции (.), который можно прочитать как f после g или g, составленный с помощью f .
Сам оператор композиции ∘ можно определить в Haskell с помощью лямбда-выражения :
(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = \x -> f (g x)
Первая строка описывает тип (.) — он принимает пару функций f , g и возвращает функцию (лямбда-выражение во второй строке). Обратите внимание, что Haskell не требует указания точных типов ввода и вывода f и g; a, b, c и x — заполнители; имеет значение только отношение между f и g (f должно принять то, что возвращает g). Это делает (.) полиморфным оператором.
Лисп [ править ]
Варианты Lisp , особенно Scheme , взаимозаменяемость кода и данных вместе с обработкой функций чрезвычайно хорошо подходят для рекурсивного определения вариативного композиционного оператора.
(define (compose . fs)
(if (null? fs) (lambda (x) x) ; if no argument is given, evaluates to the identity function
(lambda (x) ((car fs) ((apply compose (cdr fs)) x)))))
; examples
(define (add-a-bang str)
(string-append str "!"))
(define givebang
(compose string->symbol add-a-bang symbol->string))
(givebang 'set) ; ===> set!
; anonymous composition
((compose sqrt negate square) 5) ; ===> 0+5i
АПЛ [ править ]
Многие диалекты APL имеют встроенную композицию функций с использованием символа ∘
.
Эта функция высшего порядка расширяет композицию функций до диадического применения левой функции, так что A f∘g B
является A f g B
.
foo←f∘g
Кроме того, вы можете определить состав функций:
o←{⍺⍺ ⍵⍵ ⍵}
В диалекте, который не поддерживает встроенное определение с использованием фигурных скобок, доступно традиционное определение:
∇ r←(f o g)x
r←f g x
∇
Raku [ edit ]
Raku , как и Haskell , имеет встроенный оператор композиции функций, основное отличие состоит в том, что он пишется как ∘
или o
.
my &foo = &f ∘ &g;
Также, как и в Haskell, вы можете определить оператор самостоятельно. Фактически, ниже приведен код Raku, используемый для его определения в реализации Rakudo .
# the implementation has a slightly different line here because it cheats
proto sub infix:<∘> (&?, &?) is equiv(&[~]) is assoc<left> {*}
multi sub infix:<∘> () { *.self } # allows `[∘] @array` to work when `@array` is empty
multi sub infix:<∘> (&f) { &f } # allows `[∘] @array` to work when `@array` has one element
multi sub infix:<∘> (&f, &g --> Block) {
(&f).count > 1
?? -> |args { f |g |args }
!! -> |args { f g |args }
}
# alias it to the "Texas" spelling ( everything is bigger, and ASCII in Texas )
my &infix:<o> := &infix:<∘>;
Nim [ edit ]
Nim поддерживает единый синтаксис вызова функций , что позволяет создавать произвольную композицию функций через синтаксис метода. .
оператор. [3]
func foo(a: int): string = $a
func bar(a: string, count: int): seq[string] =
for i in 0 ..< count:
result.add(a)
func baz(a: seq[string]) =
for i in a:
echo i
# equivalent!
echo foo(5).bar(6).baz()
echo baz(bar(6, foo(5)))
Питон [ править ]
В Python способ определения композиции для любой группы функций — использование функции сокращения (используйте functools.reduce в Python 3):
# Available since Python v2.6
from functools import reduce
from typing import Callable
def compose(*funcs) -> Callable[[int], int]:
"""Compose a group of functions (f(g(h(...)))) into a single composite func."""
return reduce(lambda f, g: lambda x: f(g(x)), funcs)
# Example
f = lambda x: x + 1
g = lambda x: x * 2
h = lambda x: x - 3
# Call the function x=10 : ((x-3)*2)+1 = 15
print(compose(f, g, h)(10))
JavaScript [ править ]
В JavaScript мы можем определить его как функцию, которая принимает две функции f и g и создает функцию:
function o(f, g) {
return function(x) {
return f(g(x));
}
}
// Alternatively, using the rest operator and lambda expressions in ES2015
const compose = (...fs) => (x) => fs.reduceRight((acc, f) => f(acc), x)
С# [ править ]
В C# мы можем определить его как метод расширения, который принимает Funcs f и g и создает новый Func:
// Call example:
// var c = f.ComposeWith(g);
//
// Func<int, bool> g = _ => ...
// Func<bool, string> f = _ => ...
public static Func<T1, T3> ComposeWith<T1, T2, T3>(this Func<T2, T3> f, Func<T1, T2> g) => x => f(g(x));
Руби [ править ]
Такие языки, как Ruby, позволяют вам самостоятельно создавать бинарный оператор:
class Proc
def compose(other_fn)
->(*as) { other_fn.call(call(*as)) }
end
alias_method :+, :compose
end
f = ->(x) { x * 2 }
g = ->(x) { x ** 3 }
(f + g).call(12) # => 13824
Однако в Ruby 2.6 появился собственный оператор композиции функций: [4]
f = proc{|x| x + 2}
g = proc{|x| x * 3}
(f << g).call(3) # -> 11; identical to f(g(3))
(f >> g).call(3) # -> 15; identical to g(f(3))
Исследование [ править ]
Понятия композиции, включая принцип композиционности и композиционности , настолько распространены, что отдельные направления исследований развивались отдельно. Ниже приводится выборка исследований, в которых понятие композиции занимает центральное место.
- Стил (1994) непосредственно применил функциональную композицию к сборке строительных блоков, известных как « монады » в языке программирования Haskell .
- Мейер (1988) рассмотрел проблему повторного использования программного обеспечения с точки зрения компонуемости.
- Абади и Лампорт (1993) формально определили правило доказательства функциональной композиции, которое гарантирует безопасность и жизнеспособность программы.
- Крахт (2001) определил усиленную форму композиционности, поместив ее в семиотическую систему и применив ее к проблеме структурной двусмысленности, часто встречающейся в компьютерной лингвистике .
- ван Гелдер и Порт (1993) исследовали роль композиционности в аналоговых аспектах обработки естественного языка.
- Согласно обзору Гиббонса (2002) , формальная обработка композиции лежит в основе проверки сборки компонентов в языках визуального программирования, таких как IBM Visual Age для языка Java .
Масштабная композиция [ править ]
Целые программы или системы можно рассматривать как функции, которые можно легко составить, если их входные и выходные данные четко определены. [5] Конвейеры, позволяющие легко компоновать фильтры, оказались настолько успешными, что стали образцом проектирования операционных систем.
Императивные процедуры с побочными эффектами нарушают ссылочную прозрачность и поэтому не могут быть полностью компонуемы. Однако если рассматривать «состояние мира» до и после запуска кода как его входные и выходные данные, можно получить чистую функцию. Состав таких функций соответствует последовательному запуску процедур. Формализм монады использует эту идею для включения побочных эффектов и ввода/вывода (I/O) в функциональные языки.
См. также [ править ]
- каррирование
- Функциональная декомпозиция
- Наследование реализации
- Семантика наследования
- итерируемый
- Конвейер (Unix)
- Принцип композиционности
- Виртуальное наследование
Примечания [ править ]
- ^ Кокс (1986) , стр. 15–17.
- ^ ДеМарко и Листер (1995) , стр. 133–135.
- ^ «Руководство Nim: Синтаксис вызова методов» . nim-lang.org . Проверено 17 августа 2023 г.
- ^ «Выпущен Ruby 2.6.0» . www.ruby-lang.org . Проверено 4 января 2019 г.
- ^ Раймонд (2003)
Ссылки [ править ]
- Абади, Мартин ; Лэмпорт, Лесли (1993), «Составление спецификаций» (PDF) , Транзакции ACM в языках и системах программирования , 15 (1): 73–132, doi : 10.1145/151646.151649 .
- Кокс, Брэд (1986), Объектно-ориентированное программирование, эволюционный подход , Ридинг, Массачусетс: Аддисон-Уэсли, ISBN 978-0-201-54834-1 .
- Дауме, Hal III, еще одно руководство по Haskell .
- ДеМарко, Том ; Листер, Тим (1995), «Разработка программного обеспечения: современное состояние и состояние практики», в книге ДеМарко, Том (редактор), « Почему программное обеспечение стоит так дорого» и другие загадки информационного века , Нью-Йорк, Нью-Йорк: Дорсет Хаус, ISBN 0-932633-34-Х .
- ван Гелдер, Тимоти ; Порт, Роберт (1993), «За пределами символического: пролегомены к Камасутре композиционности», в Хонаваре, Васант ; Ур, Леонард (ред.), Обработка символов и коннекционистские модели в искусственном интеллекте и познании: шаги к интеграции , Academic Press .
- Гиббонс, Джереми (2002), Арбаб, Фархад; Талкотт, Кэролайн (ред.), Proc. 5-я Международная конференция по координационным моделям и языкам (PDF) , Конспекты лекций по информатике, том. 2315, Springer-Verlag, стр. 339–350, doi : 10.1007/3-540-46000-4\_18 .
- Корн, Генри; Либери, Альберт (1974), Элементарный подход к функциям , Нью-Йорк, Нью-Йорк: McGraw-Hill, ISBN 0-07-035339-5 .
- Крахт, Маркус (2001), «Строгая композиционность и грамматики буквальных движений», Proc. 3-я Международная конференция по логическим аспектам компьютерной лингвистики , Конспекты лекций по информатике, том. 2014, Springer-Verlag, стр. 126–143, номер документа : 10.1007/3-540-45738-0_8 .
- Мейер, Бертран (1988), Объектно-ориентированное создание программного обеспечения , Нью-Йорк, Нью-Йорк: Prentice Hall, стр. 13–15, ISBN. 0-13-629049-3 .
- Миллер, Джордж А. (1956), «Магическое число семь плюс-минус два: некоторые ограничения нашей способности обрабатывать информацию» , Psychoological Review , 63 (2): 81–97, doi : 10.1037/h0043158 , hdl : 11858/00-001M-0000-002C-4646-B , PMID 13310704 , заархивировано из оригинала 19 июня 2010 г. , получено 2 мая 2010 г.
- Пирс, Бенджамин К.; Тернер, Дэвид Н. (2000), «Pict: язык программирования, основанный на пи-исчислении», Доказательство, язык и взаимодействие: эссе в честь Робина Милнера , серия «Основы вычислений», Кембридж, Массачусетс: MIT Press, стр. . 455–494, ISBN. 0-262-16188-5 .
- Рэймонд, Эрик С. (2003), «1.6.3 Правило композиции: создавайте программы, которые будут связаны с другими программами» , Искусство программирования для Unix , Аддисон-Уэсли, стр. 15–16, ISBN 978-0-13-142901-7 .
- Стил, Гай Л. младший (1994), «Создание интерпретаторов путем составления монад» , Proc. 21-й симпозиум ACM по принципам языков программирования , стр. 472–492, doi : 10.1145/174675.178068 .