ترکیب تابع (علوم کامپیوتر)

در علوم کامپیوتر ، **ترکیب تابع** یک عمل یا مکانیزم برای ترکیب توابع ساده برای ساخت توابع پیچیده تر است. مانند ترکیب معمول توابع در ریاضیات ، نتیجه هر تابع به عنوان آرگومان تابع بعدی و حاصل آخرین تابع نتیجه کل است.

برنامه نویسان اغلب توابعی را برای نتایج سایر توابع اعمال می کنند و تقریباً همه زبان های برنامه نویسی این امکان را دارند. در برخی موارد، ترکیب توابع به عنوان یک تابع در نوع خود جالب است که بعدا مورد استفاده قرار می گیرد. چنین تابعی همیشه قابل تعریف است، اما زبان هایی با توابع درجه یک آن را آسان تر می کنند.

توانایی نوشتن آسان توابع، توابع فاکتورگیری (تجزیه) را برای نگهداری و استفاده مجدد از کد تشویق می کند. به طور کلی، سیستم های بزرگ ممکن است با نوشتن برنامه های کامل ساخته شوند.

به بیان محدود، ترکیب تابع برای توابعی اعمال می شود که بر روی مقدار محدودی از داده ها کار می کنند، هر مرحله قبل از تحویل به مرحله بعدی، آن را به صورت متوالی پردازش می کند. توابعی که بر روی داده‌های بالقوه بی‌نهایت کار می‌کنند (یک جریان یا دیگر داده‌های کد ) به عنوان فیلتر شناخته می‌شوند و در عوض در یک خط لوله متصل می‌شوند که مشابه ترکیب تابع است و می‌تواند همزمان اجرا شود.

## تنظیم فراخوانی تابع
برای مثال، فرض کنید دو تابع **' و **' داریم، مانند  و  . نوشتن آنها به این معنی است که ابتدا  را محاسبه می کنیم, و سپس ** برای محاسبه  استفاده می کنیم . این مثال در زبان C است:
```c
foo←f∘g
```
اگر به نتیجه میانی نامی ندهیم، مراحل را می توان ترکیب کرد:
```c
z = f(g(x));
```
با وجود تفاوت در طول، این دو پیاده سازی نتیجه یکسانی را محاسبه می کنند. اجرای دوم تنها به یک خط کد نیاز دارد و به صورت محاوره ای به عنوان فرم "خیلی ترکیب شده" نامیده می شود. خوانایی و در نتیجه قابلیت نگهداری یکی از مزیت‌های فرم‌های بسیار ترکیبی است، زیرا به خطوط کد کمتری نیاز دارند و «مساحت سطح» برنامه را به حداقل می‌رسانند.  DeMarco و Lister به طور تجربی رابطه معکوس بین سطح و قابلیت نگهداری را تأیید می کنند.  از سوی دیگر، ممکن است استفاده بیش از حد از فرم های با ترکیب بالا امکان پذیر باشد. تودرتویی از توابع بیش از حد ممکن است اثر معکوس داشته باشد و کد را کمتر قابل نگهداری کند.

در یک زبان مبتنی بر پشته ، ترکیب عملکردی حتی طبیعی تر است: این ترکیب توسط الحاق انجام می شود و معمولاً روش اصلی طراحی برنامه است. مثال بالا در Forth :

g f

که هر آنچه قبلاً روی پشته بود را می گیرد، g و سپس f را اعمال کرده و نتیجه را روی پشته بگذارید. برای نماد ریاضی مربوطه، نماد ترکیب پسوند را ببینید.

## نامگذاری ترکیب توابع
حال فرض کنید ترکیب فراخوانی f() بر روی نتیجه g() اغلب مفید است و می‌خواهیم آن را foo() نامگذاری کنیم تا به تنهایی به عنوان یک تابع استفاده شود.

در اکثر زبان ها، ما می توانیم یک تابع جدید را تعریف کنیم که توسط ترکیب پیاده سازی شده است. مثال در C :
```c
float foo(float x) {
    return f(g(x));
}
```
(شکل طولانی با واسطه ها نیز کار می کند.) مثال در Forth :

 foo g f ;

در زبان هایی مانند C ، تنها راه برای ایجاد یک تابع جدید، تعریف آن در منبع برنامه است، به این معنی که توابع را نمی توان در زمان اجرا ترکیب کرد. با این حال، ارزیابی یک ترکیب دلخواه از توابع *از پیش تعریف شده* ممکن است:
```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

با استفاده از عملگر ترکیب داخلی (.) که می تواند به صورت *f بعد از g* یا *g تشکیل شده با f* خوانده شود.

عملگر ترکیب  خود را می توان در Haskell با استفاده از عبارت lambda تعریف کرد:
```haskell
(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = \x -> f (g x)
```
خط اول نوع (.) را توصیف می کند - یک جفت تابع  را می گیرد و یک تابع را برمی گرداند (عبارت لامبدا در خط دوم). توجه داشته باشید که Haskell نیازی به مشخصات دقیق انواع ورودی و خروجی f و g ندارد. a، b، c و x جایگاه‌بان‌ها هستند. فقط رابطه بین  مهم است (f باید آنچه را که g برمی گرداند بپذیرد). این باعث می شود (.) یک عملگر چند شکلی باشد .

## لیسپ
انواع Lisp ، به ویژه Scheme ، قابلیت تعویض کد و داده به همراه پردازش توابع، خود را بسیار مناسب برای تعریف بازگشتی از یک عملگر ترکیبی متغیر می‌سازد.
```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 - sqr) 5) ; ===> 0+5i
```


## APL
بسیاری از گویش‌های APL در ترکیب تابع با استفاده از نماد `∘` ساخته شده‌اند. این تابع مرتبه بالاتر ترکیب تابع را به کاربرد دوتایی تابع سمت چپ گسترش می دهد به طوری که `A f∘g B` `A fg B` باشد.
```apl
foo←f∘g
```
علاوه بر این، می توانید ترکیب تابع را تعریف کنید:
```apl
o←{⍺⍺ ⍵⍵ ⍵}
```
در لهجه ای که از تعریف درون خطی با استفاده از پرانتز پشتیبانی نمی کند، تعریف سنتی موجود است:
```apl
∇ r←(f o g)x
  r←f g x
∇
```


## راکو
Raku مانند Haskell دارای یک عملگر ترکیب تابع داخلی است، تفاوت اصلی این است که به صورت `∘` یا `o` نوشته می شود.
```perl
my &foo = &f ∘ &g;
```
همچنین مانند Haskell شما می توانید اپراتور را خودتان تعریف کنید. در واقع کد Raku مورد استفاده برای تعریف آن در پیاده سازی Rakudo است.
```
== Nim ==
Nim [[Uniform Function Call Syntax|از نحو فراخوانی تابع یکنواخت]] پشتیبانی می کند که امکان ترکیب تابع دلخواه را از طریق نحو متد فراهم می کند <code>.</code> اپراتور <syntaxhighlight lang="nim">
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)))
```


## پایتون
در پایتون ، راهی برای تعریف ترکیب برای هر گروه از توابع، استفاده از تابع کاهش است (از functools.reduce در پایتون استفاده کنید.&nbsp;3):
```python
# 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))
```


## جاوا اسکریپت
در جاوا اسکریپت می توانیم آن را به عنوان تابعی تعریف کنیم که دو تابع f و g را می گیرد و یک تابع تولید می کند:
```javascript
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)
```


## سی شارپ
در سی شارپ می توانیم آن را به عنوان یک متد Extension تعریف کنیم که Funcs f و g را می گیرد و یک Func جدید تولید می کند:
```csharp
// 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 به شما امکان می‌دهند خودتان یک عملگر باینری بسازید:
```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 معرفی شد: 
```ruby
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) به طور مستقیم ترکیب عملکرد را به جمع آوری بلوک های ساختمانی که به عنوان "موناد" در زبان برنامه نویسی هاسکل شناخته می شوند اعمال می کند.
* مایر (1988) مشکل استفاده مجدد نرم افزار را از نظر ترکیب پذیری حل کرد.
* ابادی و لامپورت (1993) به طور رسمی یک قانون اثبات برای ترکیب عملکردی را تعریف کردند که ایمنی و زندگی یک برنامه را تضمین می کند.
* کراخت (2001) یک شکل تقویت شده از ترکیب پذیری را با قرار دادن آن در یک سیستم سیمویتیک و اعمال آن به مشکل عدم وضوح ساختاری که اغلب در زبان شناسی محاسباتی رخ می دهد، شناسایی کرد.
*  نقش ترکیب پذیری را در جنبه های آنالوگ پردازش زبان طبیعی بررسی کرد.
* بر اساس بررسی  ، درمان رسمی ترکیب پایه اعتبار اجزای اجزای اجسام در زبان های برنامه نویسی بصری مانند IBM Visual Age برای زبان جاوا است.

## ترکیب در مقیاس بزرگ
کل برنامه‌ها یا سیستم‌ها را می‌توان به‌عنوان توابعی در نظر گرفت که در صورتی که ورودی‌ها و خروجی‌های آن‌ها به خوبی تعریف شده باشند، می‌توانند به راحتی ترکیب شوند.  خطوط لوله ای که امکان ترکیب آسان فیلترها را فراهم می کردند، چنان موفق بودند که به الگوی طراحی سیستم عامل تبدیل شدند.

رویه‌های ضروری با عوارض جانبی شفافیت ارجاعی را نقض می‌کنند و بنابراین کاملاً قابل ترکیب نیستند. با این حال، اگر "وضعیت جهان" قبل و بعد از اجرای کد را به عنوان ورودی و خروجی آن در نظر بگیریم، یک تابع تمیز دریافت می‌کنیم. ترکیب چنین توابعی با اجرای رویه ها یکی پس از دیگری مطابقت دارد. فرمالیسم موناد از این ایده برای ترکیب عوارض جانبی و ورودی/خروجی (I/O) در زبان های کاربردی استفاده می کند.

## همچنین ببینید

* Currying
* Functional decomposition
* https://en.wikipedia.org/wiki/Implementation_inheritanceارث پیاده سازی
* Inheritance semantics
* Iteratee
* https://en.wikipedia.org/wiki/Pipeline_(Unix)خط لوله (یونیکس)
* Principle of compositionality
* Virtual inheritance

## یادداشت ها


## مراجع
رده:توابع مرتبه بالاتر
رده:موضوعات مربوط به زبان‌های برنامه‌نویسی