Scala/Coursera/Функції

Матеріал з Вікіпідручника

Інструменти[ред.]

Потрібно встановити 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
}