Scala 3: як зміниться синтаксис, система типів і застосування мови

Привіт, я — Руслан Шевченко, підприємець, один із засновників групи користувачів Scala в .UA.

Я починав працювати зі Scala з версії 2.7 понад 10 років тому і з того часу беру участь у житті Scala-спільноти. З одного боку, у спільноті немає дефіциту інформації на цю тему, на ScalaUA майже половина доповідей про Scala 3, а з іншого — ми добре знаємо, що відбувається у нашій «бульбашці», але чи видно це зовні?

Наступний реліз [Dotty] буде відрізнятись від нинішнього релізу Scala, мабуть, більше, ніж нинішній відрізняється від Scala 2.7.

У цій статті спробую розповісти про найближче майбутнє Scala тим, хто не є резидентом «бульбашки».

Чого чекати від Scala

Сьогодні мова Scala найбільш поширена в інфраструктурі проєктів обробки потоків даних. Головні killer applications — Spark, що дає змогу обробляти великі об’єми даних, і Kafka, що організує інфраструктуру брокера обміну повідомлень. До речі, 21 квітня компанія Confluent, що стоїть за Kafka, підняла 250 мільйонів доларів у раунді інвестицій. Також Scala часто використовується для організації софт-реалтайм процеcингу, прикладом є рекламні аукціони або обробка платежів.

Як для мови «загального призначення», у Scala зависокий вхідний бар’єр: якщо треба взяти щось із бази даних і показати на фронтенді, то починати з пошуку вільних Scala-розробників буде не найбільш оптимальним шляхом. Можливо, зусилля EPFL змінять це співвідношення.

Нижче — карта доповіді Мартина Одерського Scala 3 Update з конференції ScalaLove, що відбулась 18 квітня.

Реліз наступної версії Scala заплановано на кінець 2020 року. Вона міститиме багато змін. Розповісти про всі в одній статті складно, тому окреслю лише найважливіші.

Cинтаксис

Почнемо з найпростішої та водночас найбільш обговорюваної частини — змін у синтаксисі. Scala належить до сім’ї так званих C-подібних мов, де конструкції відділяються одна від одної за допомогою фігурних дужок. Наприклад:

trait Handler  {

   def  apply(request:Request): M[Reply] = {
        if (authorized(request)) {
             val context = newContext()
             process(request, context)
        } else {
            process(request, PublicContext)
        }
   } 

}

У Scala 3 можна писати по-старому, а можна довірити розставляти дужки компілятору. Тоді цей код матиме такий вигляд:

trait Handler:

   def  apply(request:Request): M[Reply] = 
        if authorized(request) then
             val context = newContext()
             process(request, context)
        else 
            process(request, PublicContext)

Компілятор сам розставить дужки, якщо побачить набір рядків, вирівняних з однаковим відступом після конструкцій управління або кінцевої двокрапки в рядку. (Деталі дивіться тут).

Я користувався новим синтаксисом і можу підтвердити, що таким чином код справді здається «чистішим». Після тижневого користування пишеш його автоматично з дужками, а потім так само автоматично ці дужки видаляєш.

Система типів

Тепер поговорімо про малопомітну, але складну частину — теоретичні основи. Традиційно в об’єктно-орієнтованих мовах програмування систему типів створювали не на основі формальної теорії, а прагматично, на основі наявних практик програмування. Формалізація йшла як доповнення уже згодом. У результаті в нинішніх об’єктно-орієнтованих мовах є різні властивості, що здаються дивними з математичної точки зору. Наприклад, така програма:

object unsoundMini {
  trait A { type L >: Any}
  def upcast(a: A, x: Any): a.L = x
  val p: A { type L <: Nothing } = null
  def coerce(x: Any): Nothing = upcast(p, x)
  coerce("Uh oh!")
}

В Scala 2 програма компілюється і проходить перевірку типів, але видає ClassCastException при запуску. Тобто система типів не є обґрунтованою: існує можливість побудувати такий тип об’єкта, для якого неможлива реалізація. (Докладніше про необґрунтовані системи типів у Java та Scala можна прочитати тут).

Scala 3 ґрунтується на DOT-численні (Dependend Object Types), для якого доведено властивість обґрунтованості: тобто якщо програма пройшла тайпчекінг, ClassCastException під час запуску не буде. Система типів стала розгалуженішою: з’явилися операції перетину та об’єднання типів; за допомогою типів зіставлення та лямбда-типів вирази над типами можна виконати прямо. Система стала і більш регулярною, оскільки там, де раніше треба було вибудовувати ланцюжки імпліцитів, тепер можна написати типові обчислення на зразок:

type MyCollection[T] = hasOrd[T] match 
                                       case  Nothing => HashMap[T]
                                       case  other => TreeMap[T]

Ще одна важлива зміна — Null перестав бути підтипом будь-якого типу посилання. Тобто якщо в нас є клас Person, ми не можемо використовувати значення Null як його екземпляр. А об’єкти, що приходять з Java, мають тип Person | Null. Це дає змогу статистично гарантувати відсутність Null Pointer Exception.

Ергономіка навчання

Ще одна галузь, яка стала напрямом змін. У EPFL (інституті, де розробляється мова Scala) проаналізували, які труднощі виникають у студентів під час вивчення Scala, і змінили мову так, щоб їх стало менше — ввели всі необхідні конструкції більш зрозуміло.

Зокрема, у Scala 2 одним із фундаментальних механізмів є implicit-значення, яке використовують практично всюди. Наприклад, для передачі та синтезу контексту:

def sort[T](list: List[T])(implicit ord: Ord[T])

implicit object PersonOrd extends Ord[Person] {
     def compare(x:Person, y:Person) = 
        implicitly[Ord[String]].compare(x.lastName, y.lastName)
}

іmplicit def listOrd(v:List[T])(implicit elemOrd:Ord[T]):  Ord[List[T]]

Проте засвоєння концепції універсального неявного значення є досить складним для студентів. У Scala 3 цю концепцію змінили: є given-значення, яке може синтезуватись автоматично та передаватися в using clauses для використання. Основна відмінність від implicit — таку концепцію краще пояснювати:

def sort[T](x:List[T])(using Ord[T])   

given Ord[Person]:
     def compare(x:Person, y:Person) = 
        summon(Ord[String]).compare(x.lastName, y.lastName)

given Ord[List[T]](using Ord[T]):
     def compare(x:List[T],y:List[T]) = ….

Для розширення класів ввели спеціальний синтаксис:

extension on x:T (using ord:Ord[T]):

    def  < (y:T) = ord.compare(x,y) 
   …

Замість:

implicit class OrdOps[T](x: T) {

    def  < (y: T)(implicit ord: Ord[T]) = ord.compare(x,y) < 0
    …

} 

Такі зміни сприяють зменшенню кількості boirterplate-коду.

Також студентам заважає варіативність — коли одну й ту ж річ можна зробити по-різному за відсутності будь-яких правил вибору. Тому в Scala 3 цю варіативність намагаються зменшити й рекомендувати для кожного патерну, де це можливо, варіант за замовчуванням.

Метапрограмування

У Scala 2 макроси фактично давали доступ програмісту до нутрощів компілятора: програмісти отримували ті самі типи внутрішнього представлення дерев, що використовувались у компіляторі. Це означало, що макроси залежали від деталей реалізації компілятора, котрі могли змінюватись від версії до версії. І щоб нормально орієнтуватись в Macro API, потрібно було прочитати частину компілятора.

В Scala 3 вибудували рівень ізоляції: дерево програми представлено за допомогою Tasty API, що не міняється під час зміни версії компілятора. І сама робота з макросами стала простішою. Далі таке представлення коду на рівні дерев насправді потрібно не для всіх макросів, тому в API виділили набір ще простіших інтерфейсів. Як-от quotes, де можна писати та аналізувати Scala-вирази у Scala-синтаксисі, навіть не знаючи, як вони транслюються в дерева. Також з’явилось API стейджингу, що дає змогу легко вбудувати компілятор у свій проєкт і генерувати код на Scala, який можна одразу переводити в байт-код і запускати.

Є багато цікавих застосувань Scala-метапрограмування, що відкривають нові можливості для екосистеми. Наприклад, наразі я займаюся побудовою інтерфейсів асинхронного програмування (проєкт на Github), що дасть змогу використовувати Scala як мову програмування загального призначення навіть у відносно простих задачах, де раніше застосування Scala було схоже на стрільбу з гармати по горобцях.

Differentiable programming

Хочеься розказати про ще одне потенційне застосування метапрограмування — диференційоване програмування (Differentiable programming), де програміст пише параметризовану функцію, а набори макросів автоматично генерують похідну цієї функції та все необхідне для градієнтної оптимізації. Класичний приклад — з простого перемноження кількох матриць автоматично генерується алгоритм оберненої пропагації нейронної сітки. Разом із системою ретаргетингу виконання коду на чомусь типу TensorFlow це відкриває новий вимір можливостей, де експериментування з різними архітектурами систем машинного навчання стає набагато зручнішим.

Популярність і використання

Що можна сказати про подальшу популярність Scala? Давати прогнози — невдячна справа. Однозначно у сфері інфраструктури обробки даних Scala буде однією з найважливіших мов дуже довго. Оскільки ця галузь зростає, то й застосування Scala загалом буде збільшуватись. Проте інфраструктурні проєкти рідше віддають на сторонню розробку, тому я не впевнений, що це буде видно з позиції нашої аутсорсингової індустрії.

Чи будуть нові сфери застосування — тут багато залежить від того, як нововведення до Scala 3 демократизують криву навчання. Загалом настрій у спільності оптимістичний: у підсумку люди вибирають технології, а опанувавши Scala, важко не стати її палким прихильником.

У підсумку

Як бачимо, кількість змін у Scala 3 робить її ледь не іншою мовою. Перехід екосистеми буде відбуватися поступово, в Dotty є можливість використовувати версії бібліотек для Scala 2, а також опція підтримки старого синтаксису.

Експериментувати з цим можна вже зараз: на сайті Dotty є все необхідне.

Відомо, що після Scala 3 буде дослідження в царині систем ефектів. Також варто звернути увагу на розвиток бекенду для не-jvm платформи (scala-js та scala-native).

Загалом я думаю, Scala буде залишатись одним з основних каналів зв’язку, що поєднують світи академії та індустрії, темпи її еволюції вражають і найближчі роки будуть цікавими.

Похожие статьи:
ІТ-фахівці звернулися до DOU та поскаржилися, що компанія SoftTeam заборгувала їм гроші за виконану роботу. Цю інформацію нам підтвердили...
Продовжуємо літній цикл статей про зарплати айтівців. Уже вийшов матеріал про розробників, а цей — про QA-фахівців. В опитуванні цього...
В Україні з’явилося нове об’єднання IT-компаній — UA Tech Network. Його заснували компанії Sigma Software, IdeaSoft (член Sigma Software Group) та UA Tech Club....
Єлизавета Юхнова, в минулому спеціалістка технічної підтримки, разом з маленьким сином переїхала у Францію в березні 2022 року...
Image via Shutterstock.[Об авторе: Алексей Витченко — серийный предприниматель, имеет более 10 лет опыта в digital, e-commerce на западных...
Яндекс.Метрика