Sürdürmeler ve gelişmiş akış denetimi

0
butch
IBM Türkiye ve Fazlamesai.net işbirliği ile dilimize kazandırılan yeni bir IBM developerWorks makalesi ile karşınızdayız. Diğer makalelere buradan ulaşabilirsiniz.

Makalenin özgün haline bu adresten ulaşabilirsiniz.

Sürdürmeler ve gelişmiş akış denetimi

Uzman dillerden yeni teknikler öğrenin

Düzey: Orta

Jonathan Bartlett (johnnyb@eskimo.com), Teknoloji Direktörü, New Medio

24 Mayıs 2006

Akış denetimi genellikle basit bir süreçtir: sıralama, seçim, yineleme. Bu birincil denetim yapılarını temel alarak yetişmiş pek çok programcı, başka ne tür akış denetimlerinin gerekli olabileceğini görmekte zorlanabilir. Bu makalede, sürdürmeler tanıtılır ve akış denetimine bambaşka açılardan nasıl bakabileceğiniz öğretilir.

Programcılığa ilk başladıklarında, pek çok programcı akış denetimi üzerine düşünmez, çünkü çoğu programcılık dilinde yalnızca birkaç basit akış denetimi yapısı bulunur. Ancak, akış denetimi dünyası, çoğu yaygın programlama dilinin izin verdiğinden daha zengindir. Daha az bilinen birçok uzmanlaşmış dilde, gelişmiş ve ilginç akış denetimi yapıları bulunur.

Gelişmiş akış denetimi örnekleri

Başlamanın en iyi yolu, farklı dillerdeki gelişmiş akış denetimi yapı örneklerine bakmaktır. Ardından, bu bilgileri gelişmiş akış denetimi durumları için genel bir çerçeveye yerleştirebiliriz.

Yerel olmayan çıkışlar

İlk gelişmiş akış denetimini büyük olasılıkla zaten biliyorsunuzdur: yerel olmayan çıkışlar. Yerel olmayan çıkışların birkaç türü vardır; bunların her biri, yapılandırılmış ve yapılandırılmamış olarak iki kategoriye ayrılabilir. Yapılandırılmamış yerel olmayan çıkışlar, bilgisayar mühendisliği fakültesinde hocalarınızın asla yapmamanızı söylediği şeylerdir, çok korkulan goto (git) komutu gibi. Aslında, bilinçli bir şekilde ve doğru yerde kullanıldığında, yapılandırılmamış yerel çıkışlar çok kullanışlı olabilir. Bunlar, örneğin, karmaşık akış denetimi içeren programların okunabilirliğini artırabilir. Karmaşık akış denetiminin kendisi iç içe geçmiş bir yapıda değilse, onun üzerine iç içe geçmiş bir yapı getirilmesi, onu daha fazla değil, tersine daha az okunabilir hale getirir. Goto kodlarının kullanılmasının avantaj ve dezavantajlarıyla ilgili ek bilgi almak için bu makalenin sonunda yer alan Kaynaklar bölümündeki bağlantılara bakın.

Yapılandırılmış yerel olmayan çıkışlar tekniğine gelince, muhtemelen en sık kullanılan türünü tanıyorsunuzdur: istisnalar. Son 20 yıldır kafanızı kaldırmadan C, Fortran ve Cobol programlıyorsanız, işte size istisnalara kısa bir giriş.

İstisna, kod içindeki bir hatanın işaretlenmesi ve yerelleştirilmesi için kullanılan bir yöntemdir. Genellikle, bir hata oluştuğunda, hatanın ele alınmasını ve siz açıkça belirtmedikçe işin geri kalanının sürmemesini istersiniz. Örneğin, Java™ dilindeki bu basit veritabanı koduna bir bakalım:



Liste 1. Basit veritabanı kodu örneği
//NOTE -- this code will not compile, but that's on purpose.
import java.sql.*;
  ...
  public static int testdbcode(Connection conn) {
    //Prepare the statement
    PreparedStatement s = conn.prepareStatement(
       "select id from test_table where name = ?");
    //Set the parameters
    s.setString(1, "fred");
    //Run the query
    ResultSet rs = s.executeQuery()
    //Move to the first result row
    rs.next();
    //Get the answer (first result parameter)
    int id = rs.getInt(1);

    return id;
  }
  ...

Bu kodun sorunu, hata işlemesi olmamasıdır ve veritabanlarıyla ya da başka dış varlıklarla uğraşırken, hatalar hemen her yer noktada ortaya çıkabilir. Kod içinde herhangi bir yerde bir hata oluşursa, devam etmenin pek bir anlamı yoktur. Örneğin, sorgumu hazırlarken bir hata oluştuğunda, parametreleri ayarlamanın, sorguyu çalıştırmanın ya da yanıta erişmenin pek bir yararı olmaz. İlk hatadan sonra sorgu artık işe yaramaz sayılır. Bu nedenle, Java'da bir kod bloğunu kapatmanıza olanak tanıyan istisnalar vardır, böylece kod bloğunun bütünü ilk hatada atlanır. Bunu Java dilinde gerçekleştirmek için, kodu aşağıdaki biçimde yeniden yazarsınız:



Liste 2. İstisna işleme gerçekleştiren basit veritabanı fonksiyonu
import java.sql.*;
  ...
  public static int testdbcode(Connection conn) {
    try {
      //Prepare the statement
      PreparedStatement s = conn.prepareStatement(
         "select id from test_table where name = ?");
      //Set the parameters
      s.setString(1, "fred");
      //Run the query
      ResultSet rs = s.executeQuery()
      //Move to the first result row
      rs.next();
      //Get the answer (first result parameter)
      int id = rs.getInt(1);

      return id;
    } catch (Exception e) {
      //Put error handling code here
      return -1;
    }
  }
  ...


Try bloğunun içindeki kod, tamamlanıncaya dek ya da bir deyim hata durumuyla sonuçlanıncaya dek yürütülür. Bir hata atılırsa (throw), try bloğunun içindeki kodun geri kalanı atlanır ve catch bloğu, istisna bilgilerini tutan e değişkeni ile yürütülür. Java'da atılan hataların kendileri de tam sınıflardır, böylece, istisnanın içine istendiği kadar bilgi yerleştirilebilir. Aslında, her biri özel bir istisna sınıfını işleyen birden çok catch blokları oluşturulabilir.

Yukarıdaki kod örneğinde, sistem tarafından oluşturulan istisnaları ele aldık. Ancak, uygulama düzeyindeki istisnalar da aynı şekilde oluşturulup işlenebilir. Uygulama her an throw anahtar sözcüğünü kullanarak bir istisna işaretleyebilir. Örneğin, diyelim ki yukarıdaki kodda 0 gibi bir tanıtıcı numarası aldık, bu bir uygulama düzeyinde istisnadır. Aşağıdakileri yapabiliriz:



Liste 3. Uygulama düzeyinde istisna atma içeren basit bir veritabanı örneği
import java.sql.*;
  ...
  public static int testdbcode(Connection conn) {
    try {
      //Prepare the statement
      PreparedStatement s = conn.prepareStatement(
         "select id from test_table where name = ?");
      //Set the parameters
      s.setString(1, "fred");
      //Run the query
      ResultSet rs = s.executeQuery()
      //Move to the first result row
      rs.next();
      //Get the answer (first result parameter)
      int id = rs.getInt(1);

      //Check for application exception
      if(id == 0) {
        //Signal the exception
        //Assume that InvalidUserIDException is defined elsewhere
        throw new InvalidUserIDException();
      }

      return id;
    } catch (InvalidUserIDException e) {
      //Specialized error handling here
      return -1;
    } catch (Exception e) {
      //Handle all other errors here
      return -1;
    }
  }
  ...

Ayrıca, istisnayı işleyen kodun fonksiyonun içinde bulunması için bir neden yoktur. Try ve catch deyimleri herhangi bir fonksiyonun içinde yer alabilirler. İstisna işleme mekanizması, uygun bir istisna işleyiciye ulaşıncaya dek yığını bırakacaktır (unwind), bu noktada da istisna işleme kodunu çalıştıracaktır. Yapılandırılmış yerel olmayan çıkışları gerçekleştirme yeteneği, kodların yazılmasını da, bakımını da çok kolaylaştırır çünkü bazı durumlarda hata işleme, işi gerçekleştiren asıl koddan bütünüyle ayrılabilir. Elbette, kolaylığı yanıltıcıdır. İstisna işlemenin bu makalenin kapsamına girmeyen bir kaç başlıca püf noktası vardır, ancak Kaynaklar bölümündeki bağlantılardan bazılarında bunlara ilişkin yönergeler bulabilirsiniz.

Üretim fonksiyonları

Gelişmiş akış denetimi yapısının bir başka tipi de üretici (generator) fonksiyonudur. Üreticiler, bir takım değerleri üretebilen ve fonksiyon çağırıldığında onları birer birer döndürebilen fonksiyonlardır. Üretici, programlamayı kolaylaştırmak üzere geçerli fonksiyondaki yerini "işaretleyebilir".

Örneğin, 1 ile başlayan ve her çağırdığınızda artan bir dizi sayı döndüren bir fonksiyon istediğinizi varsayalım. Bunun kapatmalar ve hatta genel bir değişkenle yapılması güç olmasa da, bunu üreticilerle de çok kolay bir şekilde yapabilirsiniz. Aşağıda, Python'un üretici uygulaması ile bir örnek sunulmuştur:



Liste 4. Basit bir Python üretici programı
#This is our generator function
def sequence(start):
  current = start
  while true:
    yield current
    current = current + 1

generator_func = sequence(1)  #Create a new sequence generator starting at 1
print generator_func.next()   #prints 1
print generator_func.next()   #prints 2
print generator_func.next()   #prints 3

Gördüğünüz gibi, üretici her seferinde fonksiyonda kaldığı yere döner, sonra da bir yield deyimi buluncaya dek devam eder. Bu tipte bir "işaretleme" ve "işaretli yerden devam etme" özelliği, pek çok dilde standart bir özellik değildir, ancak bunlar oldukça yararlıdır ve karmaşık mantığı okunabilir ve kolay programlanabilir hale getirirler.

Mantık programlaması

Gelişmiş akış denetiminin başka bir tipi de, Prolog gibi programlama dillerinde sıkça kullanılan mantık programlamasıdır. Prolog'da, bilgisayara bir takım tanımlar verirsiniz ve o da "büyüleyici bir biçimde" sorgularınızı yanıtlar ve sizin için değerler belirler. Örneğin, aşağıdaki Prolog programına bakın (büyük harfler değişkenleri gösterir):



Liste 5. Prolog dilinde basit bir mantık programı
likes(john,sports).            %this asserts that john likes sports
likes(john,food).              %this asserts that john likes food
likes(amy,X) :- likes(john,X). %amy likes X if john likes X
likes(brad,food).              %brad likes food
likes(brad,television).        %brad likes television

%query for a value that both amy and brad like, and write it out.
?- likes(amy,X), likes(brad,X), write(X).

Bu kodun işleyişinde, Prolog john ve brad'ın sevdiği şeylerin bir listesini oluşturur. Ayrıca amy'nin neyi sevdiğine ilişkin kuralın john'un sevdiği şeyler olduğunu da belirtir. Ardından, sorguyu yürüttüğünüzde, önce "amy neyi sever?" sorusunu yanıtlar. Bu sorunun yanıtı, "john'un sevdiği şeyler"dir. Ardından, john'un sevdiği şeyler listesine gider ve ilk öğeyi çekip alır, bu da spordur (sports). Sonra bir sonraki öneriye gider, o da brad'in amy'nin sevdiği şeyleri sevdiğidir (bu ifadede X olarak belirtilir). Ancak, brad'in öğeler listesinde spor yoktur. Bu nedenle, Prolog geriye döner (backtracking) ve amy'nin listesinden X öğesine yeni bir değer bulur. Bir sonraki değer yiyecektir (food). Sonra gidip brad'in listesinde yiyeceğin bulunup bulunmadığını denetler. Listede yiyecek vardır, bu durumda bir sonraki adıma geçer, bu da X'e hangi değerin bulunduğunun yazılmasıdır.

Bu tür bir programlamaya mantık programlaması, adı verilir, çünkü hedefleri mantıksal ilişkiler çerçevesinde belirtmenize olanak tanır ve bu mantıksal ilişkilere uygun çözümler bulmak için tüm ayak işlerini bilgisayarın yapmasını sağlar. Bizim amaçlarımız açısından en önemli kısmı, bu programlama tipinin kurduğu benzersiz akış denetimidir; geriye dönüş. Buna göre, hesaplamanın herhangi bir noktasında, belirli bir değişkenin içinde uygun bir değer bulunamazsa ya da bir grup değişken arasındaki belirli bir ilişki doğru değilse, program "geriye dönüp" yeni bir değer atayabilir. Bu tür programlama, özellikle de akıllı sistemler alanında, çok sayıdaki sorun kümelerini basitleştirir.




Sürdürmeler: Akış denetimi için çok amaçlı bir olanak

Buraya kadar üç tip gelişmiş akış denetimi inceledik: yerel olmayan çıkışlar, üretim fonksiyonları ve geriye dönüş. Bunların ortak noktası nedir? Temelde, her biri, program yığını ve yönerge işaretçisi ile bir miktar jimnastik yapar. Yerel olmayan çıkışlarda, çıkış işleme bloğunun yanı sıra, çıkış bloğunu içeren yığın çerçevesi de işaretlenir. Yerel olmayan çıkışı çağırdığınızda, yığın işaretlenen noktaya dek geri sarılır (unwind) ve yönerge işaretçisi işleyici bloğa taşınır. Üretim fonksiyonlarında, üreticiyi içeren değişken, üretici fonksiyonunun yığın çerçevesini ve üreticinin fonksiyon içinde en son kaldığı noktayı gösteren bir işaretleyici içerir. Geriye dönüş işleminde ise, atamanın gerçekleştiği yerde bir işaret tutulur ve hesaplama başarısız olursa ve geriye dönülmesi gerekirse akış denetimi o yere döndürülür.

Bu işaretlere "sürdürme noktaları" denebilir; bunlar, gelişmiş akış denetimi yapısı çağrıldığında programın sürdürüleceği yerlerdir. Ya da, daha doğru bir ifadeyle bunlar sürdürmeler olarak bilinir. Aslında, tüm bu denetim yapıları tek bir akış denetimi fonksiyonu kullanılarak uygulanabilirler: call-with-current-continuation (geçerli sürdürme ile çağır).

Call-with-current-continuation, Scheme programlama dilinde, geçerli yığın çerçevesini ve yönerge işaretleyicisini alan ve bunları tek bir çağrılabilir varlık (sürdürme) olarak paketleyen bir fonksiyondur ve sürdürme ile birlikte başka bir fonksiyonu onun tek parametresi olarak çağırır. Sürdürme, tek bir parametre alan, çağırılabilir bir varlıktır ve o parametre daha sonra sürdürmenin oluşturulduğu noktaya döndürülür. Bu anlatılanları karmaşık bulmuş olabilirsiniz, öyledir de. Uygulamada nasıl işlediğini görmek için birkaç örneğe bakalım.

Önce, sürdürme kullanılmasına ilişkin kısa bir örnek:



Liste 6. Sürdürme örneği
(display
  ;;Calling the continuation will return the parameter as the return value to
  ;;the call-with-current-continuation function.  This is the "bookmarked" location
  (call-with-current-continuation
    ;;Continuation variables are often written with a "k" or "kont" to remind
    ;;yourself that it is a continuation.  In this example, "kont" will be
    ;;the continuation.  It is a function, that, when called, will return its
    ;;parameter to the bookmarked location.
    (lambda (kont)
       ;;For this example, we will simply run the continuation.  This returns
       ;;the number 5 to the "bookmarked" location
       (kont 5))))
(newline)

Önceki program yalnızca 5 sayısını görüntüler. Sürdürmenin tek bir parametreyle çağrılmasının o değeri işaretlenen yere döndürdüğüne dikkat edin. Şimdi biraz daha karmaşık bir örneği inceleyelim. Bu örnekte, sürdürmeyi erken çıkış olarak kullanacaksınız. Bu örnek yapmacık olabilir, ancak durumu iyi örnekler. Bu programda, bir listedeki her sayının karesini alacaksınız. Ancak, listede sayı olmayan öğeler varsa, program listeye döndürmek yerine *error* simgesini döndürür.



Liste 7. Sürdürme ile hata durumlarından erken çıkış
(define a '(1 2 3 4 5))
(define b '(1 b 2 e f))
(define (square-list the-list)
  ;;Bookmark here
  (call-with-current-continuation
    ;;early-exit-k will return us to our bookmark
    (lambda (early-exit-k)
      ;;run the procedure
      (map
        (lambda (x)
          ;;Check to see if it is a number
          (if (number? x)
              ;;yes it is, perform the multiplication
              (* x x)
              ;;no it isn't, exit _now_ with the value *error*
              (early-exit-k '*error*)))
        the-list))))
;;This will square the list
(display (square-list a))
(newline)
;;This will give the error
(display (square-list b))
(newline)

Umarız yukarıdaki örnek, size sürdürmeleri istisnaların uygulanmasında nasıl kullanabileceğinize ilişkin bir fikir vermiştir.

Sıradaki örnek sürdürmelerin başka bir olağan dışı özellik olarak, sınırsız kapsam özelliğine sahip olduklarını gösterir. Bu, istisnaların aksine, sürdürmelerin çıkı noktaları olan kod bloğunun dışında da etkinleştirilebildikleri anlamına gelir. Sürdürme ile bir "işaret" oluşturduğunuzda, bu, sürdürme değeri etkin olduğu sürece, o yığın çerçevesini de etkin kalmaya zorlar. Bu nedenle, sürdürmeyi oluşturan bloktan dönseniz bile, onu döndürürseniz, sürdürme çağrıldığında önceden etkin olan yığın çerçevesi geri yüklenir ve işlem buradan devam eder.

Aşağıdaki örnekte, bir sürdürme genel bir değişkene kaydedilir ve sürdürmenin etkinleştirilmesinden sonra, başlangıçtaki yığın çerçevesi yeniden etkinleştirilir.



Liste 8. Sürdürmelerle bir yığın çerçevesinin yeniden etkinleştirilmesi
;;Global variable for the continuation
(define k-global #f)

;;We are using let* instead of let so that we can guarantee
;;the evaluation order
(let* (
       (my-number 3)
       (k
         ;;set bookmark here
         (call-with-current-continuation
           ;;The continuation will be stored in "kont"
           (lambda (kont)
             ;;return the continuation
             kont))))

     ;;We are using "my-number" to show that the old stack
     ;;frame is being saved.  When we revisit the continuation
     ;;the second time, the value will remain changed.
     (display "The value of my-number is: ")
     (display my-number)
     (newline)
     (set! my-number (+ my-number 1))

     ;;Save the continuation in a global variable.
     (set! k-global k))

;;Is "kontinuation" a continuation?
(if (procedure? k-global)
    (begin
       (display "This is the first run, k-global is a continuation")
       (newline)
       ;;Send "4" to the continuation which goes to the bookmark
       ;;which will assign it to "k"
       (k-global 4))
     (begin
       ;;This occurs on the second run
       (display "This is the second run, k-global is not a continuation, it is ")
       (display k-global)
       (newline)))

Bu özelliklerle, başka her türlü özelliğe öykünen ilginç yordamlar ve makrolar oluşturabilirsiniz.

Sürdürmeler içeren istisnalar

İstisnaların neye benzediğine bir bakalım:



Liste 9. İstisna örneği
try {
     //Code here which might generate an exception
} catch(SQLException e) { //catch a specific exception
     //Error handling code
} catch(Exception e) { //catch a general exception
     //Error handling code
}

//remaining code here

Yani, temel olarak yapmanız gereken, aşağıdakileri oluşturan bir makro yaratmaktır:

  • Hata işleme kodu bloğu
  • Kalan kodun yeri
  • Çalıştırılan kod

Böylece, makro genişletmesinin sonucu aşağıdaki gibi görünmelidir:



Liste 10. Varsayımsal bir istisna makrosunun istenen genişlemesi
;;This establishes the throw function as globally available, but displays an
;;error message if used without being in a try block.
(define throw (lambda (x) (display "No try clause is active!") (newline)))

(let* (
       ;Save the old containing try block
       (old-throw throw)
       ;we are saving the results in retval because we still have to clean up after
       ;ourselves before we can exit
       (retval (call-with-current-continuation
                 ;The exception will use this continuation to get back to the
                 ;remaining code
                 (lambda (k-exit-to-remaining-code)
                   (let (
                         ;This defines the exit handler
                         (error-handler
                           (lambda (exception)
                              (k-exit-to-remaining-code
                                 ;;error-handling code here
                                 ))))
                        ;This sets our error handler to be the official "throw" function
                        (set! throw error-handler)
                        ;;Regular code here
                        ;;You can throw an exception by doing:
                        (throw 'test-exception))))))

     ;Reinstate old try block
     (set! throw old-throw)

     ;Return the result
     retval)

Bu işlem, iç içe geçme işlemini, throw her zaman en içteki try bloğuna gönderme yapacak şekilde ayarlar. Artık kodun neye benzemesini istediğinizi bildiğinize göre, tüm altyapının ayarlanmasını halledecek bir makro yazabilirsiniz.



Liste 11. İstisna kodunu oluşturacak makro
;;This establishes the throw function
(define throw (lambda (x) (display "No try clause is active!") (newline)))

;;establishes syntax for a "try" block
(define-syntax try
  (lambda (x)
    (syntax-case x (catch)
      (
       (try expression (catch exception exception-handler-expression))
       (syntax
         (let* (
                (old-throw throw)
                (retval
                  (call-with-current-continuation
                    (lambda (k-exit)
                      (let (
                            (error-handler
                              (lambda (exception)
                                (k-exit exception-handler-expression))))
                           (set! throw error-handler)
                           expression)))))
               (set! throw old-throw)
               retval))))))

;;Short test suite

;Function that throws an error
(define (test-function)
  (throw 'ERROR))

;Function that does not throw an error
(define (test-function-2)
  (display "No error is generated.")
  (newline))

;Test out our try block
(try (test-function) (catch e (begin (display "Exception!  e is: ") (display e) (newline))))
(try (test-function-2) (catch e (begin (display "Exception!  e is: ") (display e) (newline))))

Temel öğelerin çoğunu bitirdik, ancak hala birkaç sorun daha var. Sorun, sürdürmelerin karıştırılmasında ortaya çıkıyor. Örneğin, istisnalarda sürdürmeler kullanmanın yanı sıra, onları başka tipte erken çıkış mantıklarında da kullanırsanız, o zaman bir sorunla karşılaşırsınız. Aşağıdaki koda bir bakın:



Liste 12. Kötü sürdürme etkileşimleri
;;Uses previously defined try/catch macro
(try
  (begin
    (call-with-current-continuation
      (lambda (kont)
        (try
          (kont 'value)  ;;jumps out of the continuation, but also skips resetting
                         ;;the active continuation
          (catch e (begin
                     (display "Interior exception handler.  Exception e is: ")
                     (display e)
                     (newline))))))
    ;;Because we didn't exit out through the try block in a normal fashion, this will
    ;;actually send us _back_ to the interior catch block the first time it is called!
    (throw 'ERROR))
  (catch e (begin
             (display "Exterior exception handler.  Exception e is: ")
             (display e)
             (newline))))

Gördüğünüz gibi, serbest bir şekilde oradan buraya atlama yeteneği kırtasiye işlemlerinde bazı güçlüklere yol açabilir. Bu sorunları hafifletmek için Scheme'de dynamic-wind (dinamik sarma) adı verilen özel bir yordam bulunur. Dynamic-wind yordamı, sürdürme belirli bir yığın çerçevesini atladığında, bu durumu saptar. Sürdürme ileri geri atladığında, bu yordamı kullanarak yığını ilk durumuna getirebilirsiniz. Dynamic-wind üç bağımsız değişken alır, bunların her biri sıfır bağımsız değişken (zero-argument) yordamıdır. İlki, bir yığın çerçevesine her girişinizde çalışacak olan yordamdır. Bir sonraki asıl çalışacak olan yordamdır. Sonuncusu da yığın çerçevesinden her çıkışınızda çalışacak olan yordamdır. Aşağıdaki örnekte dynamic-wind yordamının nasıl çalıştığına ilişkin kısa bir örnek yer alır:



Liste 13. Dynamic wind kullanımı örneği
(let (
      (k-var (call-with-current-continuation
               (lambda (kont)
                 (dynamic-wind
                   (lambda () (display "Entering frame") (newline))
                   (lambda ()
                     (begin
                       (display "Running")
                       (newline)
                       (call-with-current-continuation
                         (lambda (inner-kont)
                           ;;Exit across the dynamic-wind boundary,
                           ;;saving the current continuation
                           (kont inner-kont)))))
                   (lambda () (display "Leaving frame") (newline)))))))
  (if (procedure? k-var)
      (k-var 'the-value)
      (begin
        (display k-var)
        (newline))))

Bu örnek önce bir dış sürdürme oluşturur. Sonra, "giriş" yordamını çağırarak yığın çerçevesine girer. Ardından, dynamic-wind içinde yeni bir sürdürme üreten bir yordam çalıştırır. Bu sürdürme sonra başlangıçtaki sürdürmeye döndürülür. Ancak, dynamic-wind satırını geçtiği için, "ayrılma" yordamı yürütülür. Daha sonra, iç sürdürme yeniden yürütülür, bu, denetimi yeniden dynamic-wind üzerinden taşır, bu da "giriş" yordamını yeniden çağırır. Sonra dynamic-wind boyunca geri döner, bu da "ayrılma" yordamını yeniden çağırır.

Bu çağrı sırası biraz karışık gelebilir, ancak dynamic-wind, çok ileriye uzanan sürdürmelere karşı bir "koruma" çizgisi olarak düşünülürse anlamlı gelebilir. Akış denetiminin, "koruma" çizgisinin ötesine geçebilmesi için (sürdürmeyle ya da olağan akış denetimi sayesinde), temizlik için uygun yordam yürütülmelidir (yönüne göre, "giriş" ya da "çıkış").

Bunu kullanarak, try makrosundaki bazı sorunlardan korunabilirsiniz. Kodun hangi try/catch bloğunda yürütüldüğünü ilk duruma getirmek için dynamic-wind yordamını kullanabilirsiniz. Kod şöyledir:



Liste 14. Dynamic-wind ile iyileştirilmiş try/catch
;;This establishes the throw function as globally available, but displays an
;;error message if used without being in a try block.
(define throw (lambda (x) (display "No try clause is active!") (newline)))

;;establishes syntax for a "try" block
(define-syntax try
  (lambda (x)
    (syntax-case x (catch)
      (
       (try expression (catch exception exception-handler-expression))
       (syntax
          ;;Exit point using continuation k-exit
          (call-with-current-continuation
            (lambda (k-exit)
              (let (
                    ;;These are the two exception handlers, the old and the
                    ;;new.  Dynamic-wind sets them up and tears them down
                    ;;upon entering and exiting from scope
                    (old-throw throw)
                    (error-handler
                      (lambda (exception)
                        (k-exit exception-handler-expression))))
                   (dynamic-wind
                     ;;Entering scope -- set exception handler
                     (lambda () (set! throw error-handler))
                     ;;Actual processing
                     (lambda () expression)
                     ;;Exitting scope -- set exception handler to old value
                     (lambda () (set! throw old-throw)))))))))))

Bu örnek biraz daha kısadır ve hem başlangıçtaki sınama örneklerini hem de sürdürmeleri kullanan örneği sağlar. Ayrıca, dynamic-wind ile yeni bir denetim yapısı ekleyerek hile yaptığınızı düşünüyorsanız, Kent Dybvig, dynamic-wind yordamının call-with-current-continuation'a dayanarak uygulanabileceğini göstermiştir.

Try/catch yordamlarının beklenmedik davranışlar sergileyebileceği yerlerin hepsini ele almamış olsak da, bu kadarı çoğu durum için yeterlidir. Sıradaki bölümde olası sorunlardan bazılarına değinilir.

Sürdürme kullanan üreticiler

Yukarıda belirtildiği gibi, üreticiler, akış denetiminin bir biçimidir. Python, üreticileri uygularken en sık kullanılan dildir. Şimdi, üreticilerin nasıl çalıştıklarını ve sürdürmelerin onları uygulamakta nasıl kullanılabileceklerini ele alalım.

İlk olarak, bir üretici oluşturulur. Bu, yığın çerçevesinin ya sürdürme ya da kapatma aracıyla kaydedilmesini gerektirir. Ardından, bir değer döndürürsünüz ve fonksiyon içindeki mevcut konumunuzu işaretlersiniz. Bu, döndüğünüz yeri zaten işaretlemiş olmanız gerektiği anlamına gelir.

Böylece, üreticimizde sürdürmelerle uygulanan iki işaret vardır. İlk işaret, üretici ilk yaratıldığında oluşturulan bir değişkendir. Bu, üretici fonksiyonunun içinde bulunduğu konumu tutar. Ardından, üreticiyi çalıştırdığınızda, çağırma fonksiyonunda dönüş noktası olan bir sürdürmeniz olur. Dönmeden hemen önce, mevcut konumun bir sürdürmesini oluşturursunuz ve onu bir sonraki çağrı için kaydedersiniz.

Şimdi, Python tarzındaki üreticiler için isteyebileceğiniz Scheme arabirimine bakalım:



Liste 15. Python tarzında bir üretici kullanımı örneği
(define-syntax generator
   (syntax-rules ()
     (
       (generator (yieldfunc) body ...)
       (let (
             (placeholder #f)  ;;placeholder in this function
             (return-proc #f)  ;;how to get out
             (finished #f))    ;;whether or not it is finished
         ;;this is the generator entrance
         (lambda ()
           (call-with-current-continuation
             (lambda (tmp-return-proc)
               ;;save the way out in "return-proc"
               (set! return-proc tmp-return-proc)
               (let (
                     ;;"yieldfunc" resets the placeholder and returns the value
                     (yieldfunc
                       (lambda (x)
                         (call-with-current-continuation
                           (lambda (current-placeholder)
                             ;;new placeholder
                             (set! placeholder current-placeholder)
                             ;;return value
                             (return-proc x))))))

                 ;;If the generator is done, return a special value
                 (if finished
                     'generator-finished

                     ;;If this is the first time, there will not be a
                     ;;placeholder established, so we just run the body.
                     ;;If there is a placeholder, we resume to that point
                     (if placeholder
                       (placeholder 'unspecified)
                       (let (
                             (final-value (begin body ...)))
                         (set! finished #t)
                         (return-proc final-value))))))))))))

(define sequence-generator
  ;;Initial parameters
  (lambda (start end increment)
    ;;"yield" will be used to generate a single value
    (generator (yield)
      ;;Main function body
      (let loop ((curval start))
         (if (eqv? curval end)
             curval
             (begin
                ;;yield the value
                (yield curval)
                ;;continue on
                (loop (+ curval increment))))))))

;;Example usage
(define my-sequence (sequence-generator 1 3 1))
(display (my-sequence))(newline)
(display (my-sequence))(newline)
(display (my-sequence))(newline)
(display (my-sequence))(newline)

Bu kodu izlemek biraz güçtür. Üreticide başka değerler ya da başka bir durum sorgusu olup olmadığını sorgulama yeteneği eklendiğinde kod çok daha karmaşık olur. Yine de, iki üreticiye yayılan fonksiyonlara dikkat edin. Biri çağırma programına dönüş, diğeri ise üreticinin yürütüldüğü mevcut yerdir. Dönüş yordamının, üreticinin tümünü kapsayan bir değişkende depolanması tuhaf gelebilir, ancak sürdürme çağrısından sonra değişkenin doğru versiyonunun etkin olması için böyle olmalıdır. Aksi halde, ilk çağrıdan sonra, yer tutucu sürdürme oluşturulduğunda etkin olan versiyona döner.

Yukarıda sözü edildiği gibi, istisna tabanlı sürdürmeler sorunlara yol açabilir. Temel olarak, soru şudur; bir üreticiyi başlattığınızda bir try bloğunuz ve bir üreticiyi çalıştırdığınızda bir try bloğunuz varsa ve üretim fonksiyonunda bir istisna atılırsa, hangi catch bloğu çalıştırılır? Benim kullandığım uygulamalarda, ilk catch bloğu kullanılır. Bu en kolay yöntem midir? Duruma göre değişir. Ancak, bu tipte sürdürme-sürdürme etkileşimleri sorunlu olabilir, çünkü "uygun" işlemin ne olacağı açık seçik bilinmez.

Sürdürme kullanılan geriye dönüş

Son olarak, geriye dönüşe bir bakalım. Geriye dönüş için kullanılan arabirim amb adında bir fonksiyondur. Bu fonksiyon bir dizi değer alır. amb, her değer için bir geriye dönüş işareti belirler. Listedeki (amb:fail fonksiyonunu çağırarak gösterilen) mevcut değer çalışmazsa, program, son işaretli yere "geri döner" ve yeni bir değer dener. Parametre doğru değilse, amb:assert adındaki bir yardımcı program fonksiyonu amb:fail işareti verir. Liste 16 içinde bu fonksiyonların nasıl kullanıldığı gösterilir:



Liste 16. Scheme'de geriye dönüşün kullanılması
(let* (
      (a (amb 1 2 3 4 5 6 7))
      (b (amb 5 6 7 8 9 10)))
  (amb:assert (> a b))
  (display "a is ") (display a) (newline)
  (display "b is ") (display b) (newline))


Bu fonksiyon ilk çalıştığında, a için 1'i ve b için 5'i seçer. a, b'den büyük olmadığı için başarısız olur ve en son işaretlenmiş olan geriye dönüş yerine gider. Bu durumda, bu b'nin atandığı nokta olacaktır. Sonra b'nin her bir değerini deneyecektir. Hiç biri çalışmayacaktır, bu nedenle a'nın atandığı noktaya geri dönecektir. Sonra, b'nin her değeriyle 2'yi deneyecektir. Bu, a'nın b'den büyük bir değerini buluncaya dek devam eder ve bulduğunda bu noktadan işlemi sürdürür.

Liste 17'de uygulama gösterilir:



Liste 17. Geriye dönüşün uygulanması
;AMB definition
(define amb:fail '*)

(define amb:initialize-fail
  (lambda x
    (set! amb:fail
      (if (null? x)
         (lambda () (error "amb tree exhausted!"))
         (car x)))))

(amb:initialize-fail)

(define amb
  (lambda alternatives
    (letrec (
             (amb-internal
               ;;"sk" returns the value (success continuation),
               ;;"alts is the list of values
	       (lambda (sk alts)
                 ;;fail if there are no alternatives
                 (if (null? alts)
                   (prev-amb-fail)
                   (begin
                      (call/cc
                        ;;"fk" is where to go when an
                        ;;alternative fails (failure continuation)
                        (lambda (fk)
                          ;;set the failure function to come back here
                          (set! amb:fail
                            (lambda ()
                              (set! amb:fail prev-amb-fail)
                              (fk 'fail)))
                          ;;return the first alternative
                          (sk (car alts))))
                      ;;We get here after a failure, so we
                      ;;run the function again using the rest
                      ;;of the list
                      (amb-internal sk (cdr alts))))))
             ;;this is the previous failure function
             (prev-amb-fail amb:fail))
      (call/cc
        ;;bookmark the way to the assignment into "sk"
        (lambda (sk)
          ;;go through each alternative
          (amb-internal sk alternatives)
          ;;After going through them, note that we have a failure
          (prev-amb-fail))))))

;;Calling "amb" without arguments will cause a failure.  This function
;;just makes it a little more obvious what is supposed to be happening.
(define amb:assert-failure
  (lambda () (amb)))

(define amb:assert
  (lambda (pred)
    (if (not pred) (amb:assert-failure))))

Kodu okurken, geriye dönücünün değerler listesinde geriye dönüşünün "başarısızlık sürdürmesi", fonksiyonun sıradaki değeri olağan program akışına döndürmesinin ise "başarı sürdürmesi" olduğunu unutmayın.




Sürdürmeler: Akış denetiminden hep istediğiniz her şey

Gördüğünüz gibi, sürdürmeleri her tür gelişmiş akış denetimi deyimlerini uygularken kullanabilirsiniz. Yalnızca birkaç deyimle, istisnalara, geriye dönüşlere ve başka gelişmiş akış denetimi tiplerine sürdürmeler yerleştirebilirsiniz. Ancak bu makalede yapabileceklerinizin yalnızca pek azına değindik. Sürdürmelerle ayrıca Web uygulamalarının daha geleneksel akış denetimi yapılarına dönüştürülmesi ve kullanıcı düzeyinde iş parçacıkları uygulanması gibi işler de yapabilirsiniz. Ne yazık ki, pek çok dil sürdürmeleri uygulamamıştır ve dolayısıyla, kullanıcılarını çok sayıdaki akış denetimi özelliğinden mahrum bırakırlar. Bir dilde sürdürmeler bulunuyorsa, diğer gelişmiş akış denetimi özelliklerini neredeyse rutin bir şekilde uygulayabilir.




Kaynaklar

Bilgi Edinme

Ürün ve teknoloji edinme
  • SEK for Linux ürününü sipariş edin. Bu ürün, DB2®, Lotus®, Rational®, Tivoli® ve WebSphere® üzerinde Linux için en güncel IBM deneme yazılımlarını içeren ikili DVD setidir.

  • Doğrudan developerWorks sitesinden yükleyebileceğiniz IBM deneme yazılımını kullanarak yeni geliştirme projenizi Linux üzerinde oluşturabilirsiniz.


Tartışma



Yazar hakkında

Jonathan Bartlett Programming from the Ground Up adlı, Linux çevirici dili kullanılarak programlamaya giriş kitabının yazarıdır. Bartlett, New Medio'da baş geliştiricidir ve istemciler için Web, video, kiosk ve masaüstü uygulamaları geliştirmektedir.

Görüşler

0
FZ
Bir sonraki IBM dW makalesi için önerileri alalım lütfen ;-)
Görüş belirtmek için giriş yapın...

İlgili Yazılar

UNIX Dilinde Konuşma, Bölüm 4: UNIX sahiplik kuralları ve izinleri

butch

IBM Türkiye ve Fazlamesai.net işbirliği ile dilimize kazandırılan yeni bir IBM developerWorks makalesi ile karşınızdayız. Diğer makalelere buradan ulaşabilirsiniz.

Makalenin özgün haline bu adresten ulaşabilirsiniz.

Ajax Konusunda Uzmanlaşma, Bölüm 5

butch

IBM Türkiye ve Fazlamesai.net işbirliği ile dilimize kazandırılan yeni bir IBM developerWorks makalesi ile karşınızdayız. Diğer makalelere buradan ulaşabilirsiniz.

Makalenin özgün haline bu adresten ulaşabilirsiniz.

UNIX Dilinde Konuşma, Bölüm 7: Komut satırı deyimleri

butch

IBM Türkiye ve Fazlamesai.net işbirliği ile dilimize kazandırılan yeni bir IBM developerWorks makalesi ile karşınızdayız. Diğer makalelere buradan ulaşabilirsiniz.

Makalenin özgün haline bu adresten ulaşabilirsiniz.

Yeni kullanıcılar için UNIX ipuçları ve incelikler, Bölüm 1

butch

IBM Türkiye ve Fazlamesai.net işbirliği ile dilimize kazandırılan yeni bir IBM developerWorks makalesi ile karşınızdayız. Diğer makalelere buradan ulaşabilirsiniz.

Makalenin özgün haline bu adresten ulaşabilirsiniz.

Ajax Konusunda Uzmanlaşma, Bölüm 2

butch

IBM Türkiye ve Fazlamesai.net işbirliği ile dilimize kazandırılan yeni bir IBM developerWorks makalesi ile karşınızdayız. Diğer makalelere buradan ulaşabilirsiniz.

Makalenin özgün haline bu adresten ulaşabilirsiniz.