Въведение във функционалното програмиране със
Scala
Инсталиране на Scala
- Ще ни е нужна Java среда. Инсталирайте JDK (последната версия 21
работи перфектно, по-ранна също е ок)
- Не забравяйте да си зададете
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!
Скриптове
$ scala HelloWorld.scala
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.9.8
Тестове
Използваме библиотеката 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
}
Форматиране
$ scalafmt
Конфигурира се през .scalafmt
файл.
Вижте повече в документацията
(включително как да си го пуснете в IDE-то).
Включване на Java библиотеки
build.sbt
:
name := "hello-world"
version := "0.1"
scalaVersion := "3.3.1"
libraryDependencies ++= Seq(
// Java библиотека, може да се използва директно в Scala код
"com.google.guava" % "guava" % "30.1-jre",
// Scala библиотека,чрез %% sbt успява вземе артефакта за правилната версия на Scala
"org.typelevel" %% "cats-core" % "2.10.0",
// Test посочва, че библиотеката ще е налична само за тестовете (в src/test/scala)
"org.scalatest" %% "scalatest" % "3.2.18" % Test
)
Представете ни се за бонус точка
Споделете няколко думи за себе си и своите интереси и качете снимка с
инсталирана и работеща Scala в #да-се-представим
в Slack за 1 бонус точка.
Типове и литерали
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"
От тук нататък ще използваме само този стил
От Scala 3.3 може да го правим и при аргументи на функция:
println {
val result = squared(42)
s"The result is: $result"
}
// ->
println:
val result = squared(42)
s"The result is: $result"
Типова йеархия
![]()
Родител на всички други типове
Сравнение по стойност: ==
,
!=
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
Досега
- Scala
- Scala toolset
- Типове и литерали. Низови литерали
- Дефиниции –
val
, var
, def
,
type
. Функции
- Type inference
- Файлове и пакети
- Всяка стойност е обект. Инфиксно извикване на методи
- Всичко е израз (със стойност и тип) или дефиниция
if
конструкция, блокове (и те са изрази 😊)
- Типова йеархия
- Any, AnyVal, AnyRef, Unit, Nothing, Null
- Memory model на JVM
Днес
- Pattern matching
- Малко конструкции със странични ефекти
- Съставни типове и колекции
- Функционални конструкции
- Няколко задачки
- Други благинки при дефиниране на функции
- (днес или сряда) Какво всъщност е ФП
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
Подобно на List, имаме и оператор за добавяне на елемент към Seq:
1 +: 2 +: Seq.empty // Seq(1, 2)
1 +: 2 +: Seq(3, 4, 5) // Seq(1, 2, 3, 4, 5)
Задача
Напишете функция, проверяваща, че скобите в един израз са
балансирани
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): Seq[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",
)
Fewer braces – Scala 3.3
println {
val result = squared(42)
s"The result is: $result"
}
// ->
println:
val result = squared(42)
s"The result is: $result"
Още нямаме мнение, ще го открием с вас
Още за функциите
- типови параметри
- 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-и)
- изпълнявани във времето
- редът е важен и всеки statement зависи от всички
предишни
- „места“ (places), съдържащи данни, които се
променят постоянно
- пести памет и позволява по-добър контрол над
памметта
- полезно при оптимизации
- трудни за проследяване – трябва да имаме предвид
всяка една стъпка и реда на всички стъпки
![]()
Умни хора
![]()
“Software is not about memory places”
- The heart of a software is working with information
- and transforming information to something
useful
- Information is a collection of facts
- data, events, actions, time passing, …
- Which lead to derived (often aggregated) facts and
actions taken
- actions from software perspective is sending a
derived fact to another component/system
- Facts are values
- They don’t change!
“The Value of Values”, Rich Hickey
Функционални програми
описват трансформации на стойности
декларативно – описваме проблема чрез “какво”
желаем да получим, а не “как”
изразите са единственото, определящо
зависимости
val a = 10
val b = 40
val c = 50
val x = a + c
val y = b * 40
val z = y * x * x
- нищо не се променя, единствено се генерират нови
стойности
Функционални програми
- лесни за проследяване и за разсъждаване върху тях
- всяка променлива винаги съдържа една стойност
- …и има един смисъл
- информацията е неизменима
- липсват странични ефекти
- времето е „спряло“
![]()
Функционални програми
- лесна композиция на отделните части
- Важна е единствено тяхната стойност
- Нужно е само да съвпаднат типовете
- “Guided by the types”
Функционални програми
![]()
Constraints enable!
Но… нещата не се ли променят с времето?
val oldZdravko = Person("Zdravko", 32, "Varna", "Bulgarian")
val newZdravko = oldZdravko.copy(age = 33, address = "Sofia")
oldZdravko.sayHiTo(newZdravko) // и двете версии на Здравко продължават да съществуват
- и двете версии са факт, който е бил актуален в
даден момент
- неизменимите стойности ни дават моментна снимка във
времето
- нещо, което има идентичност и се изменя във
времето, се нарича “entity”
- често ни интересува само последното му
състояние
- но предишните му съществуват като факти и носят
допълнителна информация
Функционалното програмиране работи с факти
Програма без странични ефекти програма ли е?
Ще разгледаме множество функционални средства за ограничаване на
страничните ефекти само до определени места
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 => ""