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

в 6:47, , рубрики: generics, java, ненормальное программирование, типизация, Читальный зал, юмор

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

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

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

  void setЦарь(Человек<? extends РешилВсеЗадачи> новыйЦарь);
  Человек<?> 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 ВремяГода<X> {} 

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

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

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

"Эх, теперь времена года не 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 МеньшеЧем<Р> { }

"Смысл простой ими будем предметы помечать. И тогда, например, тип Размер<? super Размер3> предмет опишет любой предмет с размером от Размер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 КощеевоБарахло & МеньшеЧем<? super РазмерПредмета>
        > void больше(Предмет предмет, ПредметМеньше поменьше) {}    

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

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

class SortedList<BaseType, HeadType extends BaseType> { 
    private HeadType head; 
    private SortedList<BaseType, ?> tail; 
    public final int size; 

    private SortedList(HeadType head, SortedList<BaseType, ?> 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 КощеевоБарахло & МеньшеЧем<? super РазмерПредмета> 
        > 
         SortedList<КощеевоБарахло, Предмет> внутрь(
            Предмет предмет, 
            SortedList<КощеевоБарахло, ПредметМеньше> предметПоменьше
        ) { 
              return new SortedList<>(предмет, предметПоменьше); 
}

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

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

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

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

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

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

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

  • Вор был один

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

public static <
    I extends МожетПоднятьТяжесть 
       & ДалВзаймы<? extends ИмеетКлюч> 
       & Друг<? 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Царь(Человек<? extends РешилВсеЗадачи> новыйЦарь) {

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

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

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

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

java: incompatible types: 
Царь cannot be converted to Человек<? extends ЦарёвУказ1844.РешилВсеЗадачи>

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

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

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

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

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

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

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

Автор: Алексей Гришин

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js