Scala-da sizning birinchi birinchi sinfingizni yaratishning 5 bosqichi

Ushbu blog postida siz funktsional dasturlash tillari ikonkasida - Haskellning asosiy til xususiyati bo'lgan birinchi sinfingizni qanday amalga oshirish haqida bilib olasiz.

Stenli Dey surati Unsplash-da

Type Class - bu Haskelldan kelib chiqqan va polimorfizmni amalga oshirishning odatiy usuli. Bunday polimorfizmga ad-hok polimorfizm deyiladi. Uning nomi shundan kelib chiqadiki, taniqli pastki matn terish polimorfizmidan farqli ravishda, biz foydalanmoqchi bo'lgan kutubxonaning va kodning dastlabki kodlaridan foydalanmasdan ham kutubxonaning ba'zi funktsional imkoniyatlarini kengaytira olamiz.

Ushbu xabarda siz tipli sinflardan foydalanish odatdagi OOP polimorfizmidan foydalanish kabi qulay bo'lishi mumkinligini ko'rasiz. Quyidagi tarkib sizga funktsional dasturlash kutubxonalarining ichki qismini yaxshiroq tushunishga yordam berish uchun Type Class naqshini amalga oshirishning barcha bosqichlarida yordam beradi.

Birinchi turdagi sinfingizni yaratish

Texnik jihatdan tip - bu aniqlangan belgi, bu mavqega ega usullar soni va shu xususiyatni kengaytiruvchi sinflarda bajarilishi mumkin. Hozircha hamma taniqli pastki terish modelidagi kabi ko'rinadi.
Faqatgina farq shundaki, sub-terish yordamida shartnomani domen modelining bir bo'lagi bo'lgan sinflarda bajarishimiz kerak, Type Class-larda belgilarni bajarish parametr parametrlari bo'yicha "domen sinfi" bilan bog'langan mutlaqo boshqa sinfga joylashtirilgan.

Ushbu maqolada misol sifatida men mushuklar kutubxonasidagi Eq tipidagi klassdan foydalanaman.

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

Eq [A] toifasi - AreEquals metodida amalga oshirilgan ba'zi mezonlar asosida A turidagi ikkita ob'ekt teng yoki yo'qligini tekshirish imkoniyatiga ega bo'lgan shartnoma.

Bizning Type sinfimiz namunasini yaratish, qo'zg'atuvchi sinf singari oddiy, bu belgi kengaytiriladigan ob'ekt sifatida kirish mumkin bo'lgan farqni faqat bitta farq bilan kengaytiradi.

def moduloEq (bo'linuvchi: Int): Eq [Int] = yangi Eq [Int] {
 def defEquals (a: Int, b: Int) = a% bo'luvchi == b% bo'luvchi
}
yashirin val moduli5Eq: Eq [Int] = moduloEq (5)

Kodning yuqorisida quyidagi shaklda biroz siqish mumkin.

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

Kutib turing, qanday qilib Eq [Int] turiga murojaat qilish uchun (Int, Int) => Boolean funktsiyasini tayinlashingiz mumkin ?! Bu interfeysning yagona mavhum usul turi deb nomlangan Java 8 xususiyati tufayli mumkin. Agar bizda faqat bitta mavhum usul mavjud bo'lsa, biz buni qila olamiz.

Class piksellar sonini kiriting

Ushbu paragrafda men sizga sinf sinflarining namunalarini qanday ishlatish kerakligini va kerak bo'lganda A turidagi mos keladigan ob'ekt bilan Eq [A] sinfini qanday qilib sehrli ravishda bog'lash kerakligini ko'rsataman.

Bu erda biz ikkita Int qiymatlarini taqqoslash funktsional imkoniyatlarini ularning modulo bo'linish qiymatlari tengligini tekshirish orqali amalga oshirdik. Bajarilgan barcha ishlarimiz bilan biz Type Class-dan ba'zi bir biznes mantiqlarini bajarish uchun foydalana olamiz, masalan. modulo teng bo'lgan ikkita qiymatni ulashni xohlaymiz.

def pairEquals [A] (a: A, b: A) (noaniq eq: Eq [A]): ​​Variant [(A, A)] = {
 agar (misol uchun (a, b)) Ba'zi ((a, b)) hech biri yo'q
}

Biz har qanday turdagi Eq [A] sinfining misolini taqdim etadigan har qanday tur bilan ishlash uchun parametr parametrlarini juftlashtirdik.

Agar kompilyator yuqoridagi deklaratsiyaga mos keladigan biron bir misolni topmasa, u taqdim etilgan aniq doirada tegishli nusxaning yo'qligi to'g'risida kompilyatsiya xatosi haqida ogohlantirish bilan yakunlanadi.
  1. Tuzuvchi bizning funktsiyamizga argumentlarni qo'llash orqali taqdim etilgan parametrlarning turini keltiradi va uni A taxallusiga tayinlaydi.
  2. Oldingi eq argumenti: Eq [A] kalit so'z bilan to'ldirilgan holda, Eq [A] tipidagi ob'ektni yashirin doirada qidirish taklifi paydo bo'ladi.

Implitlar va kiritilgan parametrlar tufayli kompilyator o'z sinfining tegishli namunasi bilan klassni bog'lash imkoniyatiga ega.

Barcha holatlar va funktsiyalar aniqlandi, keling, bizning kodimiz to'g'ri natijalarni beradimi, tekshirib ko'raylik

juftliklar (2,7)
res0: Variant [(Int, Int)] = Ba'zilar ((2,7))
juftliklar (2,3)
res0: Variant [(Int, Int)] = Yo'q

Ko'rayotganingizdek, biz kutgan natijalarni oldik, shuning uchun bizning sinfimiz yaxshi o'ynayapti. Ammo bu juda oz miqdordagi qozonga ega, biroz chalkash ko'rinadi. Scala sintaksisining sehrlari tufayli biz juda ko'p qozonni tarqatib yuborishga qodirmiz.

Kontekst chegaralari

Bizning kodni takomillashtirishni istagan birinchi narsa - bu ikkinchi argumentlar ro'yxatidan (bu so'zsiz yashirin kalit so'zlar bilan) xalos bo'lish. Vazifani ishga tushirganda biz uni to'g'ridan-to'g'ri o'tmayapmiz, shuning uchun yana yashirin bo'lishi kerak. Scala turida aniq parametrlarga ega bo'lgan aniq bo'lmagan argumentlarni Context Bound deb nomlangan til tuzilishi bilan almashtirish mumkin.

Kontekst chegarasi - bu parametr parametrlari ro'yxatidagi deklaratsiya, A: Eq sintaksisi juftEquals funktsiyasining argumenti sifatida ishlatiladigan har bir turdagi yashirin doirada Eq [A] turiga xil bo'lmagan qiymatga ega bo'lishi kerakligini aytadi.

def pairEquals [A: Eq] (a: A, b: A): Variant [(A, A)] = {
 agar (aniq bo'lsa [Eq [A]] .Equval (a, b)) Boshqa ((a, b)) boshqa hech biri
}

Siz bilganingizdek, biz yashirin qiymatga ishora qilmagan holda yakunladik. Ushbu muammoni echish uchun biz aniqlangan [F [_]] funktsiyasidan foydalanmoqdamiz va qaysi turga murojaat qilganimizni aniqlab, aniqlanmagan qiymatni tortib olamiz.

Skala tili bizga buni yanada aniqroq qilishni taklif qiladi. Hali ham bu menga yaxshi ko'rinmaydi. Kontekst chegarasi bu juda ajoyib sintaktik shakar, ammo bu bizning kodimizni mutlaqo ifloslantiradi. Men bu muammoni qanday engib o'tishni va amalga oshirishimiz hajmini kamaytirishni qanday qilib yaxshi ko'rsataman.

Biz qila oladigan narsa, bizning tip sinfimizning sheriklik ob'ekti ichida parametrlashtirilgan amaliy funktsiyani berishdir.

obyekt Eq {
 def apply [A] (noaniq eq: Eq [A]): ​​Eq [A] = eq
}

Bu haqiqatan ham oddiy narsa, bizdan mutlaqo xalos bo'lishga va domen mantig'ida qozonxonasiz ishlatishga imkon beradigan narsadan tortib olishga imkon beradi.

def pairEquals [A: Eq] (a: A, b: A): Variant [(A, A)] = {
 agar (Eq [A] .areEquals (a, b)) Boshqalar ((a, b)) Yo'q
}

Aniq konversiyalar - aka. Sintaksis moduli

Mening ish stolimga kirishni xohlaganim bu - Eq [A] .areEquals (a, b). Ushbu sintaksis juda verbal ko'rinadi, chunki biz aniq sinf namunalariga murojaat qilamiz, to'g'rimi? Ikkinchidan, bu erda bizning sinf namunamiz A-ning haqiqiy kengaytmasi o'rniga xizmat (DDD ma'nosida) kabi ishlaydi. Yaxshiyamki, boshqasini yashirin kalit so'zidan foydalansa, uni ham tuzatish mumkin.

Bu erda nima qilishimiz kerakligi noma'lum konversiya yordamida sintaksis yoki (masalan, ba'zi FP kutubxonalaridagi) modulni taqdim etish bo'lib, bu bizga ba'zi sinflarning API-larini dastlabki kodini o'zgartirmasdan kengaytirish imkonini beradi.

yashirin sinf EqSyntax [A: Eq] (a: A) {
 def === (b: A): Boolean = Eq [A] .areEquals (a, b)
}

Ushbu kod kompilyatorga A sinfini Eq [A] toifadagi namunani EqSyntax = bitta funktsiyaga ega bo'lgan sinfga aylantirishni aytadi. Bularning barchasi, biz kodni o'zgartirmasdan, A sinfiga === funktsiyasini qo'shganimiz kabi taassurot qoldiradi.

Biz nafaqat sinf namunalari uchun yashirin ma'lumotlarga egamiz, balki A sinfida amalga oshirilayotgan taassurotga ega bo'lgan ko'proq sinf sintaksisini ham taqdim etamiz, hatto biz bu sinf haqida hech narsa bilmaymiz. Ikki qush bitta tosh bilan o'ldirilgan.

Endi bizda EqSyntax sinfi mavjud bo'lgan joyda A = = usulini qo'llashimiz mumkin. Endi bizning juftliklar musobaqasini amalga oshirish biroz o'zgaradi va quyidagicha bo'ladi.

def pairEquals [A: Eq] (a: A, b: A): Variant [(A, A)] = {
 if (a === b) Boshqalar ((a, b)) Yo'q
}

Va'da qilganimdek, biz amalga oshirishni yakunladik, bu erda OOP amalga oshirish bilan taqqoslanadigan yagona farq - bu A parametr parametridan keyin Kontekst chegarasi izohi. Tip sinfining barcha texnik jihatlari bizning domenimiz mantig'idan ajratilgan. Bu sizning kodingizga zarar bermasdan (yanada yaqinda alohida nashr etiladigan maqolada aytib o'taman) yanada ajoyib narsalarga erishishingiz mumkin degan ma'noni anglatadi.

Ko'zga ko'rinmas qamrov

Ko'rinib turibdiki, Scalada sinflar aniq yashirin funktsiyadan foydalanishga bog'liq, shuning uchun yashirin doirada qanday ishlashni tushunish kerak.

Aniq bo'lmagan miqyos - bu kompilyator aniq bo'lmagan misollarni qidiradigan sohadir. Ko'p tanlovlar mavjud, shuning uchun instantsiyalar ko'rib chiqiladigan tartibni aniqlash kerak edi. Buyurtma quyidagicha:

1. Mahalliy va meros qilib olingan holatlar
2. Import qilingan misollar
3. Turdosh sinf yoki parametrlarning sherik ob'ektidan olingan ta'riflar

Bu juda muhim, chunki kompilyator bir nechta yoki umuman bo'lmagan hollarni topganda, xato yuzaga keladi. Men uchun tipli sinflar misollarini olishning eng qulay usuli bu ularni o'zlari uchun tipdagi sinfning sherik ob'ektiga joylashtirishdir. Shu sababli biz joylashuvni aniqlashni unutishga imkon beradigan misollarni import qilish yoki amalga oshirish bilan bezovtalanishning hojati yo'q. Hammasi sehrgar tomonidan kompilyator tomonidan ta'minlangan.

Shunday qilib, 3-bandni muhokama qilish imkonini beradi, bu Scala standart kutubxonasidagi taniqli funktsiya misolidan foydalanib, qaysi funktsionallik taqdim etilgan taqqoslovchilarga asoslangan.

saralangan [B>: A] (yashirin buyruq: math.Ordering [B]): Ro'yxat [A]

Sinf sinfining namunasi quyidagicha qidiriladi:
 * Hamroh ob'ektiga buyurtma berish
 * Hamroh ob'ekti ro'yxati
 * B hamroh ob'ekti (pastki chegaralar aniqligi mavjudligi sababli hamrohi ob'ekti bo'lishi mumkin)

Simulakrum

Klass klaviaturasini ishlatishda bularning barchasi ko'p narsaga yordam beradi, ammo har bir loyihada bajarilishi kerak bo'lgan takrorlanadigan ish. Ushbu maslahatlar jarayonni kutubxonaga olib o'tishning aniq belgisidir. Simulacrum deb nomlangan juda yaxshi makroga asoslangan kutubxona mavjud bo'lib, u sintaksis modulini (Simulakrumdagi oplar) va hokazolarni yaratish uchun zarur bo'lgan barcha narsalarni qo'l bilan ishlaydi.

Biz kiritadigan yagona o'zgarish bu sintaksis modulimizni kengaytirish uchun makroslar uchun belgisi bo'lgan @typeclass izohidir.

simulacrum._ ni import qilish
@typeclass traektoriyasi Eq [A] {
 @op ("===") defektiv shartlar (a: A, b: A): Boolean
}

Amalga oshirishning boshqa qismlari hech qanday o'zgarishlarni talab qilmaydi. Hammasi shu. Endi siz o'zingizning qo'lingiz bilan Scala-dagi sinflar jadvalini qanday amalga oshirishni bilasiz va umid qilamanki, siz Simulacrum kutubxonalari qanday ishlashi to'g'risida xabardor bo'lasiz.

O'qiganingiz uchun rahmat, har qanday fikr-mulohazangiz uchun juda minnatdorman va kelgusida yana bir nashr etilgan maqolada siz bilan uchrashishni kutyapman.