Інструменти

ред.

Потрібно встановити IDE (Eclipse), та Scala Build Tool (sbt).

Парадигми програмування

ред.

Парадигма - це особливі концепти чи шаблони мислення в певній дисципліні.

Парадигма імперативного програмування - набір інструкцій для комп’ютера в фон-нейманівській архітектурі (процесор та пам’ять, в якій пам’ять даних та пам’ять інструкцій не відмежовуються архітектурно), які змінюють пам’ять (стан).

Парадигма функціонального програмування - це обчислення результатів виконання функцій над даними.

Теорія в математиці це:

  • Один чи більше тип даних
  • Операції над цими типами даних
  • Закони що пов’язують операції та результати цих операцій.

Теорії не описують зміну стану, тому що зміни стану знешкоджують дію корисних законів в математичній теорії.

Функціональне програмування має такі переваги:

  • Простіше виводити теореми (і будувати теорії) про програми
  • Краща модульність
  • Легше працювати з конкурентним кодом

Елементи програмування

ред.

REPL в Scala викликається командою scala. Далі вона може працювати як звичайний калькулятор.

Можна також писати означення:

def radius = 10

Імена обчислюються за допомогою їх заміни на означення. Оператори з операндами замінюються на результат застосування операторів до операндів.

В Scala всі імена і аргументи функцій мають тип. Приклад примітивних типів - Int, Double, Boolean

Функції означаються так:

def square(x: Double) = x * x
def power(x: Double, n: Int) = ...

Модель обчислень, при яких вирази замінюються їх значеннями називається моделлю заміщень (Substitution model). Ця модель описується в лямбда-численні.

Побічні ефекти (типовий приклад - вираз c++ ) неможливо описати моделлю заміщень.

Не кожен вираз заміщеннями можна обчислити за скінченний час, наприклад: def loop: Int = loop

При обчисленні функції перед заміщенням її імені означенням, всі аргументи обчислюються. Такий підхід називається call-by-value. Інший підхід - call-by-name, не вимагає обчислення значень аргументів перед застосуванням до них функції. Виклик за іменем має більше кроків ніж виклик за значенням, але не обчислюється, наприклад, коли аргумент не використовують.

Умовні вирази

ред.
def abs(x: Int) = if (x >= 0) x else -x

Булева логіка містить такі вирази: true, false, !b, b && b, b || b, і порівняння як у звичайних мовах програмування. Бінарні оператори не завжди потребують правого операнду щоб знайти значення.

def - означення за іменем, виконується при виклику. def x = loop - просто інше ім’я для loop. val - означення за значенням, виконується зразу. val x = loop - не закінчить виконання.

def and(x: Boolean, y: => Boolean) // приклад передачі параметру y за іменем.

Блок - це кілька виразів у фігурних дужках. Значення блоку дорівнює значенню останнього виразу. Наприклад:

{
  val x = f(3)
  x * x
}

Означення ззовні блоку видимі всередині. Але затіняються внутрішніми.

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

Хвостова рекурсія

ред.

Якщо функція останнє що виконує - це сама себе, то вона може оптимізуватись.

def factorial(n: Int) =
  if (n == 0) 1 else n * factorial(n - 1)

Не оптимізується, бо остання дія - множення.

Позначити функцію як хвостово-рекурсивну можна анотацією (декоратором):

import scala.annotation.tailrec

@tailrec
def factorial ...

Частково-рекурсивний факторіал:

def factorial(n: Int): Int = {
  def loop(acc: Int, n: Int) : Int =
    if (n == 0) acc
    else loop(acc * n, n - 1)
  loop(1, n)
}

Функції вищого порядку

ред.

Кожна функція має тип, який записується як A => B, що означає що функція приймає тип A, і повертає B. Або (A, B) => C - якщо функція двох аргументів. Для більшого числа аргументів - аналогічно.

Приклад функції що приймає функції:

map(l: List[Int], f: Int => Int): List[Int] 
// приймає список цілих, і функцію що приймає ціле і повертає інше ціле, 
// та повертає інший список такої ж довжини в якому всі значення 
// це результати виклику функцій до значень вхідного списку.

Анонімна функція: (x: Int, y: Int) => x + y, чи наприклад x => x * x, а тип виведеться.

(x, y) => x + y також можна записати як _ + _.

Каррінг

ред.
def sum(f: Int => Int)(a: Int, b: Int): Int = 
  if (a > b) 0 else f(a) + sum(f)(a + 1, b)

це синтаксичний цукор для

def sum(f: Int => Int): ((a: Int, b: Int) => Int) = {
  def sumF(a: Int, b: Int): Int = 
     if (a > b) 0
     else f(a) + sumF(a + 1, b)
  sumF
}