Цей документ призначений для початківців розробників Scala3, які вже знайомі з прозою Scala, але спантеличені всіма `implicits
` і параметризовані характеристики в коді.
Цей документ пояснює, чому, як, де і коли Класи типів (TC).
Прочитавши цей документ, розробник-початківець Scala3 отримає міцні знання для використання та зануриться у вихідний код багато бібліотек Scala та почніть писати ідіоматичний код Scala.
Почнемо з того, чому…
Проблема виразу
У 1998, – заявив Філіп Уодлер що «проблема виразу — це нова назва для старої проблеми». Це проблема розширюваності програмного забезпечення. Згідно з писанням містера Вадлера, розв'язок задачі виразу повинен відповідати наступним правилам:
- Правило 1: дозволити реалізацію існуюча поведінка (подумайте про ознаку Scala), до якої потрібно застосувати нові уявлення (подумайте про клас case )
- Правило 2: Дозволити впровадження нова поведінка бути застосованим до існуючі представництва
- Правило 3: Це не повинно загрожувати безпеки типу
- Правило 4: не потрібно перекомпілювати існуючий код
Вирішення цієї проблеми буде срібною ниткою цієї статті.
Правило 1: реалізація існуючої поведінки на новому представленні
Будь-яка об’єктно-орієнтована мова має вбудоване рішення для правила 1 підтиповий поліморфізм. Ви можете сміливо реалізувати будь-який `trait
` визначений у залежності від `class
` у вашому власному коді, без перекомпіляції залежності. Давайте подивимося на це в дії:
def todo = 42
type Height = Int
type Block = Int
object Lib1:
trait Blockchain:
def getBlock(height: Height): Block
case class Ethereum() extends Blockchain:
override def getBlock(height: Height) = todo
case class Bitcoin() extends Blockchain:
override def getBlock(height: Height) = todo
object Lib2:
import Lib1.*
case class Polkadot() extends Blockchain:
override def getBlock(height: Height): Block = todo
val eth = Lib1.Ethereum()
val btc = Lib1.Bitcoin()
val dot = Lib2.Polkadot()
У цьому фіктивному прикладі бібліотека `Lib1
` (рядок 5) визначає рису `Blockchain
` (рядок 6) із двома його реалізаціями (рядки 2 і 9). `Lib1
` залишиться незмінним у ВСІМ цьому документі (застосування правила 4).
`Lib2
` (рядок 15) реалізує існуючу поведінку `Blockchain
`на новому класі`Polkadot
` (правило 1) безпечним (правило 3) способом, без перекомпіляції `Lib1
` (правило 4).
Правило 2: реалізація нової поведінки, яка буде застосована до існуючих представлень
Давайте уявимо в `Lib2
«ми хочемо нової поведінки».lastBlock
` буде реалізовано спеціально для кожного `Blockchain
`.
Перше, що спадає на думку, це створити великий перемикач на основі типу параметра.
def todo = 42
type Height = Int
type Block = Int
object Lib1:
trait Blockchain:
def getBlock(height: Height): Block
case class Ethereum() extends Blockchain:
override def getBlock(height: Height) = todo
case class Bitcoin() extends Blockchain:
override def getBlock(height: Height) = todo
object Lib2:
import Lib1.*
case class Polkadot() extends Blockchain:
override def getBlock(height: Height): Block = todo
def lastBlock(blockchain: Blockchain): Block = blockchain match
case _:Ethereum => todo
case _:Bitcoin => todo
case _:Polkadot => todo
object Lib3:
import Lib1.*
case class Polygon() extends Blockchain:
override def getBlock(height: Height): Block = todo
import Lib1.*, Lib2.*, Lib3.*
println(lastBlock(Bitcoin()))
println(lastBlock(Ethereum()))
println(lastBlock(Polkadot()))
println(lastBlock(Polygon()))
Це рішення є слабкою повторною реалізацією поліморфізму на основі типів, який уже вбудований у мову!
`Lib1
` залишається недоторканим (пам’ятайте, правило 4 застосоване у всьому цьому документі).
Рішення, реалізоване в `Lib2
` є гаразд поки ще один блокчейн не буде представлено в `Lib3
`. Це порушує правило безпеки типу (правило 3), оскільки цей код не працює під час виконання в рядку 37. І зміна `Lib2
` порушує правило 4.
Іншим рішенням є використання `extension
`.
def todo = 42
type Height = Int
type Block = Int
object Lib1:
trait Blockchain:
def getBlock(height: Height): Block
case class Ethereum() extends Blockchain:
override def getBlock(height: Height) = todo
case class Bitcoin() extends Blockchain:
override def getBlock(height: Height) = todo
object Lib2:
import Lib1.*
case class Polkadot() extends Blockchain:
override def getBlock(height: Height): Block = todo
def lastBlock(): Block = todo
extension (eth: Ethereum) def lastBlock(): Block = todo
extension (btc: Bitcoin) def lastBlock(): Block = todo
import Lib1.*, Lib2.*
println(Bitcoin().lastBlock())
println(Ethereum().lastBlock())
println(Polkadot().lastBlock())
def polymorphic(blockchain: Blockchain) =
// blockchain.lastBlock()
???
`Lib1
` залишається без змін (застосування правила 4 у всьому документі).
`Lib2
` визначає поведінку для свого типу (рядок 21) і `розширення` для існуючих типів (рядки 23 і 25).
Рядки 28-30, нову поведінку можна використовувати в кожному класі.
Але неможливо назвати цю нову поведінку поліморфною (рядок 32). Будь-яка спроба зробити це призводить до помилок компіляції (рядок 33) або до перемикачів на основі типу.
Це Правило № 2 є складним. Ми спробували реалізувати це за допомогою нашого власного визначення поліморфізму та трюку «розширення». І це було дивно.
Відсутня частина називається спеціальний поліморфізм: здатність безпечно відправляти реалізацію поведінки відповідно до типу, де б не було визначено поведінку та тип. Введіть Тип Клас рисунок.
Шаблон класу типу
Рецепт шаблону типу класу (скорочено TC) складається з 3 кроків.
- Визначте нову поведінку
- Реалізуйте поведінку
- Використовуйте поведінку
У наступному розділі я реалізую шаблон TC найпростішим способом. Це багатослівно, незграбно і непрактично. Але зачекайте, ці застереження будуть виправлені крок за кроком далі в документі.
1. Визначте нову поведінку
object Lib2:
import Lib1.*
trait LastBlock[A]:
def lastBlock(instance: A): Block
`Lib1
` знову залишається недоторканим.
Нова поведінка is ТК матеріалізується ознакою. Функції, визначені в ознакі, є способом застосування деяких аспектів цієї поведінки.
Параметр `A
` представляє тип, до якого ми хочемо застосувати поведінку, які є підтипами `Blockchain
` в нашому випадку.
Деякі зауваження:
- Якщо потрібно, параметризований тип `
A
` може додатково обмежуватися системою типу Scala. Наприклад, ми могли б застосувати `A
` бути `Blockchain
`. - Крім того, у ТК може бути задекларовано набагато більше функцій.
- Нарешті, кожна функція може мати набагато більше довільних параметрів.
Але давайте зробимо все просто для зручності читання.
2. Реалізація поведінки
object Lib2:
import Lib1.*
trait LastBlock[A]:
def lastBlock(instance: A): Block
val ethereumLastBlock = new LastBlock[Ethereum]:
def lastBlock(eth: Ethereum) = eth.lastBlock
val bitcoinLastBlock = new LastBlock[Bitcoin]:
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
Для кожного типу новий `LastBlock
` поведінка очікувана, є конкретний випадок такої поведінки.
Файл `Ethereum
` рядок реалізації 22 обчислюється з `eth
` екземпляр, переданий як параметр.
Реалізація `LastBlock
` для `Bitcoin
` рядок 25 реалізовано з некерованим IO і не використовує його параметр.
Отже, `Lib2
` реалізує нову поведінку `LastBlock
` для `Lib1
` класи.
3. Використовуйте поведінку
object Lib2:
import Lib1.*
trait LastBlock[A]:
def lastBlock(instance: A): Block
val ethereumLastBlock = new LastBlock[Ethereum]:
def lastBlock(eth: Ethereum) = eth.lastBlock
val bitcoinLastBlock = new LastBlock[Bitcoin]:
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
import Lib1.*, Lib2.*
def useLastBlock[A](instance: A, behavior: LastBlock[A]) =
behavior.lastBlock(instance)
println(useLastBlock(Ethereum(lastBlock = 2), ethereumLastBlock))
println(useLastBlock(Bitcoin(), bitcoinLastBlock))
Рядок 30`useLastBlock
` використовує екземпляр `A
` і `LastBlock
` поведінка, визначена для цього екземпляра.
Рядок 33`useLastBlock
` викликається з екземпляром `Ethereum
` і реалізація `LastBlock
` визначено в `Lib2
`. Зверніть увагу, що можна передати будь-яку альтернативну реалізацію `LastBlock[A]
` (подумай ін'єкційна залежність).
`useLastBlock
` є сполучною ланкою між представленням (фактичним A) і його поведінкою. Дані та поведінка розділені, за що виступає функціональне програмування.
Обговорення
Давайте повторимо правила задачі виразу:
- Правило 1: дозволити реалізацію існуюча поведінка бути застосованим до нові класи
- Правило 2: Дозволити впровадження нова поведінка бути застосованим до існуючі класи
- Правило 3: Це не повинно загрожувати безпеки типу
- Правило 4: не потрібно перекомпілювати існуючий код
Правило 1 може бути вирішено з коробки за допомогою поліморфізму підтипу.
Щойно представлений шаблон TC (див. попередній знімок екрана) вирішує правило 2. Він безпечний для типу (правило 3), і ми ніколи не торкалися `Lib1
` (правило 4).
Однак використовувати його недоцільно з кількох причин:
- У рядках 33-34 ми повинні явно передати поведінку в його екземпляр. Це додаткові накладні витрати. Ми повинні просто написати `
useLastBlock(Bitcoin())
`. - Рядок 31 синтаксис незвичайний. Ми б віддали перевагу написати стислий і більш об’єктно-орієнтований `
instance.lastBlock()
` заява.
Давайте виділимо деякі функції Scala для практичного використання TC.
Покращений досвід розробника
Scala має унікальний набір функцій і синтаксичних цукрів, які роблять TC справді приємним досвідом для розробників.
Імпліцитні
Неявна область — це спеціальна область, яка вирішується під час компіляції, де може існувати лише один екземпляр даного типу.
Програма поміщає екземпляр у неявну область за допомогою `given
` ключове слово. Крім того, програма може отримати екземпляр із неявної області за допомогою ключового слова `using
`.
Неявна область вирішується під час компіляції, існує спосіб динамічно змінювати її під час виконання. Якщо програма компілюється, неявна область вирішується. Під час виконання неможливо мати відсутні неявні екземпляри, де вони використовуються. Єдина можлива плутанина може виникнути через використання неправильного неявного екземпляра, але це питання залишається для істоти між стільцем і клавіатурою.
Він відрізняється від глобального масштабу, оскільки:
- Це вирішується контекстно. Два розташування програми можуть використовувати екземпляр того самого заданого типу в неявній області видимості, але ці два екземпляри можуть бути різними.
- За сценою код передає функцію неявних аргументів для функціонування, доки не буде досягнуто неявне використання. Він не використовує глобальний простір пам’яті.
Повертаючись до класу типу! Візьмемо точно такий же приклад.
def todo = 42
type Height = Int
type Block = Int
def http(uri: String): Block = todo
object Lib1:
trait Blockchain:
def getBlock(height: Height): Block
case class Ethereum() extends Blockchain:
override def getBlock(height: Height) = todo
case class Bitcoin() extends Blockchain:
override def getBlock(height: Height) = todo
`Lib1
` це той самий немодифікований код, який ми визначили раніше.
object Lib2:
import Lib1.*
trait LastBlock[A]:
def lastBlock(instance: A): Block
given ethereumLastBlock:LastBlock[Ethereum] = new LastBlock[Ethereum]:
def lastBlock(eth: Ethereum) = eth.lastBlock
given bitcoinLastBlock:LastBlock[Bitcoin] = new LastBlock[Bitcoin]:
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
import Lib1.*, Lib2.*
def useLastBlock[A](instance: A)(using behavior: LastBlock[A]) =
behavior.lastBlock(instance)
println(useLastBlock(Ethereum(lastBlock = 2)))
println(useLastBlock(Bitcoin()))
Рядок 19 нова поведінка `LastBlock
` визначається так само, як ми це робили раніше.
Рядок 22 і рядок 25, `val
` замінюється на `given
`. Обидві реалізації `LastBlock
` поміщаються в неявну область видимості.
Рядок 31`useLastBlock
` декларує поведінку `LastBlock
` як неявний параметр. Компілятор розпізнає відповідний екземпляр `LastBlock
` з неявної області, контекстуалізованої з місць виклику (рядки 33 і 34). Рядок 28 імпортує все з `Lib2
`, включаючи неявну область. Отже, компілятор передає екземпляри, визначені рядки 22 і 25, як останній параметр `useLastBlock
`.
Як користувач бібліотеки, використовувати клас типу легше, ніж раніше. Рядки 34 і 35 розробник має лише переконатися, що екземпляр поведінки введено в неявну область (і це може бути просто `import
`). Якщо неявний не є `given
` де знаходиться код `using
` це, каже йому компілятор.
Неявне використання Scala полегшує завдання передачі екземплярів класів разом із екземплярами їхньої поведінки.
Неявні цукру
Рядки 22 і 25 попереднього коду можна ще покращити! Давайте повторимо реалізацію TC.
given LastBlock[Ethereum] = new LastBlock[Ethereum]:
def lastBlock(eth: Ethereum) = eth.lastBlock
given LastBlock[Bitcoin] = new LastBlock[Bitcoin]:
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
Рядки 22 і 25, якщо ім’я екземпляра не використовується, його можна опустити.
given LastBlock[Ethereum] with
def lastBlock(eth: Ethereum) = eth.lastBlock
given LastBlock[Bitcoin] with
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
У рядках 22 і 25 повторення типу можна замінити на `with
` ключове слово.
given LastBlock[Ethereum] = _.lastBlock
given LastBlock[Bitcoin] = _ => http("http://bitcoin/last")
Оскільки ми використовуємо вироджену ознаку з однією функцією, IDE може запропонувати спростити код за допомогою виразу SAM. Хоча це правильно, я не думаю, що це належне використання SAM, якщо ви випадково не кодуєте гольф.
Scala пропонує синтаксичні цукри для оптимізації синтаксису, видалення непотрібних імен, декларацій і надмірності типів.
Розширення
Розумно використане `extension
` механізм може спростити синтаксис для використання класу типу.
object Lib2:
import Lib1.*
trait LastBlock[A]:
def lastBlock(instance: A): Block
given LastBlock[Ethereum] with
def lastBlock(eth: Ethereum) = eth.lastBlock
given LastBlock[Bitcoin] with
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
extension[A](instance: A)
def lastBlock(using tc: LastBlock[A]) = tc.lastBlock(instance)
import Lib1.*, Lib2.*
println(Ethereum(lastBlock = 2).lastBlock)
println(Bitcoin().lastBlock)
Рядки 28-29 загальний метод розширення `lastBlock
` визначається для будь-якого `A
` з `LastBlock
` Параметр TC у неявній області.
У рядках 33-34 розширення використовує об’єктно-орієнтований синтаксис для використання TC.
object Lib2:
import Lib1.*
trait LastBlock[A]:
def lastBlock(instance: A): Block
given LastBlock[Ethereum] with
def lastBlock(eth: Ethereum) = eth.lastBlock
given LastBlock[Bitcoin] with
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
extension[A](instance: A)(using tc: LastBlock[A])
def lastBlock = tc.lastBlock(instance)
def penultimateBlock = tc.lastBlock(instance) - 1
import Lib1.*, Lib2.*
val eth = Ethereum(lastBlock = 2)
println(eth.lastBlock)
println(eth.penultimateBlock)
val btc = Bitcoin()
println(btc.lastBlock)
println(btc.penultimateBlock)
Рядок 28, параметр TC можна також визначити для всього розширення, щоб уникнути повторення. У рядку 30 ми повторно використовуємо TC у розширенні для визначення `penultimateBlock
` (хоча це може бути реалізовано на `LastBlock
` ознака безпосередньо)
Магія відбувається, коли використовується TC. Вираз виглядає набагато природнішим, створюючи ілюзію поведінки `lastBlock
` поєднується з примірником.
Універсальний тип з TC
import Lib1.*, Lib2.*
def useLastBlock1[A](instance: A)(using LastBlock[A]) = instance.lastBlock
def useLastBlock2[A: LastBlock](instance: A) = instance.lastBlock
val eth = Ethereum(lastBlock = 2)
assert(useLastBlock1(eth) == useLastBlock2(eth))
У рядку 34 функція використовує неявний TC. Зауважте, що TC не потрібно називати, якщо це ім’я непотрібне.
Шаблон TC настільки широко використовується, що існує загальний синтаксис типу для вираження «типу з неявною поведінкою». Синтаксис рядка 36 є більш короткою альтернативою попередньому (рядок 34). Він уникає оголошення конкретного безіменного неявного параметра TC.
На цьому розділ досвіду розробника завершено. Ми бачили, як розширення, неявні та деякі синтаксичні цукри можуть забезпечити менш захаращений синтаксис, коли TC використовується та визначається.
Автоматичне виведення
Багато бібліотек Scala використовують TC, залишаючи програмістам реалізувати їх у своїй базі коду.
Наприклад, Circe (бібліотека десеріалізації json) використовує TC `Encoder[T]
` і `Decoder[T]
` для впровадження програмістами у свою кодову базу. Після впровадження можна використовувати весь обсяг бібліотеки.
Такі реалізації TC більш ніж часті картографи, орієнтовані на дані. Їм не потрібна жодна бізнес-логіка, їх нудно писати, і їх важко підтримувати в синхронізації з класами case.
У такій ситуації ті бібліотеки пропонують, що називається автоматичний виведення або напівавтоматичний виведення. Дивіться, наприклад, Цирцея автоматичний та напівавтоматичний виведення. За допомогою напівавтоматичного виведення програміст може оголосити екземпляр класу типу з деяким другорядним синтаксисом, тоді як автоматичне похідне не потребує будь-яких модифікацій коду, окрім імпорту.
Під капотом, під час компіляції, аналіз загальних макросів Типи як чисту структуру даних і генерувати TC[T] для користувачів бібліотеки.
Виведення загального TC є дуже поширеним явищем, тому Scala представила повний інструментарій для цієї мети. Цей метод не завжди рекламується в бібліотечній документації, хоча це спосіб використання деривації у Scala 3.
object GenericLib:
trait Named[A]:
def blockchainName(instance: A): String
object Named:
import scala.deriving.*
inline final def derived[A](using inline m: Mirror.Of[A]): Named[A] =
val nameOfType: String = inline m match
case p: Mirror.ProductOf[A] => compiletime.constValue[p.MirroredLabel]
case _ => compiletime.error("Not a product")
new Named[A]:
override def blockchainName(instance: A):String = nameOfType.toLowerCase
extension[A] (instance: A)(using tc: Named[A])
def blockchainName = tc.blockchainName(instance)
import Lib1.*, GenericLib.*
case class Polkadot() derives Named
given Named[Bitcoin] = Named.derived
given Named[Ethereum] = Named.derived
println(Ethereum(lastBlock = 2).blockchainName)
println(Bitcoin().blockchainName)
println(Polkadot().blockchainName)
Рядок 18 новий ТК `Named
` вводиться. Цей TC не має відношення до блокчейн-бізнесу, строго кажучи. Його мета — назвати блокчейн на основі назви класу case.
Спочатку зосередьтеся на визначеннях рядків 36-38. Є 2 синтаксиси для отримання TC:
- У рядку 36 примірник TC можна визначити безпосередньо в класі case за допомогою `
derives
` ключове слово. Під капотом компілятор генерує заданий `Named
` екземпляр у `Polkadot
` супутній об'єкт. - Рядки 37 і 38, екземпляри класів типів наведено на вже існуючих класах за допомогою `
TC.derived
`
Рядок 31 визначає загальне розширення (див. попередні розділи) і `blockchainName
` використовується природно.
Файл `derives
` ключове слово очікує метод із формою `inline def derived[T](using Mirror.Of[T]): TC[T] = ???
`, яка визначена в рядку 24. Я не буду детально пояснювати, що робить код. У загальних рисах:
- `
inline def
` визначає макрос - `
Mirror
` є частиною інструментарію для самоаналізу типів. Існують різні види дзеркал, і рядок 26 коду фокусується на `Product
` дзеркала (клас case є продуктом). Рядок 27, якщо програмісти намагаються вивести щось, що не є `Product
`, код не компілюється. - `
Mirror
` містить інші типи. Один із них, `MirrorLabel
`, це рядок, який містить назву типу. Це значення використовується в реалізації, рядок 29, `Named
` TC.
Автори TC можуть використовувати метапрограмування для надання функцій, які генерують екземпляри TC певного типу. Програмісти можуть використовувати API спеціальної бібліотеки або інструменти отримання Scala для створення екземплярів свого коду.
Незалежно від того, чи потрібен вам загальний чи спеціальний код для реалізації TC, для кожної ситуації є рішення.
Короткий перелік усіх переваг
- Це вирішує задачу виразу
- Нові типи можуть реалізувати існуючу поведінку через успадкування традиційних ознак
- Нова поведінка може бути реалізована на існуючих типах
- Поділ занепокоєння
- Код не зіпсований і його легко видалити. TC розділяє дані та поведінку, що є девізом функціонального програмування.
- Це безпечно
- Це безпечно для типу, оскільки не покладається на самоаналіз. Це дозволяє уникнути зіставлення великих шаблонів із залученням типів. якщо ви зіткнетеся з написанням такого коду, ви можете виявити випадок, коли шаблон TC підійде ідеально.
- Неявний механізм є безпечним для компіляції! Якщо екземпляр відсутній під час компіляції, код не компілюється. Нічого несподіваного під час виконання.
- Це приносить спеціальний поліморфізм
- Спеціальний поліморфізм зазвичай відсутній у традиційному об'єктно-орієнтованому програмуванні.
- За допомогою спеціального поліморфізму розробники можуть реалізувати однакову поведінку для різних непов’язаних типів без використання традиційного підтипу (який об’єднує код)
- Ін’єкція залежностей стала легкою
- Екземпляр ТК можна змінити відповідно до принципу підстановки Ліскова.
- Якщо компонент залежить від TC, імітований TC можна легко впровадити для цілей тестування.
Протипоказання
Кожен молоток призначений для цілого ряду завдань.
Класи типу призначені для поведінкових проблем і не повинні використовуватися для успадкування даних. Для цього використовуйте склад.
Звичайний підтип більш простий. Якщо ви володієте базою коду і не прагнете розширюваності, класи типів можуть бути надмірними.
Наприклад, у ядрі Scala є `Numeric
` тип класу:
trait Numeric[T] extends Ordering[T] {
def plus(x: T, y: T): T
def minus(x: T, y: T): T
def times(x: T, y: T): T
Використовувати такий клас типів справді має сенс, оскільки він дозволяє не лише повторно використовувати алгебраїчні алгоритми для типів, вбудованих у Scala (Int, BigInt, …), але й для типів, визначених користувачем (a `ComplexNumber
` наприклад).
З іншого боку, реалізація колекцій Scala здебільшого використовує підтипування замість класу типу. Цей дизайн має сенс з кількох причин:
- Передбачається, що API колекції буде повним і стабільним. Він розкриває загальну поведінку через ознаки, успадковані реалізаціями. Бути високорозширюваним тут не є особливою метою.
- Він повинен бути простим у використанні. TC додає накладні витрати на програміста кінцевого користувача.
- TC також може спричинити невеликі накладні витрати на продуктивність. Це може бути критично для API колекції.
- Однак API колекції все ще можна розширити за допомогою нового TC, визначеного сторонніми бібліотеками.
Висновок
Ми побачили, що TC — це простий шаблон, який вирішує велику проблему. Завдяки розширеному синтаксису Scala шаблон TC можна реалізувати та використовувати різними способами. Шаблон TC відповідає парадигмі функціонального програмування та є чудовим інструментом для чистої архітектури. Немає срібної кулі, і шаблон TC потрібно застосовувати, коли він підходить.
Сподіваюся, ви отримали знання, прочитавши цей документ.
Код доступний за адресою https://github.com/jprudent/type-class-article. Будь ласка, зв’яжіться зі мною, якщо у вас виникнуть запитання чи зауваження. Ви можете використовувати проблеми або коментарі до коду в репозиторії, якщо хочете.
Інженер-програміст
- Розповсюдження контенту та PR на основі SEO. Отримайте посилення сьогодні.
- PlatoData.Network Vertical Generative Ai. Додайте собі сили. Доступ тут.
- PlatoAiStream. Web3 Intelligence. Розширення знань. Доступ тут.
- ПлатонЕСГ. вуглець, CleanTech, Енергія, Навколишнє середовище, Сонячна, Поводження з відходами. Доступ тут.
- PlatoHealth. Розвідка про біотехнології та клінічні випробування. Доступ тут.
- джерело: https://www.ledger.com/blog/type-classes-in-scala3-a-beginners-guide
- : має
- :є
- : ні
- :де
- ][стор
- 1
- 10
- 11
- 12
- 14
- 15%
- 19
- 1998
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 35%
- 36
- 7
- 8
- 9
- a
- здатність
- МЕНЮ
- AC
- За
- дію
- фактичний
- Додає
- прихильники
- знову
- мета
- алгоритми
- ВСІ
- дозволяти
- дозволяє
- по
- вже
- Також
- альтернатива
- хоча
- завжди
- an
- та
- Інший
- будь-який
- API
- прикладної
- Застосовувати
- відповідний
- архітектура
- архів
- ЕСТЬ
- аргументація
- стаття
- AS
- аспекти
- At
- спроба
- authors
- автоматичний
- доступний
- уникнути
- назад
- база
- заснований
- BE
- оскільки
- перед тим
- Початківець
- поведінка
- буття
- між
- Великий
- blockchain
- Нудно
- обидва
- Box
- Приносить
- широкий
- БТД
- тягар
- бізнес
- але
- by
- call
- званий
- гість
- CAN
- випадок
- Крісло
- зміна
- змінилися
- клас
- класів
- очистити
- код
- кодова база
- Кодова база
- збір
- Колекції
- Приходити
- приходить
- коментарі
- загальний
- супутник
- повний
- дотримуватися
- компонент
- склад
- Занепокоєння
- лаконічний
- робить висновок
- змішаний
- замішання
- містить
- Core
- виправити
- може
- створювати
- створення
- тварь
- критичний
- дані
- заявляє
- присвячених
- визначати
- певний
- Визначає
- визначення
- Визначення
- Залежність
- глибина
- дрейф
- дизайн
- призначений
- виявляти
- Розробник
- розробників
- DID
- різний
- безпосередньо
- Відправка
- занурення
- do
- документ
- робить
- Ні
- Не знаю
- динамічно
- кожен
- простота
- легше
- легко
- легко
- ed
- вбудований
- зіткнення
- кінець
- примусове виконання
- примус
- приємний
- Що натомість? Створіть віртуальну версію себе у
- помилки
- ETH
- Ефір (ETH)
- Навіть
- все
- точно
- приклад
- Крім
- існувати
- існуючий
- очікуваний
- чекає
- досвід
- Пояснювати
- Пояснює
- явно
- експрес
- вираз
- розширення
- Розширення
- додатково
- зазнає невдачі
- риси
- почуває
- фіксованою
- Сфокусувати
- фокусується
- після
- для
- форма
- від
- функція
- функціональний
- Функції
- далі
- Отримувати
- отримала
- породжувати
- генерує
- GitHub
- даний
- дає
- Глобальний
- глобальний розмах
- мета
- керівництво
- забивати
- рука
- відбувається
- Мати
- тут
- Виділіть
- дуже
- його
- тримати
- капот
- Як
- HTML
- HTTP
- HTTPS
- i
- if
- Illusion
- картина
- здійснювати
- реалізація
- реалізації
- реалізовані
- implements
- імпорт
- імпорт
- поліпшений
- in
- У тому числі
- спадкування
- екземпляр
- випадки
- замість
- призначених
- в
- введені
- за участю
- питання
- питання
- IT
- ЙОГО
- Піддають небезпеці
- json
- просто
- тримати
- Знати
- знання
- мова
- останній
- Веде за собою
- догляд
- Гросбух
- залишити
- менше
- важелі
- libraries
- бібліотека
- як
- Лінія
- ліній
- місць
- логіка
- серія
- макроси
- made
- Made Easy
- магія
- підтримувати
- зробити
- РОБОТИ
- манера
- багато
- узгодження
- Може..
- me
- механізм
- пам'ять
- психічний
- меров
- Meta
- метод
- може бути
- mind
- незначний
- дзеркало
- відсутній
- більше
- найбільш
- в основному
- Девіз
- повинен
- ім'я
- Названий
- іменування
- Природний
- Необхідність
- необхідний
- ніколи
- Нові
- немає
- увагу
- об'єкт
- of
- пропонувати
- Пропозиції
- часто
- Старий
- on
- один раз
- ONE
- тільки
- or
- Інше
- наші
- з
- контури
- над
- власний
- парадигма
- параметр
- параметри
- частина
- приватність
- партія
- проходити
- Пройшов
- проходить
- Проходження
- Викрійки
- відмінно
- продуктивність
- частина
- plato
- Інформація про дані Платона
- PlatoData
- будь ласка
- це можливо
- Практичний
- надавати перевагу
- представлений
- попередній
- раніше
- принцип
- Проблема
- проблеми
- Product
- програма
- Програміст
- Програмісти
- Програмування
- правильний
- забезпечувати
- мета
- цілей
- put
- Ставить
- питань
- діапазон
- швидше
- досягати
- досяг
- читання
- насправді
- причина
- Короткий огляд
- рецепт
- покладатися
- залишатися
- запам'ятати
- видалення
- замінити
- Сховище
- подання
- представляє
- вирішене
- повага
- знову використовувати
- Багаті
- Правило
- Правила
- s
- сейф
- безпечно
- Безпека
- користь
- Сем
- то ж
- масштаб
- сцена
- сфера
- розділ
- розділам
- побачити
- бачив
- сенс
- комплект
- кілька
- Короткий
- Повинен
- срібло
- простий
- спростити
- спрощення
- один
- ситуація
- невеликий
- So
- Софтвер
- solid
- рішення
- вирішити
- Вирішує
- деякі
- що в сім'ї щось
- Source
- вихідні
- Простір
- розмова
- спеціальний
- конкретний
- конкретно
- стабільний
- старт
- Заява
- Крок
- заходи
- Як і раніше
- просто
- раціоналізувати
- рядок
- структура
- такі
- цукор
- пропонувати
- костюм
- передбачуваний
- Переконайтеся
- сюрприз
- перемикач
- синхронізація.
- синтаксис
- система
- T
- Приймати
- Завдання
- розповідає
- Тестування
- ніж
- Дякую
- Що
- Команда
- Джерело
- їх
- Їх
- Там.
- вони
- річ
- речі
- думати
- третій
- це
- ті
- хоча?
- через
- час
- до
- інструмент
- Інструменти
- інструменти
- торкнувся
- традиційний
- намагався
- по-справжньому
- намагатися
- два
- тип
- Типи
- Uncommon
- при
- створеного
- БЕЗ ІМЕНИ
- до
- незайманий
- невикористаний
- на
- Використання
- використання
- використовуваний
- користувач
- користувачі
- використовує
- використання
- звичайний
- зазвичай
- значення
- різний
- розбирається
- дуже
- хотіти
- було
- шлях..
- способи
- we
- Що
- Що таке
- коли
- в той час як
- який
- ВООЗ
- всі
- чому
- широко
- волі
- мудро
- з
- без
- б
- запис
- лист
- Неправильно
- ще
- ви
- вашу
- себе
- зефірнет