Врожденный «порок вхождения»

WMix

герр M:)ller
Партнер клуба
все одно, странно это, вроде договорились Manager вернет interface типа Foo, если же ты хочешь больше, то это не Foo это любой Obj тк появляется возможность использовать совершенно другие методы (те что в FooImpl без самого Foo). каков смысл Interface Foo? в чем сила брат? сделай bind2 который вернет FooImpl. но это уже другая договоренность
 

Lionishy

Новичок
договорились Manager вернет interface типа Foo
Ключ в том, что FooManager возвращает Foo. И для клиента, который использует контракт FooManger, нет разницы между Foo и FooImpl, если FooImpl действительно подтип Foo, а FooManager -- абстракция.
Самое же интересное в том, что при вышеозначенных условиях для любого корректного утверждения M относительно FooManager::bind(): Foo, M будет корректно относительно FooManagerImpl::bind(): FooImpl.
Конечно FooManagerImpl должно оставаться подтипом FooManager, т.е. FooManagerImpl::bind(): FooImpl не нарушает аксиом FooManager (например ::bind() вообще не имеет побочных эффектов или идемпотентно). Таким образом, целостность абстракции FooManager зависит только от внутреннего содержания FooManagerImpl, а не от использования FooManagerImpl.

Писать вместо
PHP:
class SpecializedFactory implements AbstractFactory {
    public function create(): AbstractProduct
    {
        return $this->specialized_create();
    }

    public function specialized_create(): SpecializedProduct
    {
        <actual code goes here>
    }
}
вот так
PHP:
class SpecializedFactory implements AbstractFactory {
    public function create(): SpecializedProduct
    {
        <code goes here>
    }
}
удобней и ясней, позволяет работать прозрачно с подалгебрами.
 

fixxxer

К.О.
Партнер клуба
Можно считать, что переменной присваивается тот тип, который имеет выражение в правой части.
Ну, да, типа auto в C++11, но без compile-time проверки типов получается все равно лажа - фиг проверишь совместимость типов, проблема вылезет намного позже, чем хотелось бы. Так что в принципе мне понятно такое решение конкретно в php
 

fixxxer

К.О.
Партнер клуба
По факту, кстати, "благодаря" runtime only type checks, вот так сработает:

PHP:
class SpecializedFactory implements AbstractFactory {
    /** @return SpecializedProduct */
    public function create(): AbstractProduct
    {
        return new SpecializedProduct();
    }
}

function foo(SpecializedProduct $product) {
...
}

$product = $specializedFactory->create();
foo($product);
Но это жесть, конечно. :)
 

Lionishy

Новичок
Я в PHP не разбираюсь, но...

Когда я последний раз работал с PHP (а было это почти год назад), для упреждения нежелательного поведения в команде начали использовать некоторый инструмент статического анализа (кажется он прямо так и назывался PHP Analysiz). От всех type-hint отказались в пользу аннотаций вида PHPDoc.

Стоит оговориться однако, что транслятор может собирать информацию о типах для оптимизации кода, но для императивных языков такой оптимизатор представляется достаточно сложным, так что это тоже поле деятельности скорее для стороннего инструмента типа PHP Accelerator, который тоже мог бы ориентироваться на аннотации, а для скомпилированных единиц использовать механизм рефлексии над аннотациями. Например.

Это всё, конечно, в порядке размышлений во флуде.
 

WMix

герр M:)ller
Партнер клуба
добавлю еще один класс в кучу.
Код:
class Bar{
  public function do( FooManager $f ){
    return $f->bind();
  }
}
на этом уровне ничего не известно про конкретную имплементацию FooManager. и использовать FooImpl тоже невозможно. что написать в return-type?
 

Lionishy

Новичок
что написать в return-type?
В принципе, ничего мистического. Аппликация контравариантна.
Всё, что можно написать в return-type должно удовлетворять главному условию: для любого корректного P над return-type, Р должно быть корректно над Foo.

PHP:
interface Buz {
}

interface Foo extends Buz {
}

interface FooManager {
    public function bind(): Foo;
}

class Bar {
    public function do(FooManager $manager): Buz
    {
          return $manager->bind();
    }
}
 

WMix

герр M:)ller
Партнер клуба
PHP:
// поправил под конкретную проблему
// наследование наоборот
interface Foo {}

interface Buz extends Foo  {}

interface FooManager {
    public function bind(): Foo;
}

class Bar {
    public function do(FooManager $manager): /*Buz*/ Foo
    {
          return $manager->bind();
    }
}
ты не можешь гарантировать Buz, твой FooManager возвращает Foo
если брать твой пример,
Код:
interface Buz {}
interface Foo extends Buz {}
то ты наоборот уменьшил набор методов (вернув Buz), а не увеличил... можно было даже тип не менять (Foo является Buz)
 
Последнее редактирование:

WMix

герр M:)ller
Партнер клуба
а все понял
PHP:
interface Foo {}
interface Buz extends Foo  {}

interface FooManager {
    public function bind(): Foo;
}

interface BuzManager extends FooManager{
    /*public function bind(): Buz; // хочется нарушить сигнатуру*/
    public function bindBuz(): Buz // придеться добавить метод
}

class Bar {
    public function do(BuzManager $manager): Buz{
          return $manager->bindBuz();
    }
}
по логике, BuzManager является FooManagerом, и должен делать то что делает FooManager
 

AnrDaemon

Продвинутый новичок
@WMix, извини, но я не вижу за теоретическим примером практического применения.
 

Lionishy

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

Код:
Interface Output:
    + put(data: String): &Output
end

Interface FileOutput ensure Output:
    + put(data: String): &FileOutput
    + open(file_name: FileNameString): &FileOutput
    + close(): &FileOutput
end
Такое определение позволяет использовать на абстракции FileOutput беглый интерфейс без дополнительных методов, которые ничего бы не делали, а просто перебрасывали вызов.
 

WMix

герр M:)ller
Партнер клуба
а тебе обязательно знать, что FileOutput это Output, не соображу применение? ну или может есть другой пример?
 

AnrDaemon

Продвинутый новичок
@Lionishy, можешь смеяться, но я буквально месяц назад именно библиотеку ввода-вывода писал.
И об этот же пример споткнулся. Бред это. Либо не должно быть там никакого &FileOutput, либо интерфейс не продуман и нежизнеспособен.
 

Lionishy

Новичок
WMix,
идея в том, что можно использовать поток ввода в файл и так:
Код:
FileOutput (new FileOutput)
.open(file_name)
    .put(some_data)
    .put( (new DataAccessor).flush() )
.close();
и вот так:
Код:
Interface AbstractDataProvider
    + populate(output_stream: &Output): &AbstractDataProvider
end;

FileOutput file_output = new FileOutput;
    abstract_data_provider.populate(file_output);
file_output.close();
То есть, мы можем избавиться, в определённых ситуациях, от несущественных точек следования.

Можно привести и ещё более интересный пример, когда объект является неизменяемой коллекцией других объектов.
У меня нечто подобное в коде было реализовано примерно год назад, в прошлом августе.
Объект интерполятор является одновременно коллекцией узлов интерполяции, то есть копирует к себе сетку. Соответственно, интерполяторы разной степени гладкости возвращают ссылки на узлы, которые содержат всё больше и больше информации.

Примерно так это выглядело, хотя точно сейчас уже не скажу.
Код:
struct Node {
    double pos;
};

struct FirstOrderNode: public Node {
    double value;
};

struct SecondOrderNode: public FirstOrderNode {
    double derivative;
};

struct Interpolator {
    const Node& operator[](std::size_t) const throw(std::out_of_range) =0;
};

class FirstOrderInterpolation: public virtual Interpolator {
public:
    const FirstOrderNode& operator[](std::size_t i) const throw(std::out_of_range);
};

class SecondOrderInterpolation: public Interpolator {
    const SecondOrderNode& operator[](std::size_t i) const throw(std::out_of_range);
};
Ещё, кстати важно для Java, ковариантность распространяется на исключения. Наследник может выкидывать специфические для себя исключения, если эти исключения потомки тех, которые указаны в интерфейсе-родителе.

AnrDaemon,
есть разные возможности, почему у Вас что-то не сработало. Потоки ввода/вывода имеют сильные побочные эффекты, потому организация ковариантных интерфейсов -- непростое дело. Но не невозможное! А в использовании удобно и прозрачно.
Самая распространённая причина: Interface Output на самом деле является частичной реализацией ввода над абстракцией буфера. Многим кажется, что так можно сэкономить код (повторное использование). В итоге: FileOutput -- это фотоаппарат, к которому клейкой лентой прикрутили калькулятор.
Если Output -- абстракция, а не реализация, то проблем не должно возникать. Да, потоки имеют побочные эффекты и доказать в три строчки строго не получится. Но, опять же, есть масса задач, где можно уйти от побочных эффектов, тогда уже компилятор сам, без вашего участия, сможет определить, что интерфейс ковариантный.
Скажем в Haskell или ML, вам не придётся явно указывать возвращаемый тип, компилятор сам для Вас выведет FooImpl.
 

WMix

герр M:)ller
Партнер клуба
Lionishy, на самом деле идея понятная, отдал Output можно только "писáть", отдал FileOutput, можно модифицировать.
но, я только какбы понимаю, что можно всегда возвращать Output для .put(some_data). ну те вероятнее всего некая модель имеет FileOutput, его подкручивает и раздает дальше по обьектам усеченную версию типа Output. конечно после этого в единую цепочку не напишешь
Код:
FileOutput (new FileOutput)
  .open(file_name)
  .put(some_data)
  .put( (new DataAccessor).flush() )
/*  .close()*/;
ну или придется добавить метод, который на "put" вернет полноценный FileOutput, но проблемы в этом не вижу. а второй пример
Код:
Interface AbstractDataProvider
  + populate(output_stream: &Output): &AbstractDataProvider
end;

FileOutput file_output = new FileOutput;
abstract_data_provider.populate(file_output);
file_output.close();
работает и без переписывания сигнатуры.


стракты смешанные с классами мне было сложновато понять, давно это было.
 

Lionishy

Новичок
но проблемы в этом не вижу
Никакой проблемы и нет. Просто это естественное свойство. Если его добавить в структуру типов, то система становится более абстрактной, инструменты типизации становятся более выразительными. Но можно обойтись и без этого, конечно.
Можно, например, обойтись и без наследования реализаций. Я вот уж не вспомню, когда я именно реализации наследовал. Но в некоторых проектах, во многих, можно найти и смотанные скотчем сущности.
 

WMix

герр M:)ller
Партнер клуба
на семерке зато, можно сходить по взрослому )
PHP:
(function($s) {
    $this->foo($s);
})->call(
    new class() {
        private function foo($s){
            echo $s;
        }
    },
    'Hello World'
);
 
Сверху