C# 6.0 – Desen Eşleştirme (Taslak Önerisi)

   Özellikle Roslyn projesinin .NET Compiler Platform adıyla açık kaynak olarak geliştirilmeye devam etmesi ardından C# 6 çalışmaları da ivmelendi. Açık kaynak kod geliştiricilerinden destek gelmesi ve C#’a yeni özellikler katma isteği bu ivmenin dinamikleri arasında yer alıyor. Çalışmalar hızla devam ederken, geçtiğimiz hafta içerisinde yeni bir gelişme yaşandı. C# diline yeni bir özellik eklenmesi konusunda yeni bir taslak önerisi sunuldu; Pattern Matching (Desen Eşleştirme).

Uyari[6]Bu makalede paylaştığım Record Class henüz taslak aşamasındaki bir öneri olup C#’a dahil edilip edilmeyeceği, C# 6.0 ile gelip-gelmeyeceği çok kısa bir süre önce tartışmaya açılmıştır. İlerleyen zamanlarda topluluklar içerisindeki fikir alış-verişleri neticesinde nihayi şeklini alırken değişikliğe uğrama olasılığı yüksektir.

   Henüz kesin olarak C# eklenme kararı alınmamış olan ve tartışılması amacıyla açılan Pattern Matching taslak önerisine bu adresten ulaşarak inceleyebilirsiniz. Öncelikle makalemde hatırlarsanız Record Class konusuna değinmiştim. Bu defa sıra Pattern Matching konusunda. Herşeyden önce şunu belirtmem gerekir ki F# ve Scala gibi dillerden tanıdığımız Pattern Matching yaklaşımının C# kazandırılması gerçekten heyacan verici bir gelişme olacaktır.

   Hatırlarsanız bir önceki makalemde yukarıda linkini paylaştığım taslak önerisinde yer alan Record ve Record Class kavramlarından bahsetmiştim. Taslak önerisinde yer alan iki konudan bir diğeri ise özellikle fonksiyonel programlama dillerinden gelen bir konsept olan desen eşleştirme. Desen eşleştirme denilince pek çoklarının aklına switch cümlecikleri gelse de aslında bundan çok daha güçlü bir consept ile karşı karşıyayız.

   Bir önceki makalemizde Record sınıfı için derleyici tarafından üretilen kodlar arasında yer alan is operatör tanımını hatırlayalım;

public static bool operator is(Cartesian c, out double x, out double y) {
  x = c.X;
  y = c.Y;
  return true;
}

  Dikkat ederseniz operatorümüz boolean bir değer dönerek fonksiyona verilen ilk parametrenin türünü teyit etmekte. Öte yandan out parametreleri ile x ve y değerleride karşılaştırma sonucunda dönemekte. Şimdi, bu noktadan sonra işler biraz karışacak. Bu yüzden dikkatle takip etmenizi tavsiye ederim 😉

   Taslağın devamında is operatörü için genişletme metodları gibi bir yaklaşım düşünülerek operatör tanımının sadece ilgili sınıf içerisinde değil, aynı zamanda dışarıdaki static bir sınıf içerisinde de yapılabilmesi sağlanmıştır; ki bu durumda önemli bir esnekliğe sahip oluyoruz. Bu durum için aşağıdaki şekilde Polar koordinatlarında “is” tanımı yapılmıştır;

public static class Polar {
  public static bool operator is(
      Cartesian c, out double R, out double Theta) {
      R = Math.Sqrt(c.X*c.X + c.Y*c.Y);
      Theta = Math.Atan2(c.Y, c.X);
      return c.X != 0 || c.Y != 0;
  }
}

devamında kullanımı aşağıdaki şekilde örneklenmiştir;

var c = Cartesian(3, 4);
if (c is Polar(var R, *)) Console.WriteLine(R);

   (ipucu: konsola çıktı olarak 5 değeri yazılacaktır 🙂 )

  Son örneğimizde yer alan if cümleciğinde derleyici bizim adımıza bir kaç iş yapmakta. Öncelikle Polar sınıfı içerisindeki is operatörünün ilk parametresine bakarak c değişkeni ile işlem yapıp yapamayacağına karar verecektir. Örneğimizde is operatörümüz ilk parametresi olarak Cartesian sınıfını kabul ettiği ve c değişkenide bu türde olduğu için kontrol true değer dönecek ve fonksiyon çağrısı yapılacaktır. Fonksiyon çağrısındaki notasyonu şimdilik dikkat almayalım,makalemin sonunda detaylandıracağım. Fonksiyon normal bir şekilde çalışacak ve R ve Theta değerleri hesaplanacaktır. Fonksiyon dönüşünde sadece R parametresi karşılandığı için bu değer alınacak ve konsola yazılacaktır. Yani artık is operatörü ile sadece karşılaştırma işlemi yapmaktan öteye geçmiş oluyoruz. Tek kalemde; “kartezyen koordinat sistemdeki c noktasının Polar (kutupsal) koordinat sistemindeki karşılığı var ise yarıçap bilgisini bana ver” diyebiliyoruz… Yani; Kartezyen c nesnesi üzerinde Polar bir nesneymiş gibi işlem yapabiliyoruz.

   Burada dikkat etmeniz gereken nokta; is operatörüne geçtiğimiz değişkenin yaşam ömrü. Bu örneğimizde R değişkeni if bloğu ile sınırlıdır. Dolayısıyla blok dışında R değişkenine erişmek istediğimizde derlenme-zamanı hata alacağız.

   Örneğimizdeki if ifadesinde yer alan Polar fonksiyon çağrı parametrelerini şimdilik göz ardı edelim, aşağıdaki detaylar ardından onlara tekrar geri döneceğiz.

 

   Taslak öneride is ve switch ifadelerinin aşağıdaki şekilde güncellendiğini görebiliriz;

relational-expression:
    relational-expression   is  complex-pattern
    relational-expression   is  type
   
complex-pattern:
    constant-pattern
    type identifier
    recursive-pattern
    recursive-pattern identifier
   
recursive-pattern:
    type ( subpattern-listopt  )
   
subpattern-list:
    subpattern
    subpattern-list , subpattern
   
subpattern:
    argument-nameopt  pattern
   
pattern:
    simple-pattern
    complex-pattern
   
simple-pattern:
    wildcard-pattern
    var identifier
   
wildcard-pattern:
*

constant-pattern:
    constant-expression

   Bu durumda artık sabit ifadeleri switch cümlecikleri yerine daha karmaşık ifadeler de kullanabiliriz. Şimdi parça parça yukarıdaki ifadenin ne anlama geldiğini inceleyelim;

complex-pattern:
    constant-pattern
    type identifier
    recursive-pattern
    recursive-pattern identifier

Sabit Deseni

   Constant Pattern (Sabit Desen) yabancı olmadığımız, halen kullanmakta olduğumuz bir desendir. Bu desende verilen ifade sabit bir değer ile test edilir. Buna göre;

e is c

ifadesi

object.Equals(e, c)

şeklinde kontrol edilecektir. İfadenin doğru olması durumunda true değer dönülecektir.

Tür Deseni 

   Taslak öneride, sabit desen dışında type identifier formatında bir ifade alabilmekte deniliyor. Bu ifade şekline Type Pattern (Tür deseni) adı verilmekte. Yeni bir desen türü olan Type Pattern’da öncelikle değişken verilen tür için test edilir. Test’in başarılı olması durumunda değişken belirtilen tür’e dönüştürülür. Bu dönüştürme sonucu ifade içerisinde belirtilen identifier ile adlandırılarak kod içerisinde kullanılabilmesi sağlanır. Bunun için güzel bir örnek verilmiş.

var v = expr as Type;    
if (v != null) {
    // code using v
}

gibi bir ifade yerine artık aşağıdaki bir kullanıma gidebiliriz.

if (expr is Type v) {
    // code using v
}

daha netleşmesi adına;

int? x = 3;
if (x is int v) {
    // code using v
}

   Bu örnekte; nullable int türündeki x değişkeni 3 değerine sahiptir. if içerisinde x değişkeninin bir tamsayı olup olmadığı kontrol ediliyor; ki bu durumda sahip. Kontrolün başarılı olması ardından int türünden v adıyla bir değişken tanımlanarak x değişkeninin değeri atanmakta. Bu noktadan itibaren if bloğu içerisinde v değişkenini kullanabiliriz.

Var Deseni

   Taslağa göre, Var Pattern olarak adlandırılan var identifier deseni her zaman için true değer dönen basit bir desendir. Bu ifadede verilen ifade sonucu var identifier ile belirtilen isimle derleyici tarafından oluşturulan değişkene atanmaktadır.

Wildcard Deseni

      Taslağa göre, Wildcard Pattern olarak adlandırılan * desenin aynı var pattern gibi her zaman için true değer dönmektedir. Var pattern’dan farkı ise burada derleyiciye dönen değerin bizim için bir önemi olmadığını ve bunun için bizim bir değişken tanımlaması yapmayacağımızı belirtmemizdir.

Özyenileme Deseni

   Yukarıda paylaştığım var ve wildcard desenlerinin ilk bakışta size bir şey ifade etmediğini tahmin ediyorum. Üzülmeyin, yalnız değilsiniz… okumaya devam edin 🙂 . Taslakta yer alan desenlerden birisi de Recursive Pattern’dır.

recursive-pattern:
    type ( subpattern-listopt  )

   Özyenileme deseni olarak Türkçe’ye çevirebileceğim bu desende yukarıda da görebileceğiniz gibi tanımlı olan is operatör çağrısının özyenileme ile iç içe çalıştırılabilmesi öngörülmektedir.

   Recursive Pattern’a örnek için makalemin başına geri dönelim;

var c = Cartesian(3, 4);
if (c is Polar(var R, *)) Console.WriteLine(R);

   Hatırlarsanız bu ifade sonucunda konsola 5 yazasağını söylemiştim. Yukarda paylaştığım desenler üzerinden gidersek; öncelikle en dışta bir Recursive Pattern yer aldığını görebiliriz :

Polar(var R, *)

   İncelemeye devam ettiğimizde bu ifadenin içerisinde de var pattern ve wildcard patten olduğunu görebiliriz. Dolayısıyla;

c is Polar(var R, *)

ifadesiyle derleyiciye kartezyen c’nin polar sistemde karşılığı olup olmadığını, karşılığı var ise R adıyla bir değişken oluşturup Polar sistemdeki R değerini buna atamasını, Theta değerinin ise bizim için önemli olmadığını bunun için kendisinin geçici bir değişken oluşturabileceğini söylüyoruz.

 

Son olarak aşağıdaki örneği inceleyelim;

public record class Konum(int x: X, int y: Y, int z: Z);

var konumBilgisi = new Konum(1, 2, 3);
if (konumBilgisi is Konum(1, *, var z)){
    Console.WriteLine(z);
}

Bu satırlar ardında derleyici kodu şu şekilde yorumlayacak ve işlem yapacaktır;

  • $x, $y ve $z adıyla birer değişken tanımlanır tanımlanır
  • Konum.is(konumBilgisi, out $x, out $y, out $z); çağrısı yapılır
  • is kontrolünün doğru değer dönüp dönmediği kontrol edilir
  • Constant Pattern: object.Equals($x, 1) ifadesinin doğruluğu kontrol edilir
  • Wildcard Pattern: $y ifadesi gözardı edilir.
  • Var Pattern: z adıyla bir değişken oluşturularak $z değeri atanır

Bu adımlar ardında derleyicinin oluşturacağı kod aşağıdakine benzer olacaktır;

var konumBilgisi = new Konum(1, 2, 3);

int $x, $y,$z;
if (Konum.is(konumBilgisi, out $x, out $y, out $z)){
    if(object.Equals($x, 1)){
        var z = $z;
        
        Console.WriteLine(z);
    }
}

Switch İfadeleri

   Şimdiye kadar ki örneklerin is operatörü üzerinden gitmiş olsa da makelemin başında da belirttiğim gibi aynı kurallar switch ifadesi için de geçerlidir. Dolayısıyla bu taslağın kabul edilmesi durumunda aşağıdaki gibi ifadelerin de kullanabileceğiz;

case int z:
case Konum(1, *, var z):
case null:

Fatih Boy

Ankara'da yaşayan Fatih, bir kamu kurumunda danışman olarak çalışmaktadır. ALM süreçleri, kurumsal veri yolu sistemleri, kurumsal altyapı ve yazılım geliştirme konularında destek vermektedir. Boş zamanlarında açık kaynak kodlu projeler geliştirmeyi ve bilgisini yazdığı makalelerle paylaşmayı seven Fatih, aynı zamanda Visual C# ve Visual Studio teknolojileri konusundan Microsoft tarafından altı yıl üst üste MVP (En Değerli Profesyonel) ödülüne layık görülmüştür. İş hayatı boyunca masaüstü uygulamaları, web teknolojileri, akıllı istemciler gibi konularda Asp.Net, Php, C#, Java programlama dilleri ve MySql, MsSql ve Oracle gibi veritabanı yönetim yazılımları ile çalışmıştır. İngilizce ve Türkçe olarak yayınlanan makalelerini gerek İngilizce bloğunda, gerekse de Türkçe bloğunda bulabileceğiniz gibi web sitesinden de açık kaynak kodlu geliştirdiği yazılımlarına ulaşabilirsiniz. vCard - Twitter - Facebook - Google+

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir