4 puan yazan GN⁺ 2023-08-20 | 2 yorum | WhatsApp'ta paylaş
  • Railway Oriented Programming, doğrulama, logging ve ağ hataları gibi gerçek dünya uygulamalarında tekrar eden başarısızlık yollarını fonksiyonel pipeline içinde ele alan bir yaklaşımdır
  • Yalnızca başarı yolunu birbirine bağlayan örneklerin sınırlılığını demiryolu benzetmesi ile açıklayarak, F# kullanıcılarının da hata akışını görsel olarak kolayca anlamasını sağlaması temel noktadır
  • Haskell açısından Either tipi ve Kleisli bileşimine yakındır; ancak soyut teoriden çok hata işleme reçetelerine ve uygulama kalıplarına odaklanır
  • Pratik uygulama için NuGet uyumlu F# kütüphanesi Chessie, C#·F# karşılaştırmalı GitHub örneği ve FizzBuzz uygulama örneği birlikte sunulur
  • Yararlı bir yöntemdir, ancak her probleme aşırı uygulanmamalıdır; F#’ta type class olmadığı için örnek kütüphane gerekli fonksiyonları doğrudan tanımlar

Sunum materyalleri ve örnek kod

  • Railway Oriented Programming sayfası, sunum slaytlarını ve kodunu tek yerde toplayan bir kaynaktır
  • Fonksiyonel programlama örneklerinin sıkça varsaydığı “happy path” tek başına gerçek dünya uygulamaları geliştirmek için yeterli değildir; çıkış noktası bu problemdir
  • Sağlam uygulamalar doğrulama, logging, ağ hataları, servis hataları ve diğer istisnai durumları da birlikte ele almalıdır

Videolar ve slaytlar

  • Bu konu NDC London 2014’te sunulmuştur
  • Aynı sunumun başka videoları da yayımlanmıştır
  • Functional Programming eXchange’in 14 Mart 2014 tarihli sunum slaytları vardır
  • PowerPoint slaytları GitHub üzerinden de indirilebilir; serbestçe ödünç alınıp kullanılabileceği belirtilir

Either monadı ve Kleisli bileşimi

  • Haskell kullanıcıları için bu yaklaşım Either tipi gibi görünebilir
  • Burada Left durumunda özel hata tipi listesi kullanacak şekilde özelleştirilmiştir
    • Örnek tip: type TwoTrack a b = Either [a] (b,[a])
  • Yeni olan şey hata işleme kavramının kendisi değil, daha çok bunu açıklayan demiryolu benzetmesidir
  • Standart Haskell terimlerini öne çıkarmamasının nedeni açıktır
    • Bu materyal bir monad öğreticisi değil, hata işleme problemini çözmeye odaklanır
    • F# ile tanışan birçok kişi monadlara aşina olmadığı için, görsel ve daha az göz korkutucu bir açıklama daha sezgisel olabilir
    • bind içeren two-track tipini doğrudan monad olarak adlandırmak isabetli değildir; monad yasaları da ele alınmaz
    • Either çok genel bir araçtır; burada aracın kendisinden çok bir reçete sunulmak istenir

Hata işleme reçetesinin bileşenleri

  • Bu yaklaşım, “sadece Either ve bind kullanmak yeterli” demekten daha kapsamlı bir teknikler kümesidir
  • İçerdiği teknikler şunlardır
    • Either String a gibi basit bir biçim yerine hem sol hem sağ tarafta özel hata tipi listeleri kullanma
    • Monadik fonksiyonları pipeline’a entegre etmek için bind (>>=) kullanma
    • Monadik fonksiyon bileşimi için Kleisli bileşimi (>=>) kullanma
    • Monadik olmayan fonksiyonları pipeline’a entegre etmek için map (fmap) kullanma
    • F# IO monadı kullanmadığından, unit fonksiyonlarını pipeline’a entegre etmek için tee kullanma
    • İstisnaları hata durumlarına eşleme
    • Doğrulama gibi durumlarda monadik fonksiyonları paralel birleştirmek için &&& kullanma
    • Domain-driven design’da özel hata tiplerinin sağladığı avantajlardan yararlanma
    • Logging, domain event’ler ve telafi işlemleri gibi genişletmeler
  • Amaç, neredeyse her durumda kullanılabilecek kadar çok yönlü, ama tutarlı bir stili zorunlu kılacak kadar sınırlayıcı bir şablon oluşturmaktır
  • Kod yazma biçimi fiilen tek bir yapıya yakınsadığında, daha sonra bakım yapacak kişinin kodun organizasyonunu hızla anlaması kolaylaşır
  • Bu yöntem tek çözüm değildir, ancak iyi bir başlangıç noktası olabilir
  • Haskell topluluğu içinde bile hata işleme yöntemleri tutarlı değildir; bu durum yeni başlayanlar için kafa karıştırıcı olabilir

Kendi koduma nasıl uygularım

  • Hazır bir F# kütüphanesine ihtiyacınız varsa NuGet ile kullanılabilen Chessie project’e bakabilirsiniz
  • Bu tekniğin uygulandığı örnek web servisi GitHub projesi olarak sunulur
  • ROP yaklaşımının FizzBuzz’a uygulandığı örneği Railway Oriented Programming: carbonated sayfasında görebilirsiniz
  • F#’ta type class olmadığı için monadları yeniden kullanmanın genel bir yolu yoktur
  • FSharpX library yararlı bir yaklaşım sunar; ancak Rop.fs kütüphanesi tüm fonksiyonları sıfırdan doğrudan tanımlar
  • Bu şekilde izole etmek, hiçbir dış bağımlılığın olmaması avantajını sağlar

Daha fazla okuma

2 yorum

 
GN⁺ 2023-08-20
Hacker News yorumları
  • Elixir, with anahtar sözcüğü/makrosuyla bu sorunu epey temiz biçimde ele alıyor
    with {:ok, file_handle} <- File.open(filename), ... do {:ok, parsed} end örneğinde olduğu gibi fonksiyonları yukarıdan aşağıya çalıştırıyor; her dönüş değeri soldaki kalıpla eşleşmezse o değerle erken dönüş yapıyor
    Böylece her fonksiyonu hem {:ok, value} hem de {:error, reason} alacak şekilde yazmak yerine, fonksiyonun yalnızca ilgilendiği değeri almasını sağlayıp with bloğundaki pattern matching ile hata yayılımını ele almasına izin verebilirsiniz
    Örneğin File.open {:error, reason} döndürürse IO.read çalıştırılmaz ve tüm with sonucu {:error, reason} olur. Sonuçta yalnızca başarı yolunu yazıp, başarısızlık yolunu çağıranın isterse eşleştirmesine bırakabilirsiniz

    • “Eşleşmeyeni erken döndürmek” deniyorsa, bu değerlerin hepsi farklı türde olduğunda ne olacağını merak ediyorum. Dinamik tipliyse, hangi tür olduğuna bakıp nerede başarısız olduğunu tahmin etmek gerekmiyor mu diye düşünüyorum
      Bu, istisna işlemeyi yarı yarıya yeniden icat etmek gibi hissettiriyor; catch yerine else gibi bir ad verilirse sadece istisna işleme gibi daha az görünür
    • Bildiğim kadarıyla bu, Haskell’deki where ve Lisp’teki let’ten etkilenmiş bir yapı
      Fonksiyonun önkoşullarını kurmak sayılır; Elixir’de ayrıca else bloğu da kullanılabiliyor
    • Bunun istisnalardan daha iyi yanı ne, merak ediyorum. Ayrıca IO.read başarısız olursa dosya tanıtıcısını kimin kapatacağı da bir sorun
  • Yazar birkaç yıl sonra Against Railway Oriented Programming adlı bir devam yazısı da yazmış: https://fsharpforfunandprofit.com/posts/against-railway-orie...
    Demiryolu odaklı programlama ilginç ve kullanılabileceği yerler var, ama büyük bir uyarı etiketi gerektirdiğini düşünüyorum. Pratikte bunun çoğu zaman exception handling’i kötü biçimde yeniden icat etmek için kullanıldığını gördüm; exception’ları doğru anlayıp kullanırsanız çoğu hata durumunu daha temiz ve etkili biçimde ele alabilirsiniz.
    Exception’ların avantajı, çoğu durumda güvenli seçeneğin varsayılan olmasıdır. Bir hata durumu, kodun spesifikasyona göre yapması gerekeni yapamadığının sinyalidir; o durumda devam etmek, yanlış varsayımlarla verinin bozulmasına yol açabilir. Exception’lar varsayılan olarak temizlikten sonra çağırana yayılır; demiryolu odaklı programlama gibi yaklaşımlar ise unutması kolay, hata yapması kolay çok sayıda boilerplate gerektirir.
    Yine de iki durumda faydalıdır. Biri, doğrulama gibi belirli ve iyi tanımlanmış beklenen hataların oluştuğu noktada ele alınması gereken durumlar; diğeri ise exception handling kullanılamayan ortamlardır. Eski jQuery’nin Promise tabanlı asenkron kodu neredeyse demiryolu odaklı programlamanın bir implementasyonu gibiydi; ancak modern JavaScript’te async/await ile yeniden exception handling kullanılabiliyor.

    • Ana akım dillerdeki exception’ların hayal kırıklığı yaratan yanı, bu işlemenin görünmez biçimde gerçekleşmesidir. Bir fonksiyonun içinde hangi exception’ların çıkabileceğini derleme zamanında kavramak kolay değildir.
      Demiryolu odaklı programlamanın büyük ödülü, yalnızca fonksiyon imzasına bakarak hangi hataların çıkabileceğini hemen anlayabilmektir.
      Boilerplate, dile bağlı olarak azaltılabilir. Haskell’de do notasyonu var; F#’ta ise result computation expression’larıyla çok temiz biçimde yazılabilir. Örneğin LoginError = InvalidUser | InvalidPwd | Unauthorized of AuthError şeklinde tanımlayıp, akışı Result.requireSome, Result.requireTrue, Result.mapError ile kurabilirsiniz.
      Tersine, exception’ların dezavantajlarının da azaltılıp azaltılamayacağını merak ediyorum. Java IDE’leriyle entegre olup belirli bir kod satırında fırlatılabilecek yakalanmamış exception’ları otomatik gösteren bir linter ya da statik analiz aracı var mı, bilmek isterim.
      https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoo...
    • Geleneksel exception implementasyonlarının epey kötü olduğunu düşünüyorum.
      Öncelikle birçok dil, exception yakalamak için tuhaf ve gereksiz scope’lar dayatıyor. Exception fırlatabilecek bir fonksiyonun sonucuyla bir değişken bildirilip başlatılacaksa, başarısızlık durumunda başka bir değer koymak isteseniz bile bildirimi ve başlatmayı ayırmanız gerekiyor.
      Daha büyük sorun, hata durumlarının doğru biçimde ele alınması için yeterli bağlamın bulunduğu en düşük seviyede ele alınması gerekirken, exception’ların varsayılanının “en tepeye kadar fırlatmak” olmasıdır. Tek bir dosya önbelleği eklediğinizde üst seviyedeki HandleRequest fonksiyonu birden I/O exception’ı fırlatabilir hale gelebilir; soyutlamalar kolayca sızar.
      Bir fonksiyonun döndürebileceği her şeyin açıkça imzanın parçası olması gerektiğini, çağıranın da bunu açıkça ele alması ya da yukarı aktardığını en azından belirtmesi gerektiğini düşünüyorum. Dilin bunun için çok fazla boilerplate istemesi gerekmez.
    • Rust’ın, birinci sınıf sözdizim desteği olan demiryolu odaklı programlamanın boilerplate’i neredeyse ortadan kaldırabileceğini iyi gösterdiğini düşünüyorum. Kişisel olarak Rust’ın Result tipi/trait’leri hata işlemeyi doğru yaptı.
    • Değer olarak hatalar ile exception’lar arasındaki farkın, alana özgü hataları açıklarken en faydalı olduğunu düşünüyorum. Alan hatalarını değer olarak ifade etmek doğru; veritabanının bağlanamaması gibi alanla ilgisi olmayan sorunlarda ise exception daha doğal.
      Bir dosya ya da kaydın bulunmaması da beklenip beklenmediğine göre değişir. Kullanıcının verdiği ID ile yapılan bir sorguysa, o sorgunun kendisi kullanıcı girdisi doğrulamasıdır. Ama bir veritabanı kaydında saklanan blob adıyla depolamaya sorgu atıldığında blob yoksa, bu exception olarak ele almaya uygun bir durumdur.
      Hata ile exception arasındaki sınır bulanık ve ekibe, problem alanına göre değişiyor; ama bunun makul bir pratik kural olduğunu düşünüyorum.
    • Sistem seviyesi hatalar hariç, “oluştuğu noktada ele alınması gereken belirli ve iyi tanımlanmış beklenen hata” olmayan hata durumlarına örnekler bilmek isterim. Böyle durumlarda exception’ın neden daha iyi olduğunu; bunun problemin doğasından mı, yoksa çalışma zamanı kısıtlarından mı kaynaklandığını anlamak istiyorum.
      C++ kodunda veriyi başta denetleyip, denetim başarısız olursa uygulamayı çökertip, bulunan hata durumuna göre ilk denetim kodunu debug edip düzelterek ilerlemek mümkün.
      Ele aldığım veri, bilinen bir CAD veri biçimine ya da geometrik topolojiye uyan görece açık bir veri olduğundan, doğru verinin ne olduğunu önce anlayabilmek bakımından hata koşullarını ele almak “epey kolay” sayılıyor.
  • Açıkçası bu sitenin tamamı bir hazine. Fonksiyonel dil kullanmayı düşünmeseniz bile başka bir bakış açısı kazandırıyor; bu da kişisel olarak bana çok yardımcı oldu. Diğer yazıları da öneririm.

    • Sıfırdan parser combinator oluşturma serisi, okuyup takip ettiklerim arasında en değerli kaynaklardan biriydi. Orada geçen birçok kavram ve mekanizma, tipli dilleri fonksiyonel tarzda ele alırken inanılmaz derecede faydalı oldu.
      Yine de monad’ın ne olduğunu galiba asla anlayamayacağım.
      https://fsharpforfunandprofit.com/series/understanding-parse...
  • “Hata işlemeyi çağrı noktasından uzağa itmek kötüdür” iddiasının geçerli olup olmadığını merak ediyorum. Hatayı en iyi çağıran taraf işleyebileceğine göre, aşağıya aktarmak yerine doğrudan işlemesi gerekmez mi diye düşünüyorum
    Örneğin validate and-then update-db and-then send-email akışında validate hatasını çağırana döndürmek sorun olmayabilir. Ama update-db başarısız olursa yeniden denenecek mi, başka bir servis mi kullanılacak, kuyruğa geri mi konacak, kullanıcıya mı bildirilecek buna karar vermek gerekir. send-email hatası için de aynı şey geçerli
    Sonuçta ValidationError kullanıcıya bildirilir, DBError 5 dakika sonra yeniden denenir ya da uzaktan yeniden denendikten sonra bildirilir, EmailFailed ise e-postayı tekrar kuyruğa koyup başarılı sayar gibi daha karmaşık bir dallanma yapısına dönüşmez mi diye düşünüyorum. Belki de bu kabul edilebilirdir

    • Monad tarzı hata işleme, iki tarafın avantajlarını birlikte elde etme biçimidir
      Go’dan hoşlanmayanlar hata koşullarını çok sık kontrol etmek zorunda kalmaktan yakınır; Java’nın checked exception’ları da benzerdir. Either kullanırsanız kontrol etmeye zorlanmazsınız; ister doğrudan kontrol eder, ister çağırana aktarabilirsiniz
      Java’nın unchecked exception’larından hoşlanmayanlar ise neyin ne zaman fırlatılacağını bilememekten yakınır. Either kullanırsanız bu açık hâle gelir
      Asıl nokta, hata olduğunda ne yapılacağını bilmememizdir; Either de bu bilinmezliğe iyi uyar. Mevcut bir Java kod tabanında Limit getUserLimit(User) bir değer bulamadığında NotFound exception’ı fırlatabilir, null döndürebilir ya da varsayılan değer döndürebilir; kodun içine dalmadan bunu bilemezsiniz
      Either olsaydı çağıran taraf iç kodu okumadan güvenebilirdi ve orElse(null) ya da orElseThrow(...) gibi yerleşik araçlarla bunu kolayca başka hata koşullarına dönüştürebilirdi. Ayrıca Either bir ifade olduğu için soyutlamaya uygundur; bu sayede DbUpdater içine özel yeniden deneme kodu yerine genel bir yeniden deneme mantığı yazılabilir
    • Bu mantığı Java’nın try-catch tarzıyla uygulamayı denedim; iç içe try blokları, catch(DBError), sleep(5), uzaktan yeniden deneme, EmailFailedError için yeniden kuyruğa alma ve ValidationError bildirimi birbirine girince çok daha kötü görünüyor
      https://gist.github.com/Andrewp2/9d97bd213b061166d6df565ce26...
      Birisi daha iyi bir try-catch sürümü yazabilir ama bunun sunulan demiryolu tarzı sürümden çok daha kötü olduğunu düşünüyorum
    • Düşük seviye ve yüksek seviye hata işleme birlikte var olabilir, ancak bu tarzın daha çok tahta başı örneklerine ya da bir kez yapılıp bırakılan durumlara uyduğunu düşünüyorum
      DDD’yi seviyorum ama benim gibi sıradan bir programcının yönetebileceği makul bir bakım döngüsüne ihtiyaç var. Değiştirilebilir kodda TDD’nin iyi tarafı, bakımın bilişsel yükünü azaltmasıydı
      Demiryolu tarzı programlama, birçok dönüşümden geçerken çok fazla bağlam taşıyabilir; hattın sonunda hazır olmayan iş ve sistem bağlamlarıyla yüklü bir tren almak istemem
      DDD ve onion architecture’ın iki tarafın avantajlarını sunduğunu düşünüyorum. Sonuçta yine bir demiryolu oluşur ama mikro uygulamanın içine gömülü değil, dışındadır. Demiryolu tarzı konuların açık ve biraz zahmetli biçimde kodlanması daha iyidir; iyi yapıldıysa demiryolu tarzı kararların çoğu, akıllı kodlama yapılarıyla kaçınılmaması gereken iş kararlarıdır
      Monad tarzı programlama harikadır ama gerekli iş kararlarından kaçmak için kullanma yönünde bir cazibesi vardır. Belirsizliği tip sistemine koymak, ileride zor ya da imkânsız durumlara yol açabilir
    • Düşük seviye ve yüksek seviye hata işleme birlikte var olabilir. Büyük bir try/catch içinde özelleşmiş try/catch blokları olmasından farklı değildir
      Örneğin send-email fonksiyonunun içinde çok hızlı geçici hataları işlemeyi seçebilirsiniz. İzin verilen süre içinde bağlantı kurulursa başarılı yola geri dönülür; kurulamazsa çağırana döndürülür
      Kesin bir kural varsa, insan müdahalesi gerektiren işlemlerin mutlaka sarılıp yukarı aktarılması gerektiğidir. Başarılı yola geri dönülse bile uyarı log’u bırakmak iyi olur
    • Verdiğiniz örnekteki gibi bitebilir ama aynı temel üst seviye pipeline’ı koruyarak farklı biçimde yapılandırılabilir
      notify-user, ValidationError ve DBError için kullanıcı bildirimlerinden sorumlu olur; try-update-db, update-db, 5 dakika sonra yeniden deneme ve uzaktan yeniden denemeyi bir araya getirir; try-send-email ise hata durumunda e-postayı tekrar kuyruğa koyup bunu başarıya çevirebilir
      Böylece en üst seviye validate and-then try-update-db and-then try-send-email or-then notify-user gibi korunur. and/or/then yapısını map/bind’a iyi uyarlamış gibi görünüyor
  • Bu site, gerçekçi ve pratik kavramlar öğretmesi bakımından karşılaştığım programlama eğitim siteleri arasında en iyisiydi. Özellikle geçersiz durumları ifade edilemez hâle getirme kavramını seviyorum ve dilden bağımsız olarak uygulamaya çalışıyorum. Elbette bazı diller bunu daha kolaylaştırıyor
    https://fsharpforfunandprofit.com/posts/designing-with-types...

  • Çağrı/dönüş yerine veri akışı kullanınca bu sorun daha iyi çözülür ve büyük ölçüde kendiliğinden ortadan kalkar.
    Çağrı/dönüş modelinde bir şey döndürmek gerekir; dolayısıyla hata yüzünden döndürülecek değer yoksa hatayı döndürmek ya da Go’da olduğu gibi hata ile normal dönüş değerini birlikte döndürmek gerekir. Bu da başarı yolunu kirletir. Polimorfik bir kapsayıcıyla sonraki işlemleri birbirine geçirmek biraz iyileştirir, ama sorun kalır.
    Veri akışında bir sonraki filtre aşamasına hiçbir şey göndermemek yeterlidir; bu yüzden başarı yolu hiç etkilenmez. Hatalar standart hata çıktısı gibi bir yere gönderilebilir ve uygulama düzeyinde merkezileştirilebilir.
    Kulağa fazla iyi gelebilir, ama Wunderlist’te bunu gerçekten kullandık ve iyi çalıştı. Değer olmadığında kullanılan aynı teknik, henüz değer olmadığında, yani asenkron işleme için de aynen geçerlidir.

    • Bunun veri döndürmekten ya da başka bir yerde yakalanmak üzere istisna fırlatmaktan temelde farklı olup olmadığını merak ediyorum.
    • Anlamayı kolaylaştıracak bir kod örneği olsa iyi olurdu.
    • Bu yaklaşımın da dezavantajları var ve özellikle sınırlayıcı olduğunu düşünüyorum. İstisna işleme, özellikle de asenkron istisna işleme, hata kurtarmayı zorlaştırabilir.
      Basit bir veri akışında hata oluşunca bir sonraki aşamaya veri gitmediği için durması sorun değil. Ama sqrt(-1) noktasında hemen “çöküp” hata çıktısına ya da günlüğe atlanırsa, belirli kullanım senaryolarında negatif sayılar için başka bir değer verecek şekilde alanı genişletmek istediğinizde zorlaşır.
      Bu noktada Maybe gibi açık hata tipleri işe yarar. Başarı yolu, hata yokmuş gibi yazılır; ancak fonksiyon bileşimi için farklı bir operatör kullanılır. Hatadan kurtulmak istiyorsanız o noktada iki yolu da ele alırsınız ve sonrasında artık hataya izin vermeyebilirsiniz. Soyut bir “olabilir değer”den gerçek değeri çıkarmak için iki yolu da zarif biçimde ele almanın gerekmesi özellikle güzel.
      Açık hata tipleri kullanınca başarı/hata ikiliğinin de dışına çıkabilirsiniz. “Değer yok ama…” ya da “değer var ama…” gibi durumlar da ifade edilebilir; paralel işlenebilen birden çok görevde hatalar biriktirilip sonuç olarak toplanabilir. Daha da ötesi, belirlenimsizlik kodlanarak grafik dolaşımı gibi alanlarda kullanılabilir.
      Haskell programcılarının yaptığı gibi durumu saf bir biçimde taşıyabilirsiniz; kötü şöhretli Haskell IO monadı ise gerçek bir yolu temsil etmekten çok, dış dünyayla temas noktalarını main ile sınırlayan bir düzenek gibidir. Bu saflık içindir, ama saflığın her zaman mutlak biçimde peşinden gidilmesi gereken bir değer olduğunu düşünmüyorum.
      Bu teknik basit bir demiryolundan çok daha çeşitlidir ve saflığı korurken yalnızca başarı yolunu yazmanın temiz bir yolunu sunar. Denklem tabanlı statik analiz ve mock’sız birim testleri için de avantajlıdır. Daha fazla ana akım dilin bu monad araç kutusunu daha iyi sunmasını isterdim.
  • İlgili bağlantılar: Railway Oriented Programming - https://news.ycombinator.com/item?id=31404643 - Mayıs 2022, Railway-Oriented Programming (2015) - https://news.ycombinator.com/item?id=17337155 - Haziran 2018, Railway oriented programming - https://news.ycombinator.com/item?id=11955917 - Haziran 2016, Railway Oriented Programming - https://news.ycombinator.com/item?id=9166943 - Mart 2015, Railway-oriented programming - https://news.ycombinator.com/item?id=7887134 - Haziran 2014
    Ek olarak What is railway oriented programming? (2020) - https://news.ycombinator.com/item?id=34245639 - Ocak 2023, Railway Oriented Programming in Elixir (2015) - https://news.ycombinator.com/item?id=11958578 - Haziran 2016

  • Bu stil, diğer monadlar gibi bulaşıcıdır; tüm iş mantığı bu şekle bürünür. Bu sizin için sorun değilse okumaya devam edebilirsiniz.
    İstisnalar, adından da anlaşılacağı gibi beklenmeyen hatalardır; yakalandıkları yere kadar yığını geri sarar ve gerekli temizliği otomatik olarak yapar. I/O hataları ya da bellek yetersizliği gibi, iş fonksiyonunun kapsamı dışında oluşan hatalar için uygundur.
    Veriyi doğrulamak istiyorsanız istisna işleyicisine ya da monada dayanmanız gerekmez. Fonksiyonel pipeline’ın sonucunu veri haline getirmeniz yeterlidir. Doğrulama hataları varsa bunları bir kümede toplayıp pipeline bittikten sonra kontrol edebilirsiniz. Genellikle ilk hatada başarısız olmak istememe ihtimaliniz yüksektir.
    Pipeline içinde yan etki gerekiyorsa bunu yapmayın; o etkiyi veri olarak tarif edip daha sonra çalıştırın. Ortada erken çıkmanız gerekiyorsa pipeline’ı o noktada bölün, sonucu işleyin ve sonra başka bir pipeline’a koyun.
    Ya da başarı yolunun devam edip etmeyeceğine karar vermesi için bir interceptor zinciri kullanabilirsiniz. Dil destekliyorsa pattern matching kullanabilirsiniz. Önemli olan, pipeline’ın bir kısmının atlanıp atlanmayacağına dair kararın iş fonksiyonunun dışında verilmesidir; böylece iş fonksiyonu yalnızca saf veri dönüşümüne odaklanır ve yeniden kullanılabilirliği artar.

  • Böyle bir konunun yeniden gündeme gelip tartışılması bile güzel. Bu siteyi bir yıl boyunca okudum ve örnekleri takip ettim; en iyi F# kaynaklarından biriydi.
    Bu sitede ele alınan kavramlar tek başına bile tüm dillerde fonksiyonel düşünmeye yardımcı oldu.
    Eski bir site; şimdi ilgi görüyorsa bu F#’ın ivme kazandığı anlamına mı geliyor merak ediyorum. Ayrıca başka dillerdeki programcıların da Rust’taki gibi demiryolu tarzı hata işlemeyi kullanıp kullanmadığını merak ediyorum.

    • Masaüstü uygulamaları için güçlü bir C# geçmişim var ve zaman geçtikçe C# kodlama tarzım daha fonksiyonel hale geliyor. Linq’i çok kullanmak, değişmez sınıflar ve ayrıntılı hata bilgisi taşıyabilen dönüş tipleri kullanmak gibi.
      Yine de F#’a geçemememin nedeni, zamanımın çoğunu masaüstü frontend’ini ayarlamakla geçirmem. Bu alanda F# pek avantaj sağlamıyor gibi görünüyor. Üretime hazır masaüstü frontend’leri yerel F# değil, çoğunlukla C# tabanlı nesne yönelimli kod oluyor ve F# ile ince bir glue code katmanıyla bağlanıyor.
      Biraz olsun karmaşık UI özelleştirmesi gerektiğinde yeniden nesne yönelimli dünyaya dönmek gerekiyor; öyle olacaksa bunu doğrudan C#’ta yapmak daha iyi bence. Bana göre F#’ın ivme kazanması için yerel bir UI framework’üne ihtiyacı var.
    • C++’ta da böyle bir yaklaşım kullanıyoruz. Dönüş tipi T ve sonuç koşulunu tanımlayan belirli bir enum ile parametrize edilebilen bir template dönüş tipi tanımlıyoruz.
      Acele durumlarda std::pair ile de yapılabilir; hata metnini bir string içinde döndürüp string boş değilse erken çıkmak da mümkün. Tüm fonksiyonlar böyle bir tip döndürürse hata metnini en üste kadar bubble up ettirebilirsiniz.
  • Bu yazının birkaç yılda bir yeniden gündeme gelmesini seviyorum. Gerçekten harika bir yazı ve sunum.
    Her yeniden okuduğumda teknik yolculuğumda beceri ya da felsefe açısından farklı bir yerde oluyorum; bu yüzden her seferinde farklı görünmesi de ilginç.

    • Böyle kaynakları gerçekten seviyorum. Python sunumları arasında “so you want to be a python expert” adlı sunumu ilk zamanlarda birkaç yıl boyunca her 6 ayda bir izledim; her izlediğimde daha fazlasını anladım. Şimdi tekrar izlesem muhtemelen yine yeni bir şeyler çıkarırım.