Scala is Love
Камен Коцев
HackConf 2017
Какво прави един език за програмиране хубав?
Има ли най-хубав език за програмиране?
Програмисти обсъждащи езици за програмиране
Езици за програмиране
характеристика
- Трябва да могат да се изпълнят
- процесора да може да ги изпълни
- Трябва да са удобни
- програмистът да може да изрази всичко което иска - с (малко) код
- Да "скалират" добре (pun intended)
- Да помагат на програмиста
- да допуска по-малко грешки при писане
1. Трябва да могат да се изпълнят
от процесора
Покрива се от всеки език за програмиране с който бихме работили
2. Трябва да са удобни
за програмиста
Най-често решение:
Процедурно програмиране
Процедурно програмиране
дефиниция
- Описва инструкции
- Изпълняват се в определен ред
- Всяка инструкция променя state-а
- Изпълнението води до решение на проблема/задачата
Процедурно програмиране
пример
int a = 5;
int b = 7;
int c = 3;
int d = 0;
d = a + b + c;
Процедурно програмиране
пример 2
// Fraction A
int numeratorA = 3
int denominatorA = 4
// Fraction B
int numeratorB = 2
int denominatorB = 5
// Fraction C = A + B => 3/4 + 2/5 = 23/20
int numeratorC =
numeratorA * denominatorB +
numeratorB * denominatorA
int denominatorC =
denominatorA * denominatorB
А ако бяха много дроби?
Проблем:
работата с много на брой примитивни типове е трудна и уязвима на грешки
Решение:
ООП
ООП
характеристика
- Обединява известни типове данни за да построи нови типове данни
- Помага работата на програмиста
- Допускат се значително по-малко грешки при писане
- Повтаря се значително по-малко код
ООП
пример
class Fraction {
private int num;
private int den;
Fraction(int num, int den) {
this.num = num; this.den = den;
}
public Fraction add(Fraction other) {
return new Fraction(
this.num * other.den + this.den * other.num,
this.den * other.den
);
}
}
// ...
// C = A + B
Fraction C = A.add(B)
Процедурно + ООП == <3
Езици за програмиране
характеристика
-
Трябва да могат да се изпълнят
- процесора да може да ги изпълни
-
Трябва да са удобни
- програмистът да може да изрази всичко което иска - с (малко) код
- Да "скалират" добре
- Да помагат на програмиста
- да допуска по-малко грешки при писане
3. Да "скалират" добре
- Да могат да изпълняват кода бързо
- Да има начин скоростта на изпълнение да се подобри без да се променя кода
Процесори Intel 1970 - 2010
Не можем да получим повече скорост от един процесор
Ще работим с много процесори!
Асинхронно = Acync
=>
Процедурно/ООП + Async
проблеми
- shared memory
- race conditions
- deadlocks
- others
Процедурно програмиране
дефиниция (пак)
- Описва инструкции
- Изпълняват се в определен ред
- Всяка инструкция променя state-а
- Изпълнението води до решение на проблема/задачата
Процедурно/ООП + Async
има ли решения?
- Locking memory
- Semaphors
- Mutexes
- ... others
Проблем при променянето на стойности?
=>
Няма да променяме стойности!
Immutable
дефиниция
- стойност, която не може да бъде променяна
- всяка промяна на стойността води до създаване на копие на стойността с промяната вътре
Има си своята цена, но е решение!
Езици за програмиране
характеристика
-
Трябва да могат да се изпълнят
- процесора да може да ги изпълни
-
Трябва да са удобни
- програмистът да може да изрази всичко което иска - с (малко) код
- Да "скалират" добре
- Да помагат на програмиста
- да допуска по-малко грешки при писане
4. Да помагат на програмиста
да допуска по-малко грешки при писане
Проблемите най-често идват от писането със страничен ефект (Side effect)
Страничен ефект
дефиниция
Страничен ефект
решение
Pure functions - функции без страничен ефект
same input => same output
Pure functions
Можем ли да пишем само с pure functions?
Да, но рядко ще ни е полезно
Можем обаче да изнесем страничния ефект извън бизнес логиката
Pure functions
бонус
Тестват се много лесно!
Same input = Same output
Езици за програмиране
характеристика
-
Трябва да могат да се изпълнят
- процесора да може да ги изпълни
-
Трябва да са удобни
- програмистът да може да изрази всичко което иска - с (малко) код
- Да "скалират" добре
-
Да помагат на програмиста
- да допуска по-малко грешки при писане
Какво получаваме?
Easy to write
+
Immutable data
+
Pure functions
Какво получаваме?
Easy to
write
immutable
data
Pure
functions
Recursion
Higher order
functions
First-class
functions
Functional programming
Функционално програмиране
- Immutable data
- Pure functions
- Higher order functions
- Recursion
- First class functions
Къде е Scala тук?
- Scala не е по-добра Java!
- Scala не е Haskell on JVM!
Scala е мост между процедурните/ооп парадигми и функционалните парадигми
Дава ни инструментите, които са ни нужни, за да пишем на стила, който ни допада/трябва
Защо обичам да пиша на Scala?
- Кратък, лесен за четене код
- Функционален език
- Лесен синтаксис
- Свобода в избора на стил
- Високо И ниско ниво
Да видим малко код!
public class User {
private Integer id;
private String name;
User(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User(" + this.id + ", " + this.name + ")";
}
}
Защо обичам да пиша на Scala
public class User {
private Integer id;
private String name;
User(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "User(" + this.id + ", " + this.name + ")";
}
}
Защо обичам да пиша на Scala
public class User {
public Integer id;
public String name;
User(Integer id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User(" + this.id + ", " + this.name + ")";
}
}
Защо обичам да пиша на Scala
public class User(Integer id, String name) {
public Integer id;
public String name;
@Override
public String toString() {
return "User(" + this.id + ", " + this.name + ")";
}
}
Защо обичам да пиша на Scala
1. Scala compiler infers types when they are obvious
String a = "Hello!"
a = "Hello!"
val a = "Hello!"
or
val a: String = "Hello!"
public class User(Integer id, String name) {
public Integer id;
public String name;
@Override
public String toString() {
return "User(" + this.id + ", " + this.name + ")";
}
}
Защо обичам да пиша на Scala
2. toString метода зависи само от конструктора и при immutable обекти е ясен
val j = User(1, "John")
print(j) // User(1, John)
val k = User(2, "Kate")
print(k) // User(2, Kate)
public class User(Integer id, String name) {
public Integer id;
public String name;
@Override
public String toString() {
return "User(" + this.id + ", " + this.name + ")";
}
}
Защо обичам да пиша на Scala
3. Ако указваме типа и имената на атрибутите при извикването на конструктора, и обекта ни ще е immutable -> няма смисъл да ги пишем втори път
public class User(Integer id, String name) {
public Integer id;
public String name;
@Override
public String toString() {
return "User(" + this.id + ", " + this.name + ")";
}
}
Защо обичам да пиша на Scala
4. Ако дефинираме immutable обект можем да приемем че ще се използва във всички пакети освен ако не сме казали нещо друго (Ще е public по default)
case class User(id: Int, name: String)
Защо обичам да пиша на Scala
Immutable обект, с който може да се прави pattern matching
Защо обичам да пиша на Scala
public boolean exists(Integer el, List<Integer> list) {
for(int i=0; i<list.size(); i++) {
if(list.get(i).equals(el)) {
return true;
}
}
return false;
}
Защо обичам да пиша на Scala
def exists(x: Int, l: List[Int]): Boolean = {
if(l.isEmpty)
false
else if(l.head == x)
true
else
exists(x, l.tail)
}
Защо обичам да пиша на Scala
def exists(x: Int, l: List[Int]): Boolean = {
l match {
case Nil => false
case head :: tail =>
if(head == x) true
else exists(x, tail)
}
}
Защо обичам да пиша на Scala
def exists(x: Int, l: List[Int]): Boolean = l match {
case Nil => false
case head :: tail if head == x => true
case _ => exists(x, l.tail)
}
Защо обичам да пиша на Scala
def exists(x: Int, l: List[Int]): Boolean = l match {
case Nil => false
case `x` :: _ => true
case _ => exists(x, l.tail)
}
implicits в Scala
демо type conversion
case class Name(value: String)
case class User(id: Int, name: Name)
//...
val u = User(1, Name("John"))
implicits в Scala
демо type conversion
case class Name(value: String)
case class User(id: Int, name: Name)
//...
implicit def stringToName(s: String): Name = Name(s)
val u = User(1, "John") // Ok
implicits в Scala
демо type conversion
implicit def booleanToInt(b: Boolean): Int =
if(b) 1 else 0
// ...
def addOne(x: Int): Int = x + 1
// ...
addOne(5) // = 6
addOne(true) // = 2
implicits в Scala
демо
implicit class StringHelpers(x: String) {
def emphasize: String = s"!!!${x.toUpperCase}!!!"
}
val s = "John"
print(s.emphasize) // !!!JOHN!!!
implicits в Scala
демо
case class User(id: Int, name: String)
val input =
""" { "id": 5, "name": "Jill"} """
JSON.parse(input).as[User]
Bare with me
implicits в Scala
trait JSValue {
def as[T](implicit r: JSReads[T]) = r.read(this)
}
case class JSObject(value: Seq[(String, JSValue)]) extends JSValue
case class JSNumber(value: BigDecimal) extends JSValue
case class JSString(value: String) extends JSValue
case class JSBoolean(value: Boolean) extends JSValue
case class JSArray(value: Seq[JSValue]) extends JSValue
case object JSNull extends JSValue
implicits в Scala
trait JSReads[A] {
def read(js: JSValue): JSResult[A]
}
object JSReads {
def simpleReads[A](f: PartialFunction[JSValue, A]): JSReads[A] = JSReads[A] {
f.lift.andThen {
case Some(x) => JSSuccess(x)
case None => JSError("Could not parse")
}
}
implicit val intReads: JSReads[Int] =
simpleReads{ case JSNumber(x) => x.toInt }
implicit val stringReads: JSReads[String] =
simpleReads{ case JSString(x) => x }
implicit val booleanReads: JSReads[Boolean] =
simpleReads{ case JSBoolean(x) => x }
implicit val doubleReads: JSReads[Double] =
simpleReads{ case JSNumber(x) => x.toDouble }
}
implicits в Scala
object JSON {
def read[T](value: JSValue)(implicit r: JSReads[T]): JSResult[T] =
r.read(value)
def parse(json: String): JSValue = {
json.trim match {
case x if x.startsWith("[") && x.endsWith("]") =>
val children = recogniseJson(x.tail.init)
val res = children.map{case (k,v) => parse(v)}
JSArray(res)
case x if x.startsWith("{") && x.endsWith("}") =>
val children = recogniseJson(x.tail.init)
val res = children.map{case (k,v) => k -> parse(v)}
JSObject(res)
case "null" => JSNull
case x if x.startsWith("\"") && x.endsWith("\"") => JSString(x.tail.init)
case x if x.forall(x => x.isDigit || x == '.') => JSNumber(BigDecimal(x))
case x if x == "true" || x == "false" => JSBoolean(x.toBoolean)
case x => throw new Exception("WTF is " + x)
}
}
// ...
}
implicits в Scala
object JSON {
def read[T](value: JSValue)(implicit r: JSReads[T]): JSResult[T] =
r.read(value)
def parse(json: String): JSValue = {
// ...
}
}
implicits в Scala
case class User(id: Int, name: String)
object User {
implicit val read: JSReads[User] = {
case JSObject(Seq(("id",JSNumber(id)), ("name", JSString(name)))) =>
JSSuccess(User(id.toInt, name))
case _ => JSError("Cannot parse to user")
}
}
implicits в Scala
демо
case class User(id: Int, name: String)
val input =
""" { "id": 5, "name": "Jill"} """
JSON.parse(input).as[User]
Защо още обичам да пиша на Scala
- High-order functions
- Implicits
- Extractors
- Type classes
- Future/Option/Either/Try
- ...
Благодаря за вниманието
Камен Коцев
HackConf 2017
Въпроси?
Добра литература:
Scala is Love
By Kamen Kotsev
Scala is Love
- 711