Oberon space
General Category => Общий раздел => Тема начата: ilovb от Апрель 17, 2012, 09:55:10 am
-
Вчера весь вечер размышлял над этим сообщением:
http://oberspace.dyndns.org/index.php/topic,214.msg4710.html#msg4710 (http://oberspace.dyndns.org/index.php/topic,214.msg4710.html#msg4710)
А почему бы и нет? Мне нравится идея интерфейсов, в том виде как ее высказал Madzi.
Какие у них плюсы и минусы?
-
Вчера весь вечер размышлял над этим сообщением:
http://oberspace.dyndns.org/index.php/topic,214.msg4710.html#msg4710 (http://oberspace.dyndns.org/index.php/topic,214.msg4710.html#msg4710)
А почему бы и нет? Мне нравится идея интерфейсов, в том виде как ее высказал Madzi.
Какие у них плюсы и минусы?
Дык, все хорошо у них. Из минусов только усложнение компилятора и усложнение проверки типа. Для труЪ оберонщиков этого достаточно, чтобы не включать эту возможность в язык (при том, что можно и без них, правда кода ручками писать больше, но для труЪ оберонщиков это никогда не было проблемой).
-
Вчера весь вечер размышлял над этим сообщением:
http://oberspace.dyndns.org/index.php/topic,214.msg4710.html#msg4710 (http://oberspace.dyndns.org/index.php/topic,214.msg4710.html#msg4710)
А почему бы и нет? Мне нравится идея интерфейсов, в том виде как ее высказал Madzi.
Какие у них плюсы и минусы?
Дык, все хорошо у них. Из минусов только усложнение компилятора и усложнение проверки типа. Для труЪ оберонщиков этого достаточно, чтобы не включать эту возможность в язык (при том, что можно и без них, правда кода ручками писать больше, но для труЪ оберонщиков это никогда не было проблемой).
А вот в Go интерфейсы есть, а наследования интерфейсов нет. То есть никакого вообще нет. Ни множественного ни одинарного. Кода ручками в результате приходится писать сильно меньше.
-
А вот в Go интерфейсы есть, а наследования интерфейсов нет. То есть никакого вообще нет. Ни множественного ни одинарного. Кода ручками в результате приходится писать сильно меньше.
За счет чего меньше?
-
А вот в Go интерфейсы есть, а наследования интерфейсов нет. То есть никакого вообще нет. Ни множественного ни одинарного. Кода ручками в результате приходится писать сильно меньше.
За счет чего меньше?
За счет того, что нет комбинаторного взрыва интерфейсов. Допустим у нас есть функции a, b, c. Какие-то функции хотят от типа аргумента чтобы он умел только a, а кто-то хочет только b. А кто-то и a и b одновременно. Итого получаем следующие сочетания которые могут потребоваться: a, b, c, ab, ac, bc, abc.
Если мы попробуем это дело оформить в виде интерфейсов скажем в джаве, то у нас получится что-то вот такое:
interface A {void a();}
interface B {void b();}
interface C {void c();}
interface AB extends A, B {}
interface AC extends A, C {}
interface CB extends C, B {}
interface ABC extends AC, CB, AB {}
class MyType implements ABC {
void a() {}
void b() {}
void c() {}
}
И это всего лишь три функции. Также можно добавить сюда необходимость явного разрешение имен функций в некоторых ЯП (по моему, в том же C# это нужно будет сделать, но могу ошибаться).
Причем все эти интерфейсы будут болтаться где-то около объявления типа, а не там где он непосредственно нужен. То есть предусловие на функцию будет где-то за километр от самой функции, вообще в другой либе, другом пакете и так далее. И если, не дай бог, писатель этого типа забыл скажем написать интерфейс AB, то все. Придется резко извращаться. То есть в либе у нас так:
interface A {void a();}
interface B {void b();}
interface C {void c();}
interface AC extends A, C {}
interface CB extends C, B {}
interface ABC extends AC, CB {}
class MyType implements ABC {
void a() {}
void b() {}
void c() {}
}
А нам нужно такое:
void f1(ABC x) {x.a(); x.b(); x.c();}
void f2(AB xx) {x.a(); x.b();}
void run() {
MyType t = new MyType();
f1(t);
f2(t);
}
Предется лепить враппер:
interface AB extends A, B{}
class Wrapper extends MyType implements AB {}
Но это прокатит только если мы сами создаем объекты нужного типа, если же к нам уже приезжают готовые MyType объекты, то все хуже. Придется это дело агрегировать.
interface AB extends A, B{}
class Wrapper2 implements AB, ABC {
private MyType myobj;
Wrapper2 (MyType obj){myobj = obj;}
void a() {myobj.a();}
void b() {myobj.b();}
void c() {myobj.c();}
}
Ну, понятно что у меня тут в примерах код совсем игрушечный, отформатирован не по правилам (по правилам если оформить - строчек будет раза в три-четыре больше) и вообще наверняка я тут где-то зевнул и про всякие hashCode забыл и isEquals, clone и так далее. Так что можешь смело умножать на четыре объем кода. Кода которого в Go просто не будет. За не надобностью.
-
А вот в Go интерфейсы есть, а наследования интерфейсов нет.
Если взять это на вооружение там (http://oberspace.dyndns.org/index.php/topic,222.msg5066.html#msg5066), то вызов виртуальной процедуры будет чуток быстрее так как не надо будет делать лишнее приведение типов. Каждый "интерфейс" можно будет положить в отдельный массив как я сделал это раньше с делегатом. Пожалуй это даже лучше чем все предыдущие варианты...
-
Ну, понятно что у меня тут в примерах код совсем игрушечный, отформатирован не по правилам (по правилам если оформить - строчек будет раза в три-четыре больше) и вообще наверняка я тут где-то зевнул и про всякие hashCode забыл и isEquals, clone и так далее. Так что можешь смело умножать на четыре объем кода. Кода которого в Go просто не будет. За не надобностью.
ИМХО пример надуманный. Если я и сталкивался с такой проблемой, то в чужом коде, где без причины наследовали при первой возможности. Возможно в жабе все так запущенно, но в C++ нет необходимости писать враппер для требуемого комбинаторного интерфейса, если нужные интерфейсы уже отнаследованы/имплементированы. Кроме того, вообще ситуации, когда "на вход" требуется один объект с более чем одним интерфейсом крайне редки в моей практике (возможно это специфика С++, где соответсвующие решения принято делать через шаблоны). Зато случаи, когда "на вход" требуется более одного аргумента с разными интерфейсами, а в точке вызова это оказывается один объект - довольно часто.
struct Interface1;
struct Interface2;
void f(Interface1&, Interface2&); // обычно нет смысла требовать одного объекта с поддержкой и Interface1 и Interface2. Можно придумать, что это надо какому-нибудь контейнеру - но, как я говорил, в С++ это делается через шаблоны
...
f(*this, *this); // вот такое встречается
-
ИМХО пример надуманный. Если я и сталкивался с такой проблемой, то в чужом коде, где без причины наследовали при первой возможности.
Не, он не надуманный. Я сам пару раз в такое втыкался. Причем именно в С++. И по моим воспоминаниям там было еще больнее чем в жабе. Правда думаю из за того, что шаблоны в том проекте были под запретом.
Кроме того, вообще ситуации, когда "на вход" требуется один объект с более чем одним интерфейсом крайне редки в моей практике (возможно это специфика С++, где соответсвующие решения принято делать через шаблоны). Зато случаи, когда "на вход" требуется более одного аргумента с разными интерфейсами, а в точке вызова это оказывается один объект - довольно часто.
struct Interface1;
struct Interface2;
void f(Interface1&, Interface2&); // обычно нет смысла требовать одного объекта с поддержкой и Interface1 и Interface2. Можно придумать, что это надо какому-нибудь контейнеру - но, как я говорил, в С++ это делается через шаблоны
...
f(*this, *this); // вот такое встречается
Ну ты же понимаешь, что в Go не нужно и такого делать :-) То есть ничто не мешает потребовать от какого-то типа, чтобы он реализовывал нужное множество функций (причем сам интерфейс объявляется прямо перед функцией f которая на вход хочет именно это, то есть ровно там где это нужно) и все будет работать.
Подход в Go очень похож на typeclasses из Хаскеля и концепты C++ (которых пока нет).
// в каком-то библиотечном пакете
package foo
type Foo struct {}
func (self *Foo) a() {}
func (self *Foo) b() {}
func (self *Foo) c() {}
// а это уже в каком-то совершенно другом пакете который пишет совсем другой человек
package testit
import "foo"
type AB interface {
a()
b()
}
func f(ab AB) {
ab.a()
ab.b()
}
func doIt() {
obj := foo.Foo{}
f(obj)
}
-
За счет того, что нет комбинаторного взрыва интерфейсов. Допустим у нас есть функции a, b, c. Какие-то функции хотят от типа аргумента чтобы он умел только a, а кто-то хочет только b. А кто-то и a и b одновременно. Итого получаем следующие сочетания которые могут потребоваться: a, b, c, ab, ac, bc, abc.
... индусский код скипнут...
А почему нельзя напистаь так:
public interface A {
public void a();
}
public interface B {
public void b();
}
public class CAB implements A, B {
}
public class CA implements A {
}
-
За счет того, что нет комбинаторного взрыва интерфейсов. Допустим у нас есть функции a, b, c. Какие-то функции хотят от типа аргумента чтобы он умел только a, а кто-то хочет только b. А кто-то и a и b одновременно. Итого получаем следующие сочетания которые могут потребоваться: a, b, c, ab, ac, bc, abc.
... индусский код скипнут...
А почему нельзя напистаь так:
public interface A {
public void a();
}
public interface B {
public void b();
}
public class CAB implements A, B {
}
public class CA implements A {
}
А ты допиши в свой код функцию которая хочет от аргумента что бы он умел и A и B, но при этом ничего не знает про класс CAB.
-
А ты допиши в свой код функцию которая хочет от аргумента что бы он умел и A и B, но при этом ничего не знает про класс CAB.
public void foo (<T implements A. B> arg) {
}
-
А ты допиши в свой код функцию которая хочет от аргумента что бы он умел и A и B, но при этом ничего не знает про класс CAB.
public void foo (<T implements A. B> arg) {
}
А теперь без дженериков. С обобщенкой наваять много чего можно. Или ты предлагаешь в Оберон еще и обобщенку притащить?
-
Подход в Go очень похож на typeclasses из Хаскеля и концепты C++ (которых пока нет).
Интересно, как они это реализовали. Т.е., в моем (наивном) представлении в точке вызова такой функции для каждого типа передаваемого объекта должен генериться какой-то обобщенный прокси (виртуальная таблица) со ссылками на методы конкретного объекта. Насколько это эффективно?
-
А ты допиши в свой код функцию которая хочет от аргумента что бы он умел и A и B, но при этом ничего не знает про класс CAB.
public void foo (<T implements A. B> arg) {
}
Тем более что это не валидный с точки зрения java код.
-
Подход в Go очень похож на typeclasses из Хаскеля и концепты C++ (которых пока нет).
Интересно, как они это реализовали. Т.е., в моем (наивном) представлении в точке вызова такой функции для каждого типа передаваемого объекта должен генериться какой-то обобщенный прокси (виртуальная таблица) со ссылками на методы конкретного объекта. Насколько это эффективно?
По моему, там тот же самый дескриптор типа с таблицей виртуальных функций. Либо как в ObjC, где вызов метода идет по имени.
Подробней - надо в компилер смотреть.
-
Ок. Без дженериков придётся заводить ещё один интерфейс, наследующий интересующие
-
Ок. Без дженериков придётся заводить ещё один интерфейс, наследующий интересующие
Таким образом получаем тот самый "индокод" что был поскипан :-)
PS. А правильный код на java будет такой (c дженериками конечно)
public static <T extends A & B> void foo(T ab) {
ab.a();
ab.b();
}
-
Таким образом получаем тот самый "индокод" что был поскипан :-)
Да. Но это индокод для джавы, в которой есть дженерики и правильно делать на них. Для языка, где есть только интерфейсы это не будет индокодом :)
-
Таким образом получаем тот самый "индокод" что был поскипан :-)
Да. Но это индокод для джавы, в которой есть дженерики и правильно делать на них. Для языка, где есть только интерфейсы это не будет индокодом :)
Я просто взял первый попавшийся язык с "классическим" ООП. Можно было вообще псевдокодом.
Ну, можешь считать что то было на java 1.4 :-)
-
Подход в Go очень похож на typeclasses из Хаскеля и концепты C++ (которых пока нет).
Интересно, как они это реализовали.
А Go обладает JIT компилятором? Возможно функция f рассматривается как "шаблонная", но шаблон этот для JIT компилятора. JIT компилятор может накодить в машинных кодах столько копий f сколько разных типов подадут на вход...
-
А как обычно разруливается ситуация, когда совпадают имена методов в родительском классе и в прикручиваемом интерфейсе?
-
Подход в Go очень похож на typeclasses из Хаскеля и концепты C++ (которых пока нет).
Интересно, как они это реализовали.
А Go обладает JIT компилятором? Возможно функция f рассматривается как "шаблонная", но шаблон этот для JIT компилятора. JIT компилятор может накодить в машинных кодах столько копий f сколько разных типов подадут на вход...
Нет, там нет ни байткода ни jit'a ни виртуальной машины. Оно компилируется сразу в нативный код. Обычный исполняемый файл на выходе. В этом плане оно слабо отличимо от оберона (в реализации например XDS) или от C++ или ObjC.
-
А как обычно разруливается ситуация, когда совпадают имена методов в родительском классе и в прикручиваемом интерфейсе?
Все просто - там нет наследования, следовательно и нет родительсткого класса :-)
-
Т.е. интерфейсы рядом с обычной иерархией классов сосуществовать не могут?
Либо обычное наследование, либо интерфейсы?
-
Т.е. интерфейсы рядом с обычной иерархией классов сосуществовать не могут?
Либо обычное наследование, либо интерфейсы?
А зачем иерархия классов? Она обычно служит ровно одной цели (если у нас есть отдельно интерфейсы) - чтобы ручками не писать многократно один и тот же код. То есть повторное использование функционала. Но этого можно достичь и другими способами. Например так как это сделано (http://golang.org/doc/effective_go.html#embedding) в Go. Либо шаблонами, либо миксинами, либо еще как-то. Механизмов множество в разных языках.
-
Понял. Спасибо
-
Мне кажется что так лучше, чем в оберонах.
-
Вот тут показан возможный вариант CP с интерфейсами:
http://plas.fit.qut.edu.au/generics/mcp/imcpi_example.aspx (http://plas.fit.qut.edu.au/generics/mcp/imcpi_example.aspx)
-
2.7 Описания (definitions)
Описание (definition) — это синтаксический контракт 1, определяющий набор сигнатур методов. Описание D0 может быть уточнено новым описанием D1, которое наследует все методы, объявленные в D0. Описания и их методы видимы глобально. Объект может реализовать одно или несколько описаний, в этом случае он обязуется реализовать все методы определенные в этих описаниях.
DEFINITION Runnable;
PROCEDURE Start;
PROCEDURE Stop;
END Runnable;
DEFINITION Preemptable REFINES Runnable;
PROCEDURE Resume;
PROCEDURE Suspend;
END Preemptable;
TYPE
MyThread = OBJECT IMPLEMENTS Runnable;
PROCEDURE Start;
BEGIN .... END Start;
PROCEDURE Stop;
BEGIN .... END Stop;
END MyThread;
Ключевое слово IMPLEMENTS используется для указания описаний, реализованных объектным типом. Объетный тип может реализовать несколько описаний.
Описания можно понимать как дополнительные свойства, которыми должен обладать объект, но которые ортогональны иерархии типов объектов. Метод объекта может быть вызван через описание, в этом случае во время исполнения проверяется, действительно ли экземпляр объекта реализует описание, и только после этого вызывается метод; если экземпляр объекта не реализует описание, то возникает исключение (run-time exception).
PROCEDURE Execute(o: OBJECT; timeout: LONGINT);
BEGIN
Runnable(o).Start;
Delay(timeout);
Runnable(o).Stop;
END Execute;
http://maxandreev.narod.ru/oberon/ActiveOberonReport_RUS.html (http://maxandreev.narod.ru/oberon/ActiveOberonReport_RUS.html)