Въведение във функционалното програмиране със Scala
Инсталиране на Scala
- Ще ни е нужна Java среда. Инсталирайте JDK (последната версия 17 работи перфектно, по-ранна също е ок)
- Не забравяйте да си зададете
JAVA_HOME
environment variable
- Инсталирайте Coursier
- Пуснете
./cs setup
. Това ще ви инсталира няколко инструмента:
- scala
- scalac
- sbt
- scalafmt
- amm
- и други
IDE или текстов редактор
Използвайте вашето любимо IDE или редактор:
- IntelliJ Community Edition
- инсталирайте Scala plugin-а към него
- Metals – имплементация на Language Server Protocol. Работи с:
- Visual Studio Code
- Vim
- Emacs
- Sublime Text
- и други
Read-eval-print loop (REPL)
- интерактивен езиков шел
- стартира се от командния ред със
scala
Hello World
object HelloWorld:
def main(args: Array[String]): Unit =
println("Hello, World!")
Компилиране и изпълнение
$ scalac HelloWorld.scala
$ scala HelloWorld
Hello, World!
Hello World (Scala 3)
@main
def hello = println("Hello, World!")
Компилиране и изпълнение
$ scalac HelloWorld.scala
$ scala hello
Hello, World!
sbt – Директорийна структура
build.sbt
src/main/scala
– основен код
src/test/scala
- тестове
sbt команди
- sbt <команда> – изпълнява командата
- sbt – влиза в интерактивен режим
- compile – компилира кода
- run – изпълнява обект с
main
метод
- console – стартира REPL, в който е достъпно всичко от кода
- test – пуска всички тестове
Фиксиране на sbt версия
project/build.properties
:
sbt.version=1.6.2
Тестове
Използваме библиотеката ScalaTest
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class ExampleSpec extends AnyFlatSpec with Matchers:
"+" should "sum two numbers" in {
2 + 3 shouldBe 5
}
Включване на Java библиотеки
build.sbt
:
name := "hello-world"
version := "0.1"
scalaVersion := "3.1.1"
libraryDependencies ++= Seq(
// Java библиотека, може да се използва директно в Scala код
"com.google.guava" % "guava" % "30.1-jre",
// Scala библиотека, %% залепва версията на Scala към името (т.е. akka-actor-typed_2.13)
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.13",
// Test посочва, че библиотеката ще е налична само за тестовете (в src/test/scala)
"org.scalatest" %% "scalatest" % "3.2.11" % Test
)
Типове и литерали
Boolean
(8 bits) – true
, false
Char
(16 bits) – 'a'
, '\n'
- Числови типове
Byte
(8 bits)
Short
(16 bits)
Int
(32 bits) – 42
, 0x2A
Long
(64 bits) - 100000L
, 0x186A0L
Float
(32 bits) – 3.14f
Double
(64 bits) – 3.14
String
– "Hey :)!"
Дефиниции
val a = 10 * 2 // неизменима променлива – винаги сочи към една и съща стойност
var b = 20 * 4 // изменима променлива – може да бъде пренасочвана
def c = 30 * 8 // стойността се преизчислява при всяко използване, не заема памет
a = 100 // error: reassignment to val
b = 200 // успешно
Type inference
// компилаторът сам открива типа на променливите – String и Int
val inferred = "Hello, hello, hello"
def inferredSize = inferred.size
// типът се задава явно
val explicit: String = "Is there anybody in there?"
def explicitSize: Int = explicit.size
Функции
def fortyTwo = 42
def sum(a: Int, b: Int) = a + b
sum(fortyTwo, 58) // 100
Посочването на типовете на параметрите е задължително
// посочването на типа на връщания резултат е опционално,
// но препоръчително за публични функции
def twice(str: String): String = str * 2
twice(":)") // ":):)"
println("You can see me on your screen :P") // Функциите могат да имат странични ефекти
Къде живеят дефинициите?
- В блок
- Като членове на класове, обекти и др.
- (от Scala 3) на топ ниво на файл
Блокове
def solveQuadraticEquation(a: Double, b: Double, c: Double) = {
def squared(n: Double) = n * n
val discriminant = squared(b) - 4 * a * c
val discriminantSqrt = math.sqrt(discriminant)
val firstSolution = (-b - discriminantSqrt) / (2 * a)
val secondSolution = (-b + discriminantSqrt) / (2 * a)
(firstSolution, secondSolution) // Наредена двойка. Повече за тях по-късно в курса :)
}
Членове
object TestApp:
val a = 10 * 2
var b = 20 * 4
def c = 30 * 8
def sum = a + b + c
@main
def main(args: Array[String]) = println("The sum is: " + sum)
Топ ниво във файл
TestApp.scala
:
val a = 10 * 2
var b = 20 * 4
def c = 30 * 8
def sum = a + b + c
@main def printSum = println("The sum is: " + sum)
Файлове и пакети
Класовете, обектите, а от Scala 3 всички дефиниции,
се поставят във файл със .scala
разширение.
Всеки от тях принадлежи на определен пакет,
който се отбелязва преди дефинициите:
package com.scalafmi
val a = 10 * 2
var b = 20 * 4
def c = 30 * 8
def sum = a + b + c
@main def printSum = println("The sum is: " + sum)
Конвенция е файловете да се намират в директория,
съответстваща на пакета им.
Още дефиниции
type Person = String
type Address = String
def describe(name: Person, address: Address) = name + "'s address is: " + address
describe("Viktor", "Pazardzhik") // "Viktor's address is: Pazardzhik"
val ThisIsAConstantByConvention = 3.14 // имената на константите започват с главна буква
Още низови литерали
- Интерполация
- raw низове
- Многоредови низове
Интерполация
def describe(name: Person, address: Address, age: Int) =
s"$name's address is: ${twice(address)}. He is $age years old"
// "Viktor's address is: PazardzhikPazardzhik. He is 25 years old"
describe("Viktor", "Pazardzhik", 25)
raw низове
val evilLaughter = "Muhahahaha :D!"
raw"Escaping has no\\power \t here\n. $evilLaughter" // Escaping has no\\power \t here\n. Muhahahaha :D!
"Escaping works \\ \there\n" // Escaping works \ here<newline>
Полезно за регулярни изрази и други
Многоредови низове
val sql = """
SELECT id, name, address, age
FROM Person
WHERE hometown = 'Varna'
"""
Премахване на интервалите в началото:
val sql =
"""SELECT id, name, address, age
|FROM Person
|WHERE hometown = 'Varna'
|""".stripMargin
Всяка стойност е обект
Включително тези на основните типове
-42.abs // 42
1.to(10) // Range 1 to 10 (inclusive)
1.until(10) // Range 1 until 10
1.+(2) // 3, всички оператори са също методи!
4.*(5) // 20
Инфиксен запис (само за методи на един параметър)
1 to 10 // Range 1 to 10 (inclusive)
1 until 10 // Range 1 until 10
1 + 2 // 3
4 * 5 // 20
Да, имената могат да са символни:
val ==> = "An arrow"
def **(a: Double, b: Double): Double = math.pow(a, b)
**(2.0, 10.0) // 1024.0
Не могат да се смесват с букви/цифри:
val **Power = "**" // грешка!
if
конструкция
val n = 72
if n > 42 then "Greater than the ultimate answer"
else if n == 42 then "The answer"
else "Not there yet"
Всичко е израз
И си има стойност и тип
val n = 72
val evaluation =
if n > 42 then "Greater than the ultimate answer"
else if n == 42 then "The answer"
else "Not there yet"
evaluation // Greater than the ultimate answer
def fact(n: Int): Int =
if n <= 1 then 1
else n * fact(n - 1)
В Java (и други)
public int maxSquared(int a, int b) {
int max;
if (a > b) {
max = a;
} else {
max = b;
}
return max * max;
}
В Java сме задължени да извършим мутация/страничен ефект в if
В Scala
def maxSquared(a: Int, b: Int) = {
val max = if a > b then a else b
max * max
}
В Scala изглежда по-математично и ясно
Блоковете също са изрази!
val a = 2
val b = 10
val c = 5
val discriminantSqrt = {
val discriminant = b * b - 4 * a * c
math.sqrt(discriminant)
}
def maxSquared(a: Int, b: Int) = {
val max = if a > b then a else b
max * max
}
Оценяват се до последния statement в тях
Блоковете като изрази (допълнение)
def squared(n: Int) = n * n
squared(10)
squared({
val complicatedCalculation = 1 + 2 + 3 + 4
complicatedCalculation / 2
})
squared {
val complicatedCalculation = 1 + 2 + 3 + 4
complicatedCalculation / 2
}
println {
val result = squared(42)
s"The result is: $result"
}
При функции на един параметър Scala ни позволява
да пропускаме кръглите скоби
Блокове чрез индентация
Scala 3 ни позволява вместо чрез скоби да определяме блокове чрез индентация:
def solveQuadraticEquation(a: Double, b: Double, c: Double) =
def squared(n: Double) = n * n
val discriminant = squared(b) - 4 * a * c
val discriminantSqrt = math.sqrt(discriminant)
val firstSolution = (-b - discriminantSqrt) / (2 * a)
val secondSolution = (-b + discriminantSqrt) / (2 * a)
(firstSolution, secondSolution) // Наредена двойка. Повече за тях по-късно в курса :)
val theAnswerSquared =
val a = 42
a * a
Блокове чрез индентация
if n > 42 then
val greater = "Greater"
s"$greater than the ultimate answer"
else if n == 42 then "The answer"
else "Not there yet"
От тук нататък ще използваме само този стил
При аргументи на функции все пак сме задължени да използваме скобите:
println {
val result = squared(42)
s"The result is: $result"
}
Досега
- Scala
- Scala toolset
- Типове и литерали. Низови литерали
- Дефиниции –
val
, var
, def
, type
. Функции
- Type inference
- Файлове и пакети
- Всяка стойност е обект. Инфиксно извикване на методи
- Всичко е израз (със стойност и тип) или дефиниция
if
конструкция, блокове (и те са изрази 😊)
Типова йеархия
![]()
Родител на всички други типове
Сравнение по стойност: ==
, !=
42 == 84 / 2 // true
"Abc" != "Абв" // true
Hash код: ##
"Have a good day :)!".## // -244558110
10.0.## // 10
isInstanceOf[T]
, asInstanceOf[T]
toString
- JVM разделя типовете на примитивни и референтни
- Примитивните (
AnyVal
) се съхраняват в променливи чрез тяхната стойност
- Стойността на референтните (
AnyRef
) се съхранява в Heap-а като обект
- Променливите съхраняват референция към този обект
- Обектите подлежат на Garbage Collection
AnyVal
и AnyRef
val i = 42
val j = i
val strA = "Hello, Goodbye"
val strB = strA
val arr1 = Array(1, 2, 3)
val arr2 = arr1
arr2(0) = 100
![]()
Java Memory Model
![]()
Java Memory Model
![]()
AnyRef
– референция vs стойност
val name = "Tihomira"
s"Hello $name" == s"Hello $name" // true
s"Hello $name" eq s"Hello $name" // false
Типова йеархия
![]()
println???
Какъв е типът на:
- Съдържа една единствена стойност –
()
- Изразява страничен ефект
val printed: Unit = println(":)")
var mutable = 10
val mutationResult: Unit = mutable = 20
val a = 10
val b = 20
// Конвенция за функции без аргументи
def mult = a * b // Чисти функции пишем без скоби
def printMult() = println(a * b) // Функции със странични ефекти пишем със скоби
- Съществува единствено заради съвместимост с Java
- Всички
AnyRef
типове имат null
стойност
- В Scala я избягваме колкото можем
- Подтип на всички други типове
- Няма нито една стойност от тип
Nothing
![]()
Необходим за цялостност на някои изводи на ниво типова система
Тип на изключенията?
throw new RuntimeException()
def fail(reason: String): Nothing = throw new RuntimeException(reason)
def failingSqrt(n: Int) =
if n >= 0 then math.sqrt(n)
else fail(s"Square root of $n is not real")
failingSqrt(-1) // Какъв е типът?
Filling the blanks
def ??? : Nothing = throw new NotImplementedError
def twice(n: Int): Int = ???
def fib(n: Int): Int = ???
def fibDoubled(n: Int): Int = twice(fib(n)) // Компилира се
if
с разнородни типове
if 42 > 0 then true else 0
if 42 > 0 then "String" else println("String")
if 42 > 0 then 0
if 42 > 0 then true else 0 // AnyVal
if 42 > 0 then "String" else println("String") // Any
if 42 > 0 then 0 // AnyVal; else частта се счита за Unit
Pattern Matching
def stringify(n: Int): String = n match
case 1 => "One"
case 2 => "Two"
case 3 => "Three"
case _ => "I can only count to three :("
stringify(2) // Two
def parseBoolean(boolean: String): Boolean =
boolean match
case "true" | "True" | "TRUE" => true
case "false" | "False" | "FALSE" => false
case _ => false
parseBoolean("True") // true
Pattern Matching
def toInteger(value: Any): Int =
value match
case n: Int => n
case s: String => s.toInt
case d: Double => d.toInt
toInteger("42") // 42
Конструкции със странични ефекти
while
var i = 0
while i < 10 do
println(i)
i += 1
println(i)
// 0 1 2 3 4 5 6 7 8 9 10
Прихващане на изключения
val parsedResult =
try "42L".toInt
catch
case e: NumberFormatException => 0
parsedResult // 0
for
for
i <- 1 to 4
do println(i)
// 1 2 3 4
for - съставни части
- генератори
- филтри
- дефиниции
for
i <- 1 to 4 // генератор
if i % 2 == 0 // филтър
c <- 'a' to 'c' // генератор
s = s"$i$c" // дефиниция
do println(s)
// 2a 2b 2c 4a 4b 4c
Наредени n-торки (Tuples)
val person: (String, Int, String) = ("Ivan", 27, "Sofia")
def greeting(person: (String, Int, String)): String =
s"Hello, I am ${person._1} from ${person._3}. I am ${person._2} years old"
Хетерогенен тип с фиксиран размер
Хомогенни колекции
Range
, List[A]
, Set[A]
, Map[K, V]
Операции върху колекции
val xs = List(1, 2, 3, 4, 5)
xs.isEmpty // false
xs.size // 5
xs.head // 1
xs.tail // List(2, 3, 4, 5)
xs.take(3) // List(1, 2, 3)
xs.drop(2) // List(3, 4, 5)
xs.sum // 15; работи само за колекции от числени елементи
Map е колекция от tuple-и
Map(1 -> "One", 2 -> "Two", 3 -> "Three").head // (1, "One")
String е колекция от Char
"abcdef".head // 'a'
"abcdef".drop(2) // "cdef"
Range и List[A] са подтипове на Seq[A]
def sum(xs: Seq[Int]): Int = ???
def sum(xs: Seq[Int]): Int =
if xs.isEmpty then 0
else xs.head + sum(xs.tail)
sum(1 to 10) // 55
sum(List(1, 2, 3, 4)) // 10
Задача
Напишете функция, проверяваща, че скобите в един израз са балансирани
def balanced(e: List[Char]): Boolean = ???
Типови параметри
List[A]
, Set[A]
, Map[K, V]
са параметризирани типове
type
дефинициите също могат да приемат параметри:
type Index[V] = Map[Int, V]
def retrieveLast[V](index: Index[V]): V = index(index.keys.max)
retrieveLast(Map(5 -> "Five", 1 -> "One")) // Five
Функционален for :)
val squared = for
x <- List(1, 2, 3, 4)
yield x * x
squared // List(1, 4, 9, 16)
Функционален for
val result = for
x <- List(1, 2, 3, 4, 5)
if x % 2 != 0
y <- List(x, x * 2, x * 3)
yield (x, y)
result // List((1,1), (1,2), (1,3), (3,3), (3,6), (3,9), (5,5), (5,10), (5,15))
Задача
Генерирайте всички възможни поднизове на даден низ
def substrings(str: String): List[String] = ???
Scala 2 и Scala 3 синтаксис
- Scala 3 въведе опростение на синтаксиса на езика с цел четимост
- Optional braces
- Блокове чрез индентация
if
без скоби, но с then
- Все още доста код и документация са написани на стария синтаксис. Та нека да се запознаем с него
Braces – опционални в Scala 3
def discriminantSqrt(a: Double, b: Double, c: Double) =
val discriminant = b * b - 4 * a * c
math.sqrt(discriminant)
object HelloWorld:
def sayHiTo(name: String) = s"Hi $name!"
def sayHiToTheWorld = sayHiTo("World")
booleanString match
case "true" | "True" | "TRUE" => true
case "false" | "False" | "FALSE" => false
case _ => false
try "42L".toInt
catch
case e: NumberFormatException => 0
Braces – задължителни в Scala 2
def discriminantSqrt(a: Double, b: Double, c: Double) = {
val discriminant = b * b - 4 * a * c
math.sqrt(discriminant)
}
object HelloWorld {
def sayHiTo(name: String) = s"Hi $name!"
def sayHiToTheWorld = sayHiTo("World")
}
booleanString match {
case "true" | "True" | "TRUE" => true
case "false" | "False" | "FALSE" => false
case _ => false
}
try "42L".toInt
catch {
case e: NumberFormatException => 0
}
Контролни структури – Scala 3
for
x <- List(1, 2, 3, 4, 5)
if x % 2 != 0
y <- List(x, x * 2, x * 3)
yield (x, y)
for
i <- 1 to 4
do println(s)
while i < 10 do
println(i)
i += 1
if n > 42 then "Greater than the ultimate answer"
else if n == 42 then "The answer"
else "Not there yet"
Контролни структури – Scala 2
for {
x <- List(1, 2, 3, 4, 5)
if x % 2 != 0
y <- List(x, x * 2, x * 3)
} yield (x, y)
for {
i <- 1 to 4
} println(s)
while (i < 10) {
println(i)
i += 1
]
if (n > 42) "Greater than the ultimate answer"
else if (n == 42) "The answer"
else "Not there yet"
Блокове и контролни структури –
Scala 3
if n > 42 then
val difference = n - 42
"Greater than the ultimate answer with difference of $difference"
else if n == 42 then "The answer"
else "Not there yet"
for
x <- List(1, 2, 3, 4, 5)
if x % 2 != 0
y <- List(x, x * 2, x * 3)
yield
val firstElement = s"x: $x"
val secondElement = s"y: $x"
(firstElement, secondElement)
Блокове чрез индентация – приложимо при
всички контролни структури
Блокове и контролни структури –
Scala 2
if (n > 42) {
val difference = n - 42
"Greater than the ultimate answer with difference of $difference"
} else if (n == 42) "The answer"
else "Not there yet"
for {
x <- List(1, 2, 3, 4, 5)
if x % 2 != 0
y <- List(x, x * 2, x * 3)
} yield {
val firstElement = s"x: $x"
val secondElement = s"y: $x"
(firstElement, secondElement)
}
Блокове чрез скоби – приложимо навсякъде,
където е необходима стойност
Консистентен синтаксис
Scala 3 приема и двата синтаксиса
В курса ще използваме консистентно новия
В build.sbt
:
scalacOptions ++= Seq(
"-new-syntax",
"-indent"
)
Още за функциите
- типови параметри
- overloading
- default стойности на параметрите
- именувани аргументи
- променлив брой параметри
Полиморфизъм чрез типови параметри
def repeat[A](value: A, times: Int): List[A] =
if times == 0 then List.empty // or List.empty[A], but A is inferred
else value :: repeat(value, times - 1)
repeat("Hello", 3) // List("Hello", "Hello", "Hello")
Полиморфизъм чрез overloading
def twice(n: Int) = n * 2
def twice(d: Double) = d * 2
def twice(str: String) = str * 2
twice(10) // 20
twice(10.0) // 20.0
twice("10") // "1010"
Default стойности на параметрите
def enlist(items: List[String], separator: String = ", ") = items.mkString(separator)
val ingredients = List("1 egg", "200 ml milk", "1/2 cup of sugar", "2.5 cups of flour")
enlist(ingredients) // 1 egg, 200 ml milk, 1/2 cup of sugar, 2.5 cups of flour
enlist(ingredients, ";") // 1 egg;200 ml milk;1/2 cup of sugar;2.5 cups of flour
Именувани аргументи
def draw(
text: String,
textColour: String = "Blue",
backgroundColour: String = "White"
): String =
s"This is the text $text written with $textColour letters on $backgroundColour background"
draw("Hello")
draw("Hello", backgroundColour = "Cyan")
draw(backgroundColour = "Purple", textColour = "White", text = "Hello")
Променлив брой параметри
def enlist(first: String, rest: String*) = (first +: rest).mkString(", ")
// 1 egg, 200 ml milk, 1/2 cup of sugar, 2.5 cups of flour
enlist("1 egg", "200 ml milk", "1/2 cup of sugar", "2.5 cups of flour")
val ingredients = List("1 egg", "200 ml milk", "1/2 cup of sugar", "2.5 cups of flour")
// A pinch of love, 1 egg, 200 ml milk, 1/2 cup of sugar, 2.5 cups of flour
enlist("A pinch of love", ingredients*)
enlist("A cake") // A cake
enlist() // не се компилира
Функционално програмиране
Какво е функция?
Според математиката:
Релация на две множествена X
и Y
, съпоставяща
елементи от X
към елементи от Y
Функциите са:
- детерминистични – един и същи вход винаги води до един и същи изход
- чисти – без странични ефекти
- тотални – дефинирани за всеки вход (good to have)
Функционално срещу императивно
Императивни програми
- командваме програмата какво да направи
- постъпкови изчисления във времето
- всеки statement зависи от всички предишни
- трудни за проследяване – трябва да имаме предвид всяка една стъпка и нейния ред
Функционални програми
трансформират стойности
декларативно – описваме проблема чрез “какво”, а не “как”
изразите са единственото, определящо зависимости
val a = 10
val b = 40
val c = 50
val x = a + c
val y = b * 40
val z = y * x * x
лесни за проследяване и за разсъждаване върху тях
неизменими структури и липса на странични ефекти
времето е “спряло”
лесна композиция на отделните части
Моделиране на времето
val oldZdravko = Person("Zdravko", 32, "Varna", "Bulgarian")
val newZdravko = oldZdravko.copy(age = 33, address = "Sofia")
oldZdravko.sayHiTo(newZdravko) // и двете версии на Здравко продължават да съществуват
Функционалното програмиране работи с факти
Програма без странични ефекти програма ли е?
Ще разгледаме множество функционални средства за ограничаване на страничните ефекти само до определени места
Functional Wizard
![]()
Controls time & space
Има място и за двете
- Императивното е незаминимо при някои алгоритми и оптимизации
- Функционалното прави останалите части на програмата по-ясни
- ясна композитност между компоненти
“If a tree falls in a forest and no one is around to hear it, does it make a sound?”
Напълно приемлово е функция да използва mutable state,
ако е само вътрешен за нея
Тоест не може да бъде наблюдаван външно
Модел на изчисление
Колко от вас са учили СЕП?
Substitution model (операционна семантика)
def max(a: Int, b: Int) = if a > b then a else b
max(3 * 4, 2 * 3)
Предаване на параметрите по стойност
max(3 * 4, 2 * 3)
max(12, 6)
if 12 > 6 then 12 else 6
if true then 12 else 6
12
Предаване на параметрите по име
max(3 * 4, 2 * 3)
if (3 * 4) > (2 * 3) then (3 * 4) else (2 * 3)
if (12) > (6) then (3 * 4) else (2 * 3)
if true then (3 * 4) else (2 * 3)
3 * 4
12
Предаване на параметрите по име
def max(a: => Int, b: => Int) = if a > b then a else b
max(3 * 4, 2 * 3)
Предаване на параметрите по име
def ||(a: Boolean, b: => Boolean): Boolean = if a then true else b
||(true, {
println("Won't be printed")
false
}) // true
||(false, {
println("Will be printed")
false
}) // false
Предаване на параметрите по име
def describeByValue(items: List[Int], evaluation: Int): String =
if items.isEmpty then "No items available"
else s"Items: ${items.mkString(", ")} are evaluated to $evaluation"
def describeByName(items: List[Int], evaluation: => Int): String =
if items.isEmpty then "No items available"
else s"Items: ${items.mkString(", ")} are evaluated to $evaluation"
def avg(xs: List[Int]) = xs.sum / xs.size
val someItems = List(1, 2, 3)
describeByName(someItems, avg(someItems)) // Items: 1, 2, 3 are evaluated to 2
describeByValue(someItems, avg(someItems)) // Items: 1, 2, 3 are evaluated to 2
val noItems = List.empty[Int]
describeByName(noItems, avg(noItems)) // No items available
describeByValue(noItems, avg(noItems)) // java.lang.ArithmeticException: / by zero
Модел на изчисление в Haskell
def squared(n: => Int): Int =
lazy val nValue = n
nValue * nValue
Референтна прозрачност
Израз е референтно прозрачен, ако може да бъде заменен от своята стойност без това да променя поведението на програмата
Substitution модела работи само за референтно прозрачни изрази
Референтна прозрачност
// 1
val a = 1 + 2
List(а, a)
// 2
List(1 + 2, 1 + 2)
// 3
List(3, 3)
Референтна прозрачност
// 1
val a = {
println("Hey there!")
1 + 2
}
List(a, a)
// 2
List({
println("Hey there!")
1 + 2
}, {
println("Hey there!")
1 + 2
})
// 3
List(3, 3)
Изключенията не са референтно прозрачни
def exceptionThrowingFails(x: String): String =
val y: String = throw new Exception()
try x + y
catch
case _: Exception => ""
def exceptionThrowingComputes(x: String): String =
try x + (throw new Exception())
catch
case _: Exception => ""
Как да пишем функционално?
- Основни подходи
- Рекурсия
- Функциите като първокласни обекти
- Функции от по-висок ред
- Неизменими структури от данни
- Композитност
- Дизайн чрез типове и данни. Средства за трансформация на данни
- Работа с грешки и изключителни ситуации
- Контрол над страничните ефекти и ограничаването им
- Абстракции от по-висок ред