Scala'da ilk Tip Sınıfınızı yaratmanın 5 adımı

Bu blog yazısında, fonksiyonel programlama dilleri simgesinin - Haskell - temel dil özelliği olan birinci sınıf sınıfınızı nasıl uygulayacağınızı öğreneceksiniz.

Unsplash'ta Stanley Dai tarafından fotoğraf

Tip Sınıfı Haskell kaynaklı bir kalıptır ve polimorfizmi uygulamanın standart yoludur. Bu tür polimorfizm, geçici polimorfizm olarak adlandırılır. Adı, iyi bilinen alt tip polimorfizminin aksine, bazı işlevselliklerini genişletmek istediğimizden, kütüphane ve sınıfın kaynak koduna erişmeden bile, hangi işlevselliği genişletmek istediğimizi genişletebiliriz.

Bu yazıda, tip sınıflarını kullanmanın normal OOP polimorfizmini kullanmak kadar uygun olabileceğini göreceksiniz. Aşağıdaki içerik, işlevsel programlama kütüphanelerinin dahili bilgisini daha iyi anlamanıza yardımcı olmak için Tip Sınıfı modelinin tüm uygulama aşamalarında size yol gösterecektir.

İlk Tip Sınıfınızı Oluşturma

Teknik olarak Tip Class, sadece bu özelliği genişleten sınıflarda uygulanabilecek soyut yöntemlerin bulunduğu parametreli bir özelliktir. Her şey çok iyi bilinen bir alt yazma modelinde gibi görünüyor.
Tek fark, alt-yazmanın yardımıyla, bir etki alanı modeli olan sınıflarda kontratı uygulamak zorunda olduğumuzdur, Tip Sınıfları'nda özellik uygulamasının tip parametresi ile “etki alanı sınıfı” ile bağlantılı tamamen farklı bir sınıfa yerleştirilmesi gerekir.

Bu yazıda örnek olarak Cats kütüphanesinden Eq Type Class kullanacağım.

özellik Eq [A] {
  def areEquals (a: A, b: A): Boolean
}

Tip Sınıf Eşit [A], A tipi iki nesnenin areEquals yönteminde uygulanan bazı kriterlere göre eşit olup olmadığını kontrol etme kabiliyetine sahip bir sözleşmedir.

Tür Sınıfımızın bir örneğini oluşturmak, söz konusu özelliği genişleten, yalnızca sınıf sınıfı örneğimizin örtük nesne olarak erişilebilir olacağı farkını veren örnekleme sınıfı kadar basittir.

def moduloEq (bölen: Int): Eq [Int] = yeni Eq [Int] {
 def geçersiz kıl areEquals (a: Int, b: Int) = bir% bölen == b% bölen
}
üstü kapalı val modulo5Eq: Eq [Int] = moduloEq (5)

Yukarıdaki kod parçası, aşağıdaki biçimde biraz sıkıştırılabilir.

def moduloEq: Eq [Int] = (a: Int, b: Int) =>% 5 ==% b

Fakat bekleyin, nasıl fonksiyon atayabilirsiniz (Int, Int) => Boolean Eq [Int] tipine atıfta bulunmak için ?! Bu, Tek Soyut Yöntem Arabirim türü adı verilen Java 8 özelliği sayesinde mümkündür. Özelliğimizde yalnızca bir soyut yöntem olduğunda böyle bir şey yapabiliriz.

Tip Sınıfı çözünürlük

Bu paragrafta, size tip sınıfı örneklerini nasıl kullanacağınızı ve gerektiğinde Eq [A] tipini gerekli olduğunda A tipine karşılık gelen nesne ile nasıl sihirli bir şekilde birleştireceğinizi göstereceğim.

Burada, modulo bölme değerlerinin eşit olup olmadığını kontrol ederek iki Int değerini karşılaştırma işlevini uyguladık. Bütün bu işleri yaptıktan sonra, Tip Class'ımızı, örneğin bazı iş mantıklarını gerçekleştirmek için kullanabiliriz. moduloya eşit iki değeri eşleştirmek istiyoruz.

def pairEquals [A] (a: A, b: A) (örtük eq: Eq [A]): ​​Seçenek [(A, A)] = {
 if (eq.areEquals (a, b)) Bazı ((a, b)) başka hiçbiri
}

Function pairEquals parametresini, örtülü kapsamı içinde mevcut olan Eq [A] sınıfı örneğini sağlayan herhangi bir türle çalışmak üzere çalışıyoruz.

Derleyici, yukarıdaki bildirime uyan herhangi bir örnek bulamazsa, belirtilen kapsamda uygun örnek bulunmadığına dair derleme hatası uyarısı ile sonuçlanır.
  1. Derleyici, fonksiyonumuza argümanlar uygulayarak ve diğerine A atayarak sağlanan parametrelerin türünü çıkartacaktır.
  2. Önceki değişken eq: Eq [A] anahtar kelimesi örtülü ile birlikte örtülü kapsamda Eq [A] türündeki nesneyi aramak için öneriyi tetikler.

Örtüler ve yazılan parametreler sayesinde, derleyici, ilgili sınıf sınıfı örneğiyle birlikte sınıfı bağlayabilir.

Tüm örnekler ve fonksiyonlar tanımlanmıştır, kodumuzun geçerli sonuçlar verip vermediğini kontrol edelim

pairEquals (2,7)
res0: Seçenek [(Int, Int)] = Bazı ((2,7))
pairEquals (2,3)
res0: Seçenek [(Int, Int)] = Yok

Gördüğünüz gibi beklenen sonuçlar aldık, böylece sınıf sınıfımız iyi çalışıyor. Ancak bu, oldukça fazla miktarda kazan plakasıyla biraz karışık görünüyor. Scala’nın sözdiziminin büyüsü sayesinde, çok fazla kazanın kaybolmasını sağlayabiliriz.

Bağlam Sınırları

Kodumuzda geliştirmek istediğim ilk şey, ikinci argüman listesinden kurtulmak (örtük anahtar kelimeli). Fonksiyonu çağırırken doğrudan ondan geçmiyoruz, bu yüzden tekrar örtük kalsın. Scala'da örtük argümanlarda tür parametreli argümanlar Context Bound adlı bir dil yapısı ile değiştirilebilir.

Bağlam Sınırı, A: Eq sözdiziminin tip parametre listesindeki beyan olup, pairEquals işlevi argümanı olarak kullanılan her türün, örtülü kapsamda Eq [A] tipinde örtülü değere sahip olması gerektiğini söyler.

def pairEquals [A: Eq] (a: A, b: A): Seçenek [(A, A)] = {
 eğer (dolaylı olarak [Eq [A]]. areEquals (a, b)) Bazı ((a, b)) bazıları Yok
}

Fark ettiğiniz gibi, örtülü değere işaret eden hiçbir referansımız kalmadı. Bu sorunun üstesinden gelmek için, hangi türden bahsettiğimizi belirleyerek örtülü bulunan değeri çeken işlevi [F [_]] kullanıyoruz.

Scala dilinin bize her şeyi daha kısa yapmak için sunduğu şey budur. Yine de benim için yeterince iyi görünmüyor. Bağlam Bağlamı gerçekten harika bir sözdizimsel şekerdir, ancak bu açıkça kodumuzu kirletiyor gibi görünmektedir. Bu sorunun üstesinden gelmek ve uygulama ayrıntılarını azaltmak için iyi bir numara yapacağım.

Yapabileceğimiz şey, bizim tür sınıfımızın eşlik eden nesnesindeki parametreli uygulama işlevini sağlamaktır.

nesne Eq {
 def uygu [A] (örtük eq: Eq [A]): ​​Eq [A] = eq
}

Bu gerçekten basit olan şey, dolaylı olarak kurtulmamızı ve örneğimizi, sınırsız alan mantığında kullanılacak limbodan örneklememizi sağlar.

def pairEquals [A: Eq] (a: A, b: A): Seçenek [(A, A)] = {
 if (Eq [A] .areEquals (a, b)) Bazı ((a, b)) diğerleri Yok
}

Örtük dönüşümler - aka. Sözdizimi modülü

Tezgahıma almak istediğim bir sonraki şey Eq [A] .areEquals (a, b). Bu sözdizimi çok ayrıntılı görünüyor, çünkü açıkça örtük olması gereken tip sınıfı örneğine başvuruyoruz, değil mi? İkincisi, burada bizim sınıf sınıfı örneğimiz gerçek A sınıfı uzantısı yerine Servis (DDD anlamında) gibi davranıyor. Neyse ki, bu da gizli anahtar kelimenin başka bir yararlı kullanımı yardımı ile düzeltilebilir.

Burada yapacağımız şey, kaynak kodunu değiştirmeden bir sınıfın API'sini genişletmemize izin veren kapalı dönüşümleri kullanarak sözdizimi veya (bazı FP kitaplıklarında olduğu gibi) modülünü sağlamaktır.

üstü kapalı sınıf EqSyntax [A: Eq] (a: A) {
 def === (b: A): Boolean = Eq [A] .areEquals (a, b)
}

Bu kod, derleyiciye, Eq [A] türündeki örneğe sahip A sınıfının === işlevine sahip olan EqSyntax sınıfına dönüştürmesini bildirir. Bütün bunlar, kaynak kodu değişikliği olmadan A sınıfına işlev === eklediğimize dair bir izlenim bırakıyor.

Yalnızca gizli tip sınıf örneği referansını değil, bu sınıf hakkında hiçbir şey bilmesek bile, A = sınıfında uygulanmakta olan === metodunun izlenimini sağlayan daha fazla sınıf sözdizimi sağlıyoruz. İki taş bir taşla öldürüldü.

Şimdi ne zaman kapsamda EqSyntax sınıfına sahipsek, A = metodunu uygulayalım. Şimdi pairEquals uygulamamız biraz değişecek ve aşağıdaki gibi olacaktır.

def pairEquals [A: Eq] (a: A, b: A): Seçenek [(A, A)] = {
 if (a === b) Bazı ((a, b)) diğerleri Yok
}

Söz verdiğim gibi, AOP parametresinden sonra OOP uygulamasına kıyasla tek görünür farkın Bağlam Bağlamı açıklaması olduğu uygulama sonunda sona erdik. Tip sınıfının tüm teknik yönleri alan mantığımızdan ayrılmıştır. Bu, kodunuza zarar vermeden çok daha güzel şeyler (ayrı bir makalede yakında ne yayınlanacak) dediğim gibi başarabileceğiniz anlamına gelir.

Örtük kapsam

Gördüğünüz gibi Scala'daki sınıflar kesinlikle örtük özellik kullanmaya bağlıdır, bu nedenle örtük kapsamla nasıl çalışılacağını anlamak esastır.

Örtük kapsam, derleyicinin örtük örnekleri arayacağı bir kapsamdır. Çok fazla seçenek var, bu yüzden örneklerin arandığı bir sipariş tanımlamaya ihtiyaç vardı. Sipariş aşağıdaki gibidir:

1. Yerel ve miras alınan örnekler
2. Alınan örnekler
3. Type sınıfının eşlik eden nesnesinden veya parametrelerinden tanımlar

Bu çok önemlidir, çünkü derleyici birkaç örnek bulduğunda veya hiç örneği bulunmadığında bir hataya neden olur. Benim için tip sınıflarının örneklerini elde etmenin en uygun yolu, onları tip sınıfının eşlik eden nesnesine yerleştirmektir. Bu sayede, konum sorunlarını unutmamıza izin veren yerinde örnekleri içe aktarırken veya uygularken kendimizi rahatsız etmemize gerek yok. Her şey sihirli bir şekilde derleyici tarafından sağlanır.

Öyleyse, Scala’nın standart kütüphanesinden iyi bilinen bir fonksiyon örneğini kullanarak hangi işlevselliğin örtük olarak sağlanan karşılaştırıcılara dayandığını gösteren 3. noktayı tartışalım.

sıralı [B>: A] (üstü kapalı: matematik. Sıralama [B]): Liste [A]

Tip sınıfı örneği aranacaktır:
 * Sipariş arkadaşı nesnesi
 * Liste arkadaşı nesnesi
 * B eşlik eden nesne (alt sınır tanımının varlığı nedeniyle aynı zamanda bir eşzamanlı nesne olabilir)

imge

Bunlar, sınıf sınıfı modelini kullanırken çok yardımcı olurlar, ancak bunlar her projede yapılması gereken tekrarlanabilir bir iştir. Bu ipuçları, sürecin kütüphaneye çıkarılabileceğinin açık bir işaretidir. Simulacrum adında mükemmel bir makro-temelli kütüphane vardır; bu, sözdizimi modülü (Simulacrum'da ops olarak adlandırılır) oluşturmak için gereken her şeyi elle idare eder.

Tanıtmamız gereken tek değişiklik, sözdizimi modülümüzü genişletmek için makroların işareti olan @ tipi sınıf açıklamasıdır.

alma simulacrum._
@typeclass özelliği Eq [A] {
 @op (“===”) def areEquals (a: A, b: A): Boolean
}

Uygulamamızın diğer kısımları herhangi bir değişiklik gerektirmez. Bu kadar. Artık Scala'da sınıf sınıfı modelini kendiniz nasıl uygulayacağınızı biliyorsunuz ve umarım Simulacrum'un nasıl çalıştığı hakkında kütüphaneler hakkında farkındalık kazanmışsınızdır.

Okuduğunuz için teşekkürler, sizden gelen geri bildirimleri gerçekten takdir ediyorum ve gelecekte başka bir yayınlanmış makaleyle görüşmek için sabırsızlanıyorum.