Makkalot's Place

A polyglot programmer

Monad Yapısı

| Comments

Bu aralar monadların ne olduğunu anlamaya çalışıyorum. Henüz kafamda çok boşluk var ama en azından şimdiye kadar öğrendiklerimi pekiştirmek adına not almak iyi bir fikir olabilir.

Bir yapının monad olması için bir 2 tane fonksyonu olması gerekir.

  • m-bind fonksyonu : bir değer ve bu değerleri işleyecek bir fonksyon alır.
  • m-result fonksyonu : Geriye monadic bir değer döndürmesi gerekiyor (ileride açıklanacak).

Monad anladığım kadarıyla ardışıl olarak yapılması gereken işlemleri sistematize ve abstract etmek için düşünülmüş bir yöntem. Arama motorlarında “monad” aratınca genelde Haskell dili karşımıza çıksa da bu yapıları diğer dillere uygulamak mümkün.

Çoğu incelediğim örneklerde genelde clojure’ın let yapısı örnek olarak veriliyor. Örneğin :

1
2
3
4
5
6
(let
  [a 12
   b (inc a)]
  (* a b))

156

Clojure ile pek ilgilenmeyenler için açıklayacak olursak. Öncelikle a değişkenine 12 değeri veriliyor, daha sonra b değişkenine a ‘nın bir fazlası veriliyor, en sonunda da 2 değer çarpılıp sonuç geri veriliyor.

Bu tür bir yapıyı let olmadan yapmaya çalışsak nasıl olur ?

1
2
3
4
5
6
7
8
(defn m-bind [value func]
  (func value))

(m-bind 12 (fn [a]
              (m-bind (inc a) (fn [b]
                  (* a b)))))

156

Öncelikle yukarıdaki örnek biraz karmaşık görünebilir, biraz zaman verin gözünüz alışacaktır. Bu anlamda baktığımızda let işimizi oldukça kolaylaştırıyor gerçekten. Aslında let bir karar verme mekanizması gibi. [a 12] ifadesinde 12’yi alıp drek olarak a’ya atadı, bunun aksine 12 ile başka birşey de yapabilirdi. Örneğin bir listeye koymak gibi veya daha farklı işlemler. Pseudocode olarak şöyle görünebilir. Belki javascript syntaxı daha iyi gösterebilir :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function mbind(value, func){
  return func(value);
}

function mresult(v){
  return v;
}

var a = 12;
mbind(a, function(a){
  return mbind(a + 1, function(b){
          return mresult(a * b);
  });
});

Buradaki mbind ve mresult fonksyonları oldukça önemli. Bu 2 fonksyon monadların oluşması için gereken ana yapıları teşkil ediyor. Bir de monadların kuralları var ama kendi monadınızı yazmadığınız sürece pek gerekli olmuyorlar(zaten henüz açıklayacak kadar da bilmiyorum :) ).

Bir monadın diğerinden farkı bu mbind ve mresult fonksyonlarının implementasyonundan kaynaklanıyor. Yukarıda gösterdiğim monad identity monad diye geçiyor. Fakat hangi monad olursa olsun üzerinde yapılan işlemler aynı oluyor. Yani üzerinde sürekli m-bind fonksyonu çağırılıyor ve en sonunda m-result fonksyonu çağırılıp sonuç geriye döndürülüyor. Bu kısmın anlaşılması oldukça önemli.

Şimdi bir diğer monada bakalım maybe monadı. Maybe monadı, art arda yapılması gereken işlemlerden dönen sonucun geçersiz olması problemini çözüyor. Örnek olarak şöyle bir kaç işlemimiz olsun :

1
2
3
var user = get_user(user_id);
var profile = get_profile(user);
var tweets = get_tweets(profile);

Yukarıdaki kodda şöyle bir sıkıntı var. Eğer dönen değerlerden herhangi biri null ise sonucu işleyen fonksyonun kendi içinde bunu kontrol etmesi veya bunu biraraya getiren kodun buna bakması gerekiyor. Yani :

1
2
3
4
5
6
7
user = get_user(user_id);
if(!user)
  return null;
profile = get_profile(user);
if(!profile)
  return null;
tweets = get_tweets(profile);

Herhangi bir yerde bunu unutacak olursak, undefined hatası alacağız. Maybe monadı ile bu işlemi çözebiliriz :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function get_user(user_id){
  if (user_id === 1)
      return {name:"ali", profile:true};
  else if(user_id === 2)
      return {name:"osman", profile:false};
  else
      return null;
}

function get_profile(user){
  if(!user.profile)
      return null;
  if(user.name === "ali")
      return {tweets:["fp is fun"]};
  else
      return {tweets:null};
}

function get_tweets(profile){
  return profile.tweets;
}


function mbind(value, func){
  if(!value)
      return null;
  return func(value);
}

function mresult(value){
  return value;
}

Yukarıda mbind fonksyonun içeiriğini biraz değiştirdik. Eğer kendisine geçirilen değer null ise (geçersiz) işlemi durduruyoruz. Şimdi yukarıdaki kodu test edelim:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var user_id = 1;
user = mbind(user_id, get_user);
profile = mbind(user, get_profile);
tweets = mbind(profile, get_tweets);

> { name: 'ali', profile: true }
> { tweets: [ 'fp is fun' ] }
> [ 'fp is fun' ]

var user_id = 2;
user = mbind(user_id, get_user);
profile = mbind(user, get_profile);
tweets = mbind(profile, get_tweets);

> { name: 'osman', profile: false }
> null
> null

var user_id = 3;
user = mbind(user_id, get_user);
profile = mbind(user, get_profile);
tweets = mbind(profile, get_tweets);

> null
> null
> null

Görüldüğü gibi yukarıda bahsettiğim undefined hatalarından bu şekilde arınmış olduk. İlk örnekteki akışa uyduracak olursak :

1
2
3
4
5
6
7
8
9
mbind(1, function(id){
  return mbind(get_user(id), function(user){
      return mbind(get_profile(user), function(profile){
          return mbind(get_tweets(profile), function(tweets){
              return mresult(tweets);
          });
      });
  });
});

Bu örnekte de yine not edilmesi gereken ana akışın aynı olduğudur(mbind zinciri ve en sonunda mresult).

Şimdi mresult’un da içinde olduğu başka bir örneğe bakalım. Clojure’da for comprehension yapısı for ile sağlanıyor. Örneğin :

1
2
3
4
5
(for [a (range 5)
    b (range a)]
  (* a b))

 (0 0 2 0 3 6 0 4 8 12)

Yukarıdaki yapı iç içe girmiş for dongüsünü temsil ediyor. İçteki b dongüsü dıştaki a dongüsünden hızlı gidiyor. Buna benzer bir olayı monadlarla nasıl gerçekleştiririz ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function range(n){
  var result = [];
  for(var i = 0; i < n; i++){
      result.push(i);
  }

  return result;
}

function mbind(value, func){
  var result = []
  for(i in value){
      result.push(func(value[i]));
  }

  //flatten etmek lazım burada
  return result.reduce(function(a, b)
  {
      return a.concat(b);
  }, []);
}

function mresult(value){
  return [value];
}


mbind(range(5), function(a){
  return mbind(range(a), function(b){
  return mresult(a * b);
  });
});

> [ 0, 0, 2, 0, 3, 6, 0, 4, 8, 12 ]

Yukarıdaki örnek biraz complex görünebilir, ama aslında oldukça kolay(biraz inceledikten sonra). Burada mresult geriye bir liste döndürüyor. Yukarıdaki örneklerde aldığı değeri aynı şekilde geri döndürüyordu, burada durum biraz daha farklı. Bu noktada biraz tanım yapmak gerekiyor.

  • mresult tarafından dönen değer bizim monad değerimiz oluyor (monad value).
  • mbind‘in her zaman monad değeri döndürmesi gerekir. maybe monad değerin aynısını döndürüyordu, list monadı geriye liste döndürdü.
  • mbind fonksyonunun ilk parametresi de monad değeri olmak zorunda. Kısacası mbind monad alıp monad veriyor geriye. Bunun faydası, bu tür yapıların iç içe (chain) geçirilip tek fonksyon gibi kullanılıyor olması (başka bir blog yazısı).

Girdinin fazla uzamaması açısından burada kesiyorum. Bir diğer blog yazısında da state monadı hakkında öğrendiklerimi paylaşacağım.

İlgilenenler için bulduğum monad kaynakları :

Videolar :

  • Python ile Monadlar : link
  • Clojure ile Monadlar : link
  • Şekilli Monad Anlatımı : link

Diğer :

  • Clojure’da Monadlar : bu ve bu
  • Clojure’da monad kütüphanesi : link

Comments