Темы



Сказка о типизации

d053ea7ae4b7d49f4febb9323351c297.png

В некотором царстве, в некотором государстве жил был царь. Как-то раз объявил царь всему народу — «Кто решит три моих задачки, тот сам сможет царём стать». И даже контракт метода опубликовал, всё честь по чести.

public interface ЦарёвУказ1844 {

  interface РешилВсеЗадачи {}

  void setЦарь(Человек новыйЦарь);
  Человек getЦарь();
}

Пришёл к царю кузнец, в разных делах дока, и говорит: «Давай свои задачки»

Царь руки потирает и говорит: «Ну что ж, изволь. Вот моя первая задачка»

Задача о боровиках

«Хочу в указах устанавливать разрешённый сезон сбора боровиков. Скажем, весной и осенью. Могу написать так:

enum ВремяГода { ВЕСНА, ОСЕНЬ, ЗИМА, ЛЕТО }

void идтиСобиратьБоровики(ВремяГода времяГода) {
     if (!(времяГода == ОСЕНЬ || времяГода == ВЕСНА)) 
       throw new RuntimeException("Запрещено!");
}

Но тогда ошибка появляется только в рантайме. А я хочу, чтобы ещё на этапе компиляции нельзя было пойти собирать боровики зимой, чтоб прямо в указе было ясно — когда можно, когда нельзя»

«Ну, — говорит кузнец, – сделай разные классы и два перегруженных метода, вот так как-то»

sealed interface ВремяГода {}

final class Зима implements ВремяГода {}
final class Весна implements ВремяГода {}
final class Лето implements ВремяГода {}
final class Осень implements ВремяГода {}

void идтиСобиратьБоровики(Весна весна) {}
void идтиСобиратьБоровики(Осень осень) {}

«Теперь если зимой попытаться пойти – не скомпилируется»

«А если сезоны менять – это мне придётся новые методы дописывать или эти удалять? Не хочу. Хочу один метод!»

«Ежели один метод, тогда можно маркерный интерфейс завести и временам года назначать»

interface МожноСобиратьБоровики {}

final class Зима implements ВремяГода {}
final class Весна implements ВремяГода, МожноСобиратьБоровики {}
final class Лето implements ВремяГода {}
final class Осень implements ВремяГода, МожноСобиратьБоровики {}

<ВГ extends ВремяГода & МожноСобиратьБоровики> 
  void идтиСобиратьБоровики(ВГ времяГода) {}

«Уже лучше, — говорит царь. – Только неудобно, что надо навешивать интерфейсы сезонам. Можно как-то так сделать, чтобы в одном месте перечень был? Редактировать проще»

«Изволь, — говорит кузнец, — можно и перечень, но тогда чутка посложнее выйдет»

static class ВремяГода {} 

interface Зима {} 
interface Весна {} 
interface Лето {} 
interface Осень {}

interface МожноСобиратьБоровики extends Зима, Весна {}

static void идтиСобиратьБоровики(ВремяГода времяГода) {}

«Эх, теперь времена года не sealed, – вздохнул царь»

«Ну ты уж определись, надёжа государь, тебе боровики или высокий штиль кода»

«Ладно, ладно, с этой задачей справился»

Задача о Кощее

«Вторая задачка такая. От царства кощеева поступил заказ на большое количество иголок, яиц, уток, зайцев и сундуков. Надо выдавать всё это пачками и в правильном порядке — сначала сундук, потом заяц, и в конце – игла. Но если я сделаю как-то так.»

enum КощеевоБарахло { Игла, Яйцо, Утка, Заяц, Сундук }

List<КощеевоБарахло> выдатьЗаказ() 

«То никто не помешает нерадивым работникам нарушить порядок и положить зайца в яйцо»

return Arrays.asList(Сундук, Яйцо, Заяц);

«А то и вообще – два сундука подсунуть. А хочется такой список возвращать, чтобы порядок на этапе компиляции гарантировался. А, ну и пропуски возможны. Если зайцы вдруг закончатся, можно уток сразу в сундуки.»

Задумался кузнец.

«Так, говорит, начнём потихоньку ковать. Чтобы на этапе компиляции сравнивать размеры, нам нужны для них отдельные типы, связанные в иерархию. Вот они»

interface Размер1 { } 
interface Размер2 extends Размер1 { } 
interface Размер3 extends Размер2 { } 
interface Размер4 extends Размер3 { } 
interface Размер5 extends Размер4 { }

«Теперь заведём вспомогательные интерфейсы»

interface Размер<Р> { }   
interface МеньшеЧем<Р> { }

«Смысл простой – ими будем предметы помечать. И тогда, например, тип Размер предмет опишет любой предмет с размером от Размер1 до Размер3. Теперь кощеево барахло пометим»

sealed interface КощеевоБарахло { }

final class Игла implements КощеевоБарахло, Размер<Размер1>, МеньшеЧем<Размер2> { }
final class Яйцо implements КощеевоБарахло, Размер<Размер2>, МеньшеЧем<Размер3> { }
final class Утка implements КощеевоБарахло, Размер<Размер3>, МеньшеЧем<Размер4> { }
final class Заяц implements КощеевоБарахло, Размер<Размер4>, МеньшеЧем<Размер5> { }
final class Сундук implements КощеевоБарахло, Размер<Размер5> { }

«Предметы удобнее парами сравнивать, да и твоим работникам сподручнее один предмет в другой положить, а не всеми сразу жонглировать. Поэтому сделаем такой метод, который принимает два предмета, и только таких, где второй – меньше первого.»

public <РазмерПредмета,
        Предмет extends КощеевоБарахло & Размер<РазмерПредмета>,
        ПредметМеньше extends КощеевоБарахло & МеньшеЧем
        > void больше(Предмет предмет, ПредметМеньше поменьше) {}    

public static void main(String[] args) {
  // больше(new Утка(), new Заяц()); // ошибка компиляции
  // больше(new Заяц(), new Заяц()); // ошибка компиляции
   больше(new Заяц(), new Утка());  // ок
}

«А теперь делаем связный список, но такой, чтобы верхний элемент мог быть только больше следующего элемента.»

class SortedList { 
    private HeadType head; 
    private SortedList tail; 
    public final int size; 

    private SortedList(HeadType head, SortedList tail) { 
        this.head = head; 
        this.tail = tail; 
        this.size = 1 + (tail == null ? 0 : tail.size); 
    } 

    public BaseType get(int idx) { 
        assert idx >= 0 && idx < size; 
        return getUnchecked(idx); 
    } 

    private BaseType getUnchecked(int idx) { 
        return idx == 0 ? head : tail.getUnchecked(idx - 1); 
    } 
}

«А чего это ты на англицкий переключился?»

«Ваше величество, не говори под руку, а то эксепшном зашибёт. Дальше смотри что будет»

public <РазмерПредмета, 
        Предмет extends КощеевоБарахло & Размер<РазмерПредмета>
        > 
         SortedList<КощеевоБарахло, Предмет> положить(Предмет предмет) { 
             return new SortedList<>(предмет, null); 
}

public <РазмерПредмета, 
        Предмет extends КощеевоБарахло & Размер<РазмерПредмета>, 
        ПредметМеньше extends КощеевоБарахло & МеньшеЧем 
        > 
         SortedList<КощеевоБарахло, Предмет> внутрь(
            Предмет предмет, 
            SortedList<КощеевоБарахло, ПредметМеньше> предметПоменьше
        ) { 
              return new SortedList<>(предмет, предметПоменьше); 
}

SortedList<КощеевоБарахло, ?> выдатьЗаказ() { 
    var игла = new Игла(); 
    var заяц = new Заяц(); 
    var утка = new Утка(); 
    var сундук = new Сундук(); 
    var яйцо = new Яйцо(); 

    return внутрь(сундук, внутрь(заяц, внутрь(утка, внутрь(яйцо, положить(игла))))); 
}

«Вот и всё. Теперь только в таком порядке и можно отдавать заказ. А если чего не хватает – можно пропустить»

«Ладно, кузнец, тут ты тоже справился. Вот тебе последняя задача.»

Задача о перстне

«Кто-то стащил у меня перстень. И никак не могу понять – кто. Что нам известно:

  • Перстень тяжёлый, трансурановый

  • Вор был один

  • Лекарь и Писарь знают, где перстень лежать должен

  • Ключ от этих палат есть у Писаря и у Попа

  • Лекарь и Писарь щуплые, слабые

  • Конюх должен Лекарю, Поп должен Князю, а Князь должен Писарю

  • Лекарь дружит с Князем, Конюх дружит с Попом

Хочу знать, кто подлец, и чтобы ещё на этапе компиляции – так точно не отвертятся.»

«Государь-батюшка, а не проще ли Prolog для этой цели взять?»

«Ты делай давай. А не можешь – так пошёл вон с глаз моих.»

Усмехнулся кузнец, и давай размышлять.

«Так, сначала признаки раскидаем. Кто, кому, куда и как.»

interface МожетПоднятьТяжесть { }
interface ИмеетКлюч { }
interface Друг { }
interface ЗнаетГдеПерстень { }
interface ДалВзаймы { }
interface Должен { }

class Конюх implements Должен<Лекарь>, МожетПоднятьТяжесть, Друг<Поп> { }
class Лекарь implements ЗнаетГдеПерстень, Друг<Князь>, ДалВзаймы<Конюх> { }
class Поп implements Друг<Конюх>, Должен<Князь>, ИмеетКлюч, МожетПоднятьТяжесть { }
class Писарь implements ИмеетКлюч, ЗнаетГдеПерстень, ДалВзаймы<Князь> { }
class Князь implements Друг<Лекарь>, МожетПоднятьТяжесть, Должен<Писарь>, ДалВзаймы<Поп> { }

«Теперь будем составлять метод, который примет только одного из них – преступника. Но вариантов несколько, а объединений типов в Java пока не завезли, поэтому хочешь не хочешь, а придётся несколько методов делать. Понял?»

«Понял, понял. А компилятор не запутается, ежели generic тип будет? Наругает ещё, что из-за erasure типы одинаковые.»

«Наругает, коли не схитрим. А мы схитрим. Начнём с очевидной версии. Тот преступник, кто и сильный, и знает где перстень, и ключ имеет.»

public  
  void преступник(I вор, Double x) {}

«А последний аргумент зачем?»

«А вот потом увидишь. Забиваем всех пятерых, пробуем скомпилировать»

преступник(князь, null); 
преступник(поп, null); 
преступник(писарь, null); 
преступник(лекарь, null); 
преступник(конюх, null);

«Все строчки с ошибками, а значит версия наша провалилась. Но мы её пока оставим, чтобы честно было. Проверим другую версию: что если вынес кто-то сильный, а про перстень у друга узнал и ключ у него же взял?»

public static <
        H extends ИмеетКлюч & ЗнаетГдеПерстень,
        I extends МожетПоднятьТяжесть & Друг
    > void преступник(I вор, Short x) {}

«Ах вот зачем тебе второй аргумент, это чтобы компилятор видел у них разные сигнатуры и не донимал.»

«А ты шаришь. Но по прежнему все строчки у нас не компилируются.

Тогда другое предположение. Преступник сам сильный, друг ему про перстень рассказал, а ключ он у должника выклянчил»

public static <
    I extends МожетПоднятьТяжесть 
       & ДалВзаймы 
       & Друг
> void преступник(I вор, Integer i) {}

«Ага, а вот и строчка компилирующаяся — преступник(князь, null). Значит перстень князь стащил. От лекаря про перстень узнал, а у попа ключ взял.»

«Вот я так и знал, что он, подлец, за всем стоит. То-то смотрю – ходит довольный, аж светится!»

Награда

«Ну что ж, кузнец, три задачи ты честно решил. Интерфейс я тебе присваиваю с честью»

public class Кузнец implements Человек<ЦарёвУказ1844.РешилВсеЗадачи> {}

«Интерфейс это хорошо, а теперь давай место на троне уступай»

«Пожалуйста, пожалуйста, вот вам метод, вызывайте»

ЦарёвУказ1844 указ1844 = new ЦарёвУказ1844Impl(); 
Кузнец кузнец = new Кузнец(); 
указ1844.setЦарь(кузнец);

Exception in thread "main" java.lang.RuntimeException: Не по сеньке шапка 
	at ЦарёвУказ1844Impl.setЦарь(ЦарёвУказ1844Impl.java:15) 
	at Награда.main(Награда.java:12)

«Это ещё что, — нахмурился кузнец»

«А это детали реализации, родной. Я ж не совсем с ума сошёл, чтобы кому попало царство своё отдавать. Ну и подстраховался немножко, предусловия разумные поставил»

@Override
public void setЦарь(Человек новыйЦарь) {

    if (!((Человек)новыйЦарь instanceof Царь)) {
        throw new RuntimeException("Не по сеньке шапка");
    }

    царь = новыйЦарь;
}

«Что-то я не понял –, а как ты вообще этот метод с собой вызовешь? Ты же задачи не решил, у тебя интерфейс нереализован!»

указ1844.setЦарь(царь);

java: incompatible types: 
Царь cannot be converted to Человек

«Вот ты смешной кузнец, — хохочет царь, — а reflection на что?»

ЦарёвУказ1844.class
    .getDeclaredMethod("setЦарь", Человек.class)
    .invoke(указ1844, царь);

Кузнец кое-как глаза обратно впучил, мозг в кучу собрал, и сказал, головой качая:

«Нехорошо это, царь-батюшка, контракт не соблюдать.»

«Ты поучи меня тут ещё, — рассердился царь, — вали, пока цел»

Кузнец плечами пожал да и пошёл куда глаза глядят. Говорят, на Кипре его видели, в стартапе куёт криптоподковы, не тужит. А царь сам запутался в своих контрактах да и наступил на exception. Ходит теперь с фингалом, смурной.

Вот и сказочке конец, а кто слушал — молодец.

© Habrahabr.ru