Loading

Scala is Love

Kamen Kotsev

This is a live streamed presentation. You will automatically follow the presenter and see the slide they're currently on.

 Scala is Love

Камен Коцев

HackConf 2017

Какво прави един език за програмиране хубав?

Има ли най-хубав език за програмиране?

Програмисти обсъждащи езици за програмиране

Езици за програмиране

характеристика

  1. Трябва да могат да се изпълнят
    • процесора да може да ги изпълни
  2. Трябва да са удобни
    • програмистът да може да изрази всичко което иска - с (малко) код
  3. Да "скалират" добре (pun intended)
  4. Да помагат на програмиста
    • да допуска по-малко грешки при писане

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

Езици за програмиране

характеристика

  1. Трябва да могат да се изпълнят
    • процесора да може да ги изпълни
  2. Трябва да са удобни
    • програмистът да може да изрази всичко което иска - с (малко) код
  3. Да "скалират" добре
  4. Да помагат на програмиста
    • да допуска по-малко грешки при писане

3. Да "скалират" добре

  • Да могат да изпълняват кода бързо
  • Да има начин скоростта на изпълнение да се подобри без да се променя кода

Процесори Intel 1970 - 2010

Не можем да получим повече скорост от един процесор

Ще работим с много процесори!

Асинхронно = Acync

=>

Процедурно/ООП + Async

проблеми

  • shared memory
  • race conditions
  • deadlocks
  • others

Процедурно програмиране

дефиниция (пак)

  • Описва инструкции
  • Изпълняват се в определен ред
  • Всяка инструкция променя state-а
  • Изпълнението води до решение на проблема/задачата

Процедурно/ООП + Async

има ли решения?

  • Locking memory
  • Semaphors
  • Mutexes
  • ... others

Проблем при променянето на стойности?

=>

Няма да променяме стойности!

Immutable

дефиниция

  • стойност, която не може да бъде променяна
  • всяка промяна на стойността води до създаване на копие на стойността с промяната вътре

Има си своята цена, но е решение!

Езици за програмиране

характеристика

  1. Трябва да могат да се изпълнят
    • процесора да може да ги изпълни
  2. Трябва да са удобни
    • програмистът да може да изрази всичко което иска - с (малко) код
  3. Да "скалират" добре
  4. Да помагат на програмиста
    • да допуска по-малко грешки при писане

4. Да помагат на програмиста

да допуска по-малко грешки при писане

Проблемите най-често идват от писането със страничен ефект (Side effect)

Страничен ефект

дефиниция

Страничен ефект

решение

Pure functions - функции без страничен ефект

 

same input => same output

Pure functions

Можем ли да пишем само с pure functions?

Да, но рядко ще ни е полезно

Можем обаче да изнесем страничния ефект извън бизнес логиката

Pure functions

бонус

Тестват се много лесно!

Same input = Same output

Езици за програмиране

характеристика

  1. Трябва да могат да се изпълнят
    • процесора да може да ги изпълни
  2. Трябва да са удобни
    • програмистът да може да изрази всичко което иска - с (малко) код
  3. Да "скалират" добре
  4. Да помагат на програмиста
    • да допуска по-малко грешки при писане

Какво получаваме?

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 тук?

  1. Scala не е по-добра Java!
  2. 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

Въпроси?

Добра литература:

Made with Slides.com