3 Mart 2015 Salı

Closure

First Class Function Nedir?
Closure konusunu anlamak için First Class Function ile başlamak gerekir. Bu tür kavramların hepsi Functional Programming'den geliyor. Dolayısıyla bu konuya da göz atabilirsiniz.

Closure Nedir?
En basit tanımı şöyle :
“In computer science, a closure is a first-class function with free variables that are bound in the lexical environment.”

Closure ve Lambda
Closure ve lambda yakından ilişkilidir çünkü bir çok programlama dili first-class function gerçekleştirimini lambda ile sağlıyor.

Java
Basit bir closure örneği şöyle. one nesnesinin final olması gerekir.
Runnable f = () -> one.bar();

C#
First class function tanımlamak için

1. Func/delegate ikilisi kullanılabilir.
Func<string,string> myFunc = delegate (string var1)
                              {
                                  return "I'm called";
                              };

2. Func/Lambda ikilisi kullanılabilir.
Func<bool> anyCarDoesNotHaveDoor = () => { 
    ...
    return false; 
};
Func değişkenine bir sınıfın metodunu da atanabilir. Ancak o zaman klasik anlamda First Class Function olmadığı için örnek olarak almadım.

Lambda kendi başında bir First Class Function değildir. Hatta direkt çalıştırılamaz! Çalıştırmak için Func'a cast etmek gerekir.
int n = (()=>5)(); //doesn't work
int n = ((Func<int>)(()=>5))(); //works

C++
Lambda tanımı C#'takine çok benziyor sadece parametrelerin sırası ters. C#'ta önce (parametreler) sonra => işareti gelirken, C++'ta önce [] işareti, sonra (parametreler) geliyor. Yani  C++'ta Lambda kullanmak için [] (parametreler) {...} şeklinde kodlama yapmak lazım.

Lambda yazısına da bakabilirsiniz.

function pointer/lambda ikilisi
Lambda eğer capture işlemi yapmıyorsa function pointer'a dönüşebilir.
A lambda can only be converted to a function pointer if it does not capture
Bu kod parçası çalışır.
typedef bool(*DecisionFn)(int);

Decide greaterThanThree{ []( int x ){ return x > 3; } };
Ama bu kod parçası derlenmez.
int x = 5;
Decide greaterThanThree{ [x](){ return x > 3; } };

std::function/lambda ikilisi
Lambdalar std::function isteyen herhangi bir metoda geçilebilir. Lambdaları saklamak için de std::function kullanılır.

class MyClass { 
public:
    std::function<void()> function;
    MyClass(std::function<void()> f) : function(f) {
        // Handled in initializer list
    }
};

int main() {
    MyClass([]() -> void {
        printf("hi")
    }) mc; // Should be just fine.
}
std::function klasik bir fonksiyon gibi çağrılır.
std::function<int()> myFunction = []() { return 0; }
myFunction(); // Returns 0;

Hiç parametre almayan lambda örneği. 
Lambdaların return type tanımlamadığına dikkat! Return type yerine auto kelimesi kullanılıyor. Derleyici döndürülen parametre tipini kendisi bulur.
auto func = [] () { cout << "Hello world"; };
Tek parametre alan lamda örneği
auto func = [] (const string& addr) { return addr.find( ".org" ) != string::npos; } 
Mutable Lambda
Lambda'lar otomatik olarak const() olarak üretilirler. Yani bir değişkeni ref. olarak capture etse bile halen const ref olarak görür. Yani değişmesine izin vermez. Const'tan kurtulmak için lambda mutable olmalıdır.
auto lambda = [ capturedStr = std::move(str) ] () mutable {
//                                             ^^^^^^^^^^
    cout << *capturedStr.get() << endl;
    auto str2 = std::move(capturedStr);
};

Lambda Capture Türleri
Şu şekilde kullanılıyor. Açıklamaları burada. Yakalanan değişkenler const değildir!
capture:
  identifier
  & identifier
  this
Eğer yakalanan lokal değişken static ise, & veya = kullanmamız farketmez. Capture by Reference ile çalışır.

1. Capture By This
[this] lambdanın içinde bulunduğu sınıfın lambda içinde kullanılabilmesini sağlar.

2. Capture By Reference
Closure yazısına taşıdım.

3. Capture By Value
[=] Lambda herşeyi "by value" olarak yakalar. 
Basit bir örnek verelim. Şöyle bir lambda'mız olsun.
int val = 6;
fun = [=](){return val;};
Bu lambda derleyici tarfından her şeyi copy olarak yakalayan bir struct'a dönüştürülür.
int val = 6;
struct __anonymous_struct_line_8 {
    int val;
    __anonymous_struct_line_8(int v) : val(v) {}

    int operator() () const {
        return val; // returns this->val
    }
};

fun = __anonymous_struct_line_8(val);
Lambda içinde kullanılan nesnenin referans olarak geçip geçmemesi önemli değildir. Şöyle bir sınıfımız olsun.
class C
{
public:
    C()
    {
        i = 0;
    }

    C(const C & source)
    {
        std::cout << "Copy ctor called\n";
        i = source.i;
    }

    int i;
};
Bu sınıfı kullanan bir test metodumuz olsun
void test(C & c)
{
    c.i = 20;

    auto lambda = [=]() mutable {

        c.i = 55;
    };
    lambda();

    std::cout << c.i << "\n";
}
Test metodu içinde lambda çağırılır ve parametre olarak geçilen nesneye 55 değeri atanır.
Şimdi test metodunu çağıralım
int main(int argc, char * argv[])
{
    C c;
    test(c);

    getchar();
}
Sonuç olarak şunları görürüz. Çünkü lambda parametre referans olarak kullanılsa bile parametrenin bir kopyasını alır ve onu değiştirir.
Copy ctor called
20


2 yorum:

  1. Kolay gelsin, son misaldeki mutable'ın başvurudan farkı nedir, teşekkürler

    YanıtlaSil
  2. Mutable kelimesi C++'a mahsus. Lambda kodunda yakalanan parametrenin, lambda içinde dğeiştirilebileceğini belirtir.

    YanıtlaSil