Miyav! Projenizde şimdi Cats kullanmaya başlayın

Kediler kütüphanesine nazik giriş.

Giriş

Cats, Scala'da fonksiyonel programlama için soyutlamalar sağlayan bir kütüphanedir.

İnternette Cats'te birkaç harika gönderi ve kurs var (Herding cats ve Scala Exercises'te bir öğretici gibi), ancak pratikte hazır hale getirmek yerine kütüphanede uygulanan kategorileri / tip sınıfları keşfetme eğilimindedirler. Cat'lerin mevcut kod tabanlarında nasıl kullanılacağına ilişkin örnekleri kullanın. Bu blog yazısı, Cats'in yapabileceklerinin yüzeyini zar zor çiziyor, ancak bunun yerine, Scala projenizde en fazla yararlanmanızın muhtemel olduğu kalıplara kısa bir uygulamalı giriş sunuyor. Günlük olarak Gelecek veya Seçenek gibi bir monad kullanıyorsanız, Cat'lerin kodunuzun okunabilirliğini basitleştirmesi ve iyileştirmesi çok muhtemeldir.

Kütüphanenin proje bağımlılıklarınıza nasıl ekleneceğine ilişkin yönergeler için lütfen GitHub'daki Cats wiki'sine bakın. Tüm gönderide 0.9.0 sürümüne bağlıyız.

Her pakette mevcut olan sözdizimine bakarak kütüphanede bilge kütüphaneden geçelim.

Option ve Her ikisi için Yardımcıları

cats.syntax.option._ dosyasını içe aktar

Bu paketi içe aktarmak obj.some sözdizimini sağlar - Some (obj) ile eşdeğerdir. Tek gerçek fark, değerin zaten [T] 'den Seçenek [T]' ye yükseltilmiş olmasıdır.

Bazıları (obj) yerine obj.some kullanmak bazen birim testlerinin okunabilirliğini artırabilir. Örneğin, aşağıdaki gizli sınıfı BaseSpec'inize, TestHelper'a veya sınamalar için temel sınıfınız ne denirse eklerseniz:

daha sonra aşağıda gösterilen zincirleme sözdizimini kullanabilirsiniz (birim testlerinizin scalamock'a dayandığını varsayarak; ayrıca Bartosz Kowalik tarafından gönderilen bir yazıya bakınız):

Bu, Future.successful (Some (kullanıcı)) 'dan daha okunaklıdır, özellikle de bu desen test odasında sık sık tekrarlanıyorsa. Zincirleme .some.asFuture, öne koymak yerine sonunda da beklenen sarıcı türü yerine gerçekte ne iade edildiğine odaklanmaya yardımcı olur.

hiçbiri [T], yalnızca Yok olan Option.empty [T] için kısa yoldur, ancak zaten Yok. Daha özel bir tür sağlamak, bazen Scala derleyicisinin Yok içeren ifadelerin türünü doğru şekilde çıkarmasına yardımcı olur.

cats.syntax.either._ dosyasını içe aktar

obj.asRight Haktır (obj), obj.asLeft Bırakılmıştır (obj). Her iki durumda da, döndürülen değerin türü Sağdan veya Soldan İkisine genişletilir. Bazı durumlarda olduğu gibi, bu yardımcılar birim testlerinin okunabilirliğini artırmak için .asFuture ile birleştirmek için kullanışlıdır:

Her iki.fromOption (seçenek: Seçenek [A], ifNone: => E), sırasıyla, bir Seçeneği Her İkisine dönüştürmek için yararlı bir yardımcıdır. Sağlanan seçenek Bazı (x) ise, Sağ (x) olur. Aksi halde, sağlanan ifNone değerinde Left olur.

örnekleri paketleri ve kartezyen sözdizimi

cats.instances öğesini içe aktarın. ._

En önemlileri Functor, Uygulayıcı ve Monad olan Cats (ve genel olarak kategori tabanlı fonksiyonel programlama) için temel olan birkaç tip sınıf vardır. Bu blog yazısında çok fazla ayrıntıya girmeyeceğiz (örneğin, daha önce sözü edilen öğreticiye bakın), ancak bilmeniz gereken önemli, çoğu Cats sözdizimini kullanmak için, aynı zamanda yapılarınız için örtülü tür sınıfı örneklerini de içe aktarmanız gerekir. ile çalışıyorum.

Genellikle uygun cats.instances paketini içe aktarmak yeterlidir. Örneğin, vadeli işlemlerde dönüşümleri yaparken, cats.instances.future._ dosyasını içe aktarmanız gerekir. Seçenekler ve listeler için karşılık gelen paketler cats.instances.option._ ve cats.instances.list._ olarak adlandırılır. Cats sözdiziminin düzgün çalışması için gerekli tip sınıfı örnekleri sağlarlar.

Yan not olarak, gerekli örnekleri veya sözdizimi paketini bulmakta sorun yaşıyorsanız, hızlı geçici çözüm sadece cats.implicits._ dosyasını almaktır. Bu, derleme sürelerini önemli ölçüde artırabildiğinden, özellikle proje genelinde birçok dosyada kullanıldığında, tercih edilen bir çözüm değildir. Derleyiciden örtük çözüm yükünün bir kısmını almak için dar ithalat kullanmak genellikle iyi bir uygulama olarak kabul edilir.

import cats.syntax.cartesian._

Kartezyen paketi sağlar | @ | Birden fazla etkin değere (futures gibi) birden fazla parametre alan bir işlevi uygulamak için sezgisel bir yapıya izin veren sözdizimi.

Diyelim ki 3 adet futures'ımız var, biri Int, biri String, biri Kullanıcı ve biri de üç parametre kabul eden bir yöntem - Int, String ve Kullanıcı.

Amacımız, işlevi bu 3 vadeli tarafından hesaplanan değerlere uygulamaktır. Kartezyen sözdizimi ile bu çok kolay ve öz hale gelir:

Daha önce de belirtildiği gibi, | @ | Düzgün çalışabilmesi için cats.instances.future._ dosyasını içe aktarmanız gerekir.

Bu yukarıdaki fikir, daha da kısaca ifade edilebilir:

Yukarıdaki ifadenin sonucu Future [ProcessingResult] türünde olacaktır. Zincirleme vadeli işlemlerden herhangi biri arızalanırsa, ortaya çıkan gelecek, zincirdeki ilk başarısız gelecekle aynı istisna ile başarısız olur (bu, başarısız hızlı davranıştır). Önemli olan, tüm vadeli işlemler bir anlamada ne olacağının aksine paralel olarak çalışacaktır:

Yukarıdaki kod parçasında (kaputun altında, flatMap ve harita aramalarına çevrilir) stringFuture, intFuture başarıyla tamamlanana kadar çalışmaz ve aynı şekilde userFuture yalnızca stringFuture tamamlandıktan sonra çalıştırılır. Ancak hesaplamalar birbirinden bağımsız olduğu için, | @ | yerine.

traversing

cats.syntax.traverse._ dosyasını içe aktarın

çapraz

Eşleştirilebilecek (Gelecekte olduğu gibi) eşleştirilebilecek F [A] türünde bir objeniz ve A => G [B] türünde eğlenceli bir fonksiyonunuz varsa, obj.map (fun) öğesini çağırmak size F [G [ A]]. Pek çok yaygın gerçek hayatta, F'nin Seçenek ve G'nin Gelecek olduğu zaman olduğu gibi, muhtemelen istediğiniz şey olmayan Seçenek [Gelecek [B]] alırsınız.

travers burada bir çözüm olarak geliyor. Eğer obj.traverse (eğlenceli) gibi bir harita yerine geçişi çağırırsanız, bu durumda Gelecek [Seçenek [B]] olacak G [F [A]]; bu, Seçenek [Gelecek [B]] öğesinden çok daha kullanışlı ve işlenmesi daha kolaydır.

Bir yandan not olarak, Gelecekteki eşlikçi nesnesindeki Future.traverse özel bir yöntemi de vardır, ancak Cat sürümü çok daha okunaklıdır ve belirli tip sınıfların mevcut olduğu herhangi bir yapı üzerinde kolayca çalışabilir.

sıra

sekans daha basit bir konsepti temsil eder: basitçe F [G [A]] 'den G [F [A]]' ya, travers gibi çevrelenmiş değeri haritalamadan bile kolayca değiştirilebileceğini düşünebilirsiniz.

obj.sequence aslında Kedilerde obj.traverse (kimlik) olarak uygulanır. Öte yandan, obj.traverse (fun), obj.map (fun) .sequence ile kabaca eşittir.

flatTraverse

F [A] tipinde bir objeniz ve A => G [F [B]] tipinde eğlenceli bir fonksiyonunuz varsa, obj.map (f) yapmak F [G [F [B]]] tipinin sonucunu verir. - İstediğin gibi olma ihtimalin çok düşük.

Eşleme yerine objeyi gezdirmek biraz yardımcı olur - bunun yerine G [F [F [B]] elde edersiniz. G, genellikle Future ve F'nin List ya da Option gibi bir şey olduğundan, Future [Option [Option [A]] ya da Future [List [List [List [A]]]] ile bitirdiniz - işlenmesi biraz garip.

Çözüm, sonucu şöyle bir _.flatten çağrısıyla eşleştirmek olabilir:

ve bu şekilde istediğiniz G [F [B]] tipini elde edersiniz.

Ancak, bunun için flatTraverse adı verilen düzgün bir kısayol var:

ve bu sorunumuzu iyi çözdü.

Monad transformatörleri

İthalat cats.data.OptionT

OptionT [F, A] örneği, F veya Option'ın kendisinde bulunmayan yuvalanmış türlere özgü birkaç yararlı yöntem ekleyen F [Seçenek [A]] üzerinde bir sarmalayıcı olarak düşünülebilir. En tipik haliyle, F'niz Gelecek (ya da bazen kaygan DBIO olacaktır, ancak bunun için Functor ya da DBIO için Monad gibi bir Cats tipi sınıf uygulaması yapılmasını gerektirir). OptionT gibi sarmalayıcılar genellikle monad transformatörleri olarak bilinir.

Oldukça yaygın bir örnek, bir F [Seçenek [A]] örneği içinde depolanan iç değeri, A => B türündeki bir işlevle F [Seçenek [B]] örneğiyle eşlemektir. Bu, oldukça ayrıntılı sözdizimi ile yapılabilir. sevmek:

OptionT kullanımı ile, bu aşağıdaki gibi basitleştirilebilir:

Yukarıdaki harita OptionT [Future, String] türünde bir değer döndürecektir.

Altta yatan Future [Option [String]] değerini almak için, OptionT örneğinde sadece .value'yi çağırın. Ayrıca, yöntem parametre / dönüş türlerinde OptionT [Future, A] seçeneğine tamamen geçiş yapmak ve tür bildirimlerinde Future (Option [A]] tamamen (veya neredeyse tamamen) hendek açmak için uygun bir çözümdür.

OptionT örneği oluşturmanın birkaç yolu vardır. Aşağıdaki tablodaki yöntem başlıkları biraz sadeleştirilmiştir: Her bir yöntemin gerektirdiği tür parametreleri ve tür sınıfları atlanmıştır.

Üretim kodunda, bir Gelecek [Seçenek [A]] örneğini Seçenek [F, A] içine kaydırmak için en çok OptionT (...) sözdizimini kullanırsınız. Diğer metotlar ise, birim testlerinde OptionT tipi sahte değerlerin ayarlanması için faydalıdır.

Biz zaten OptionT'ın yöntemlerinden biri olan haritaya rastladık. Kullanılabilecek birkaç başka yöntem vardır ve bunlar çoğunlukla parametre olarak kabul ettikleri işlevin imzasına göre farklılık gösterir. Önceki tabloda olduğu gibi, beklenen tip sınıfları atlandı.

Uygulamada, map ve semiflatMap'i kullanmanız çok muhtemeldir.

Her zaman olduğu gibi flatMap ve map'te olduğu gibi, onu sadece açıkça değil, aynı zamanda anlamada kaputun altında da, aşağıdaki örnekte olduğu gibi kullanabilirsiniz:

GetReservedFundsForUser tarafından döndürülen OptionT [Future, Money] örneği, oluşan üç yöntemden herhangi birinin Yok'a karşılık gelen bir OptionT döndürmesi durumunda bir Nonevalue içerecektir. Aksi halde, üç çağrının sonucu da Bazıları içeriyorsa, son sonuç da Bazılarını içerecektir.

İthalat cats.data.EitherT

Her iki T de [F, A, B] Her ikisi için bir monad transformatörüdür - bunu bir F [Her [A, B]] değeri üzerinde bir sarıcı olarak düşünebilirsiniz.

Yukarıdaki bölümde olduğu gibi, yöntem başlıklarını basitleştirdim, tip parametrelerini ya da bağlam sınırlarını ve alt sınırları atlayarak.

EitherT örneğinin nasıl oluşturulduğuna bir göz atalım:

Sadece açıklığa kavuşturmak için: EitherT.fromEither, sağlanan E'yi F'ye, oysa, EitherT.right ve EitherT.left, verilen F içindeki değeri sırasıyla Sırası ve Soluna kaydırır. Her iki T.pure de, verilen B değerini Sağa ve sonra F'ye kaydırır.

Bir EitherT örneği oluşturmanın başka bir faydalı yolu, OptionT'in metotlarını Sol ve toRight'a kullanmaktır:

toRight, daha önce bahsettiğimiz Either.fromOption yöntemine oldukça benziyor: tıpkı bir Option'dan bir Seçenek seçeneğinden olduğu gibi, toRight, OptionT'den bir EitherT oluşturur. Orijinal OptionTstores Bazı değerler ise, Sağa sarılır; Aksi takdirde, sol parametre olarak verilen değer bir Sola sarılır.

toLeft, Some değerini Sola sararak ve hiçbirini Sağa çevirerek sağa doğru döndüren ToRight‘ 'ın karşılığıdır. Bu, pratikte daha az kullanılan, ancak örneğin benzersizlik kontrolleri zorlamak için kodda. Değer bulunmuşsa Sol ve sistemde henüz mevcut değilse, Sağa döneriz.

EitherT'de bulunan yöntemler OptionT'de gördüğümüz yöntemlere oldukça benzer, ancak dikkate değer farklılıklar var. Örneğin, ilk başta, bazı karmaşalara maruz kalabilirsiniz. harita. OptionT durumunda, ne yapılması gerektiği çok açıktı: harita Geleceğin içindeki Seçeneği gözden geçirmeli ve sonra ekteki Seçeneğin kendisini eşlemelidir. Her iki durumda da bu durum biraz daha az açıktır: Hem Leftand Right hem de sadece Right değeri üzerine mi harita vermeli?

Cevap, EitherT'nin doğru önyargılı olduğu, bu nedenle düz harita aslında Doğru değerle ilgileniyor. Bu, Scala standart kütüphanesinde 2.11'e kadar olanlardan farklıdır, ki sırayla tarafsızdır: Her ikisinde de yalnızca sol ve sağ çıkıntıları için harita yoktur.

Bunu söyledikten sonra, EitherT [F, A, B] 'nin sunduğu doğru taraflı yöntemlere hızlıca bir göz atalım:

Bir yandan not olarak, EitherT'de (bir noktada ihtiyaç duymanız muhtemel olan) ayrıca Left değeri, leftMap gibi veya katlama veya bimap gibi hem Sol hem de Sağ değerlerin üzerinde eşlenen belirli yöntemler de vardır.

Her ikisi de başarısız hızlı zincirleme doğrulamalar için çok kullanışlıdır:

Yukarıdaki örnekte, öğeye karşı tek tek çeşitli kontroller yapıyoruz. Kontrollerden herhangi biri başarısız olursa, ortaya çıkan EitherT bir Sol değer içerecektir. Aksi takdirde, tüm kontroller bir Hak verirse (elbette EitherT'ye sarılmış bir Hak anlamına geliriz), o zaman sonuç da Hak'ı içerecektir. Bu başarısız hızlı bir davranıştır: İlk sol-sonuç sonucundaki kavrama akışını etkili bir şekilde durduruyoruz.

Bunun yerine, hataları biriktiren doğrulama (örneğin, kullanıcı tarafından sağlanan form verileriyle uğraşırken) arıyorsanız, cats.data.Validated iyi bir seçim olabilir.

Ortak sorunlar

Herhangi bir şey beklendiği gibi derlenmezse, önce tüm gerekli Cats etkilerinin kapsamda olduğundan emin olun - sadece cats.implicits._ dosyasını içe aktarmayı deneyin ve sorunun devam edip etmediğini kontrol edin. Daha önce de belirtildiği gibi, dar ithalat kullanmak daha iyidir, ancak kod derlenmezse, sorunun çözülüp çözülmediğini kontrol etmek için bazen sadece kitaplığın tamamını içe aktarmanın değeri olur.

Futures kullanıyorsanız, kapsamda örtük bir ExecutionContext sağladığınızdan emin olun, aksi takdirde, Cat'ler Future type tip sınıfları için örtük örnekler çıkaramazlar.

Derleyici, travers ve sekansmetreler için genellikle çıkarım tipi parametrelerle ilgili sorunlar yaşayabilir. Açık bir geçici çözüm, bu türleri list.traverse [Future, Unit] (eğlence) gibi doğrudan belirtmektir. Bununla birlikte, bazı durumlarda bu oldukça ayrıntılı olabilir ve list.traverseU (fun) gibi traverseU ve arrayU yöntemlerini denemek daha iyidir. Derleyicinin tip parametrelerini çıkarmasına yardımcı olmak için bazı tip seviyesinde hile yaparlar (cats.Unply, dolayısıyla U ile).

IntelliJ, bazen kaynak scalac altından geçse bile Cats yüklü kodta hatalar bildirir. Bu tür bir örnek, skala altında doğru bir şekilde derlenen fakat IntelliJ’in sunum derleyicisinin altına bir çek yazamayan cats.data.Nested sınıfının yöntemlerinin çağrılarıdır. Yine de, Scala IDE kapsamında sorunsuz çalışması gerekiyor.

Gelecekteki öğreniminiz için bir öneri olarak: Uygulamalı tip sınıf, işlevsel programlamadaki anahtar önemine rağmen, anlaşılması biraz zor olur. Bana göre miras hiyerarşisinde Functor ve Monad arasında durmasına rağmen, Functor veya Monad'dan çok daha az sezgisel. Kavramak için en iyi yaklaşım Uygulayıcı ilk olarak, biraz egzotik ap işlemine odaklanmak yerine (bir F [A] ve F [B] 'yi bir F [(A, B)]' ye dönüştüren ürünün) nasıl çalıştığını anlamaktır.