[Не]практичные типы
DESCRIPTION
Презентация, которая была сделана на 11-ой встрече коммьюнити Software Craftsmanship BYTRANSCRIPT
практичные типы Software Craftsmanship BY, @remeniuk
λ
система типов - статическая аппроксимация программы во время выполнения Бенжамин Пирс, "TAPL"
что дают типы?
производительностьбезопасностьдокументорованностьоптимизации компиляторалучшие абстракции
одни достоинства
в теории
public Map<Desk<Poker>, List<Player<Poker>> getPlayersPerDesks(){
Map<Desk<Poker>, List<Player<Poker>> players= new HashMap<Desk<Poker>, List<Player<Poker>>();
...return players;
}
boilerplate
в мейнстриме
Бенжамин Пирс приукрашал картину?
нет. просто он говорил не о Java (C#, C++, ..)
1, true, "abc" - термы / конкретные значения / абстракции нулевого порядкаx => x + 1 - функция / абстракции первого порядка (полиморфны, т.к. конструируют множественные конкретные значения)
* Int, Boolean, String конкретные типы
* -> * List[T], Map[K, V] функции на типах / полиморфные типы / конструкторы типов
trait Functor[F[_]] { def map[A, B](r: F[A], f: A => B): F[B]}
map - функция высшего порядка; в качестве переметра приниимает функцииFunctor - тип высшего порядка; в качестве параметров принимает конструкторы типов
(* -> *) -> *
λ-куб
λ->
λω
F
Fω CC
LF
F<:
Fω<:
λ<:
Fω<:
вариантность типов и System F<:
λ->
FF<:
λ<:
вариантность типов и System F<:
S подтип T, конструктор типов C<U> => C<S> подтип/супертип/? C<T>
вариантность типов в Java
cписки ковариантны: S[] подтип T[] oстальные типы - инварианты
workaround: имитация вариантности в месте вызова с помощью органиченных экзистенциальных типов
List<? extends List<? extends Number>> l = new ArrayList<List<Number>>();
вариантность типов в Scala
C[+U] - ковариантность, S подтип T => C[S] подтип C[T]
C[-U] - контрвариантность, S подтип T => C[T] супертип C[S]
С[U] - инвариантность (как в Java), S подтип T => C[T] ? C[S]
trait Suittrait Spades extends Suit
class Card[T <: Suit](rank: String)
class Hand[A <: Suit](cards: List[Card[A]] = Nil) { def add[B >: A <: Suit](card: Card[B]): Hand[B] = new Hand[B](card :: cards)}
val hand1: Hand[Spades] = new Hand.add(new Card[Spades]("A")).add(new Card[Spades]("Q"))val hand2: Hand[Suit] = new Hand.add(new Card[Spades]("A")).add(new Card[Hearts]("Q"))
trait Suittrait Spades extends Suit
class Card[+T <: Suit](rank: String)
class Hand[A <: Suit](cards: List[Card[A]] = Nil) { def add[B >: A <: Suit](card: Card[B]): Hand[B] = new Hand[B](card :: cards)}
val hand1: Hand[Spades] = new Hand.add(new Card[Spades]("A")).add(new Card[Spades]("Q"))val hand2: Hand[Suit] = new Hand.add(new Card[Spades]("A")).add(new Card[Hearts]("Q"))
class HandEvaluator[T <: Suit] (val evaluate: List[Card[T]] => Int)
val flushEval = new HandEvaluator( (_: List[Card[Spades]]) => 1000)val genEval = new HandEvaluator( (_: List[Card[Suit]]) => 100)
val hand1: Hand[Spades] = ...
> println(hand1.evaluate(flushEval))1000> println(hand1.evaluate(genEval))
Suit >:Spades, but class HandEvaluator is invariant in type T
class HandEvaluator[-T <: Suit] (val evaluate: List[Card[T]] => Int)
val flushEval = new HandEvaluator( (_: List[Card[Spades]]) => 1000)val genEval = new HandEvaluator( (_: List[Card[Suit]]) => 100)
val hand1: Hand[Spades] = ...
> println(hand1.evaluate(flushEval))1000> println(hand1.evaluate(genEval))100
неявная конверсия типов
1. class A { def foo(): Unit }2. class B
...магия... 3. val b = new B()
b.foo() // b был неявно конвертирован в A
[[A -> B]]
неявная конверсия типов
trait Card
class Hand(cards: List[Card]) { def weight = /*..*/ }
object Hand { implicit def toHand(cards: List[Card]) = new Hand(cards)}
тип List[Card] "видимый как" Hand
неявный контекст
import Hand._List[Card]().weight
ограничения на "вид"
class HandComparator {
def compare[T <% Hand](hand1: T, hand2: T) = hand1.weight compareTo hand2.weight
}
new HandComparator().compare(new Hand(Nil), new Hand(Nil))new HandComparator().compare(List[Card](), List[Card]())
ограничения на контекст
class Hand[A <: Suit](cards: List[Card[A]] = Nil) {
def add[B >: A <: Suit](card: Card[B]): Hand[B] = new Hand[B](card :: cards)
def evaluate(evaluator: HandEvaluator[A]) = evaluator.evaluate(cards)
}
val flushEval = new HandEvaluator( (_: List[Card[Spades]]) => 1000)val genEval = new HandEvaluator( (_: List[Card[Suit]]) => 100)
ограничения на контекст
class Hand[A <: Suit](cards: List[Card[A]] = Nil) {
def add[B >: A <: Suit](card: Card[B]): Hand[B] = new Hand[B](card :: cards)
def evaluate(evaluator: HandEvaluator[A]) =evaluator.evaluate(cards)
}
implicit val flushEval = new HandEvaluator( (_: List[Card[Spades]]) => 1000)implicit val genEval = new HandEvaluator( (_: List[Card[Suit]]) => 100)
помещаем в неявный контекст
ограничения на контекст
class Hand[A <: Suit : HandEvaluator](cards: List[Card[A]] = Nil) {
def add[B >: A <: Suit](card: Card[B]): Hand[B] = new Hand[B](card :: cards)
def evaluate = implicitly[HandEvaluator[A]].evaluate(cards)
}
implicit val flushEval = new HandEvaluator( (_: List[Card[Spades]]) => 1000)implicit val genEval = new HandEvaluator( (_: List[Card[Suit]]) => 100)
ограничения на контекст
val h1 = Hand().add(Card[Hearts]("A")).add(Card[Hearts]("Q"))val h2 = Hand().add(Card[Spades]("A")).add(Card[Spades]("Q"))val h3 = Hand().add(Card[Spades]("A")).add(Card[Hearts]("Q"))
> println(h1.evaluate) could not find implicit value for evidence parameter of type by.scala.pokertypes.variance.HandEvaluator[B]> println(h2.evaluate) 1000> println(h3.evaluate)100
реификация vs стирание типов
C#, F# (CLR) реализует реификацию типов, но не поддерживает типы высших порядковJava (JVM) стирает типы, и не поддерживает типы высших порядков
Scala имитирует реификацию, и поддерживает типы высших порядков
имитация реификации
def evaluate[T <: Suit : Manifest](hand: Hand[T]) = { print("Type parameter class: " + manifest[T].erasure) if (manifest[T] <:< manifest[Spades]) print("; Spades")
else print("; Random")}
val h2 = Hand().add(Card[Spades]("A")).add(Card[Spades]("Q"))
> println(evaluate(hand1))Type parameter class: interface Spades; Spades> println(evaluate(hand2))Type parameter class: interface Suit; Random
зависимые типы
LFλ->
зависимые типы
case class Desk(id: Int) {
case class Action(name: String)
def log(action: Action) = /*..*/}
desk1.log(desk1.Action("Small Blind"))
desk1.log(desk2.Action("Small Blind")) // не компилируется
черная магия
типы-фантомы тип-объеднение и гетерогенные
списки
class Dealer[T <: Stage : Manifest](deck: List[Card]) { def deal(cards: Int, recipient: Actor): Dealer = /*...*/}
если бы мы могли гарантировать на уровне типов, что дилер выдаст всем нужное количество кард...
типы-фантомы
sealed trait Stagetrait Preflop extends Stagetrait Flop extends Stagetrait Turn extends Stagetrait River extends Stagetrait Showdown extends Stage
trait StageOrder[Last <: Stage, Current <: Stage]
implicit val gotoFlop = new StageOrder[Preflop, Flop] {}implicit val gotoTurn = new StageOrder[Flop, Turn] {}implicit val gotoRiver = new StageOrder[Turn, River] {}
типы-фантомы
class Dealer[LastStage <: Stage](deck: List[Card]) {
type IsCurrentStageValid[T <: Stage] = StageOrder[LastStage, T]
def deal[CurrentStage <: Stage : IsCurrentStageValid](recipient: Actor): Dealer[CurrentStage] = {/*...*/}
}
dealer.deal[Flop](desk).deal[Turn](desk).deal[River](desk)dealer.deal[Flop](desk).deal[Flop](desk).deal[River](desk)
trait HandEvaluator {def compare(hand1: List[Card], hand2: List[Card]): Int
}
если бы мы могли проверять не только тип списка, но и его содержимое...
гетерогенный список
class Card
object Card {type NoCards = HNiltype HandCards = Card :: Card :: NoCardstype FlopCards = Card :: Card :: Card :: NoCardstype TurnCards = Card :: FlopCards
type RiverCards = Card :: TurnCards}
гетерогенный список и тип-объединение
trait HandEvaluator {
def compare[T: (HandCards ∨ (RiverCards :: HandCards))#λ] (hand1: T, hand2: T): Int
}
val handCards = new Card :: new Card :: HNilval communityCards = new Card :: new Card :: new Card :: new Card :: new Card :: HNil
handEvaluator.compare(handCards, handCards)handEvaluator.compare(communityCards :: handCards, communityCards :: handCards)handEvaluator.compare(handCards, communityCards)