Способ именования конструкторов и деструкторов
От: x-code  
Дата: 07.04.11 09:46
Оценка:
Как думаете, какой способ предпочтительнее с точки зрения дизайна языка программирования?
Из того, что попадалось:
1. имя класса
class MyClass
{
MyClass();
~MyClass();
};

2. ключевое слово this или другое, используемое также для каких-либо других целей
class MyClass
{
this();
~this();
};


3. отдельные ключевые слова = функции с предопределенными именами
class MyClass
{
ctor();
dtor();
};

Учитывается понятность и внешний вид кода, отсутствие неоднозначностей, удобство рефакторинга и работы IDE и любые другие факторы, которые могут иметь место.
Re: Ctor/dtor
От: Qbit86 Кипр
Дата: 07.04.11 09:48
Оценка: +1
Здравствуйте, x-code, Вы писали:

XC>3. отдельные ключевые слова = функции с предопределенными именами

XC>class MyClass
XC>{
XC> ctor();
XC> dtor();
XC>};

Мне последний вариант больше нравится.
Глаза у меня добрые, но рубашка — смирительная!
Re: Способ именования конструкторов и деструкторов
От: Anpek  
Дата: 07.04.11 09:49
Оценка: -1
Здравствуйте, x-code, Вы писали:

По-моему, первый. Констурктор же вызывается не изнутри класса, а откуда-то из другого места, поэтому this там будет неоднозначно воприниматься. А ctor — одинковое название для любого класса — то же визуально плохо, так как набор этих самых ctor запутает. А в первом собсобе сразу видно кто для чего и зачем
Re[2]: Определение, вызов
От: Qbit86 Кипр
Дата: 07.04.11 09:51
Оценка:
Здравствуйте, Anpek, Вы писали:

A>По-моему, первый. Констурктор же вызывается не изнутри класса, а откуда-то из другого места, поэтому this там будет неоднозначно воприниматься. А ctor — одинковое название для любого класса — то же визуально плохо, так как набор этих самых ctor запутает. А в первом собсобе сразу видно кто для чего и зачем


Топикстартер предоставил синтаксис определения, а не вызова. Вызов, я так полагаю, по прежнему будет new MyClass(params).
Глаза у меня добрые, но рубашка — смирительная!
Re[3]: Определение, вызов
От: x-code  
Дата: 07.04.11 09:52
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Топикстартер предоставил синтаксис определения, а не вызова. Вызов, я так полагаю, по прежнему будет new MyClass(params).


Да, именно так.
Re: Способ именования конструкторов и деструкторов
От: adontz Грузия http://adontz.wordpress.com/
Дата: 07.04.11 09:53
Оценка: +4
Здравствуйте, x-code, Вы писали:

Голосую за №3.
A journey of a thousand miles must begin with a single step © Lau Tsu
Re: Способ именования конструкторов и деструкторов
От: Temoto  
Дата: 07.04.11 10:52
Оценка:
XC>Как думаете, какой способ предпочтительнее с точки зрения дизайна языка программирования?

XC>Учитывается понятность и внешний вид кода, отсутствие неоднозначностей, удобство рефакторинга и работы IDE и любые другие факторы, которые могут иметь место.


Голосую за №3: предопределённые имена. Обязательно НЕ ключевые, то есть чтоб их можно было использовать в названиях переменных. Хотя последняя хотелка, конечно, сильно зависит от остальных дизайн решений.

Только слова надо поаккуратнее выбрать. В частности, чем единственное чем плохи constructor/destructor — они не глаголы, хотя имена методов по логике должны быть глаголами. create/destroy например.
Re: Способ именования конструкторов и деструкторов
От: Abyx Россия  
Дата: 07.04.11 10:57
Оценка: +3
Здравствуйте, x-code, Вы писали:

[...]

2й или 3й вариант ничем не отличаются, "~this" это такая же лексема что и "dtor". Голосую за любой из них.

1й вариант плох тем что надо повторять компилятору то что он уже знает.
При переименовании класса надо менять имя в бОльшем количестве мест.
Также если код генерируется каким-то метакодом, это делает невозможным многие вещи, например
DEFINE_CLASS_WITH_UNIQUE_NAME
{
    ...
    тут нельзя написать конструктор потому что мы не знаем имя класса
};

или
DEFINE_CLASS(Foo)
{
    ...
    DEFINE_CTOR(Foo) // тут опять надо зачем-то писать имя класса 
    {
        ...
    }
};
In Zen We Trust
Re[2]: Не глаголы
От: Qbit86 Кипр
Дата: 07.04.11 10:58
Оценка:
Здравствуйте, Temoto, Вы писали:

T>...чем единственное чем плохи constructor/destructor — они не глаголы...


Это имеет значение только для вызова. А при вызове существительные «ctor» и «dtor» не используется.
Глаза у меня добрые, но рубашка — смирительная!
Re: Способ именования конструкторов и деструкторов
От: DarkGray Россия http://blog.metatech.ru/post/ogni-razrabotki.aspx
Дата: 07.04.11 11:07
Оценка: :)
XC>Учитывается понятность и внешний вид кода, отсутствие неоднозначностей, удобство рефакторинга и работы IDE и любые другие факторы, которые могут иметь место.

дублирование информации
устранение дублирование информации — уменьшает накладные расходы при изменении кода (поменять название класса, сделать дубль класса с чуть другим функционалом и т.д.)
имена this/~this, ctor/dtor по сравнению с MyClass уменьшают дублирование информации

конфликт имен (полнота)
полнота(возможность всех вариантов) важна при автоматической генерации кода, интеграции, версионности и т.д., т.е. в тех случаях, когда затруднена возможность произвольного задания имен.
если для объявления конструктора/деструктора используется тот же самый синтаксис, что и для объявления методов, то возникает конфликт имен: есть сложности объявления функций с теми именами, которые зарезервированы для конструктора/деструктора
вариант MyClass делает невозможным объявление метода с именем MyClass.
вариант this запрещает методы this, но для языков где this зарезервированное слово(а не просто контекстно-зависимое), такие методы и так не возможны
вариант ctor/dtor запрещает методы ctor/dtor
конфликт может решаться устранением неоднозначности другими способами:
например(общий для всех трех вариантов, для статически-типизированных языков): метод — отличается добавление объявление результата. MyClass() — конструктор, а void MyClass() — метод.
например(общий для всех трех вариантов): метод — отличается добавлением @ перед названием. MyClass() — конструктор, а @MyClass() — метод.
но при беглом просмотре и для новичков, неоднозначность все равно может возникать

чтение кода с любого места в произвольном направлении (обычно конфликтует с критерием дублирование информации)
полезно при беглом чтении кода, анализе, частичном автоматизированном разборе и т.д.
конструктор/деструктор могут быть достаточно отдалены от объявления самого класса, а для названий this/~this, ctor/dtor вообще нет никаких подсказок о каких конструкторах/деструкторах идет речь, что требует либо аккуратный скроллинг к объявлению класса (при беглом скроллинге можно перескочить на чужое объявление), либо подсказок от IDE. с методами проблема менее критична, т.к. название метода уже часто несет большую часть информации — что это за метод, и название класса необходимо только когда метод реально полиморфный.


зы
порядок критериев по уменьшению времени, сколько они "используются":
чтение кода с любого места в произвольном направлении (бегло читать код приходиться больше всего)
дублирование информации (потом время уходит на рефакторинг)
конфликт имен

поэтому мне больше нравится вариант: MyClass, но с возможностью, если сильно хочется, объявить метод с тем же именем
Re[3]: Не глаголы
От: Temoto  
Дата: 07.04.11 11:13
Оценка:
T>>...чем единственное чем плохи constructor/destructor — они не глаголы...

Q>Это имеет значение только для вызова. А при вызове существительные «ctor» и «dtor» не используется.


Смотря какая грамматика.

Если рассматривать

"constructor" "(" arguments... ")"

, где constructor — ключевое слово, наравне с class/switch/return, то да, вы правы. А если рассматривать

id "(" arguments... ")"

, где "constructor" это частный случай идентификатора и оно не является никаким ключевым словом или специальной грамматической конструкцией, то нет, имя должно подчиняться тем же правилам, что и у остальных методов.
Re[2]: Способ именования конструкторов и деструкторов
От: Abyx Россия  
Дата: 07.04.11 11:16
Оценка:
Здравствуйте, DarkGray, Вы писали:

DG>чтение кода с любого места в произвольном направлении (обычно конфликтует с критерием дублирование информации)

1. ИДЕ должна показывать текущий контекст
2. та же проблема с методами — смотришь на метод, и не видишь чей он.
In Zen We Trust
Re: Способ именования конструкторов и деструкторов
От: FR  
Дата: 07.04.11 11:24
Оценка: +2
Здравствуйте, x-code, Вы писали:

XC>Учитывается понятность и внешний вид кода, отсутствие неоднозначностей, удобство рефакторинга и работы IDE и любые другие факторы, которые могут иметь место.


Из практического использования, хоть и очень небольшого языка D, второй вариант удобней чем первый.
Re[4]: Определение, вызов
От: blackhearted Украина  
Дата: 07.04.11 13:06
Оценка:
Здравствуйте, x-code, Вы писали:

XC>Здравствуйте, Qbit86, Вы писали:


Q>>Топикстартер предоставил синтаксис определения, а не вызова. Вызов, я так полагаю, по прежнему будет new MyClass(params).


XC>Да, именно так.


а смысл писать вдобавок методы типа ctor()/this() , если всё равно будет вызываться new MyClass() ???
Re[2]: Способ именования конструкторов и деструкторов
От: blackhearted Украина  
Дата: 07.04.11 13:08
Оценка:
Здравствуйте, Abyx, Вы писали:

A>Здравствуйте, x-code, Вы писали:


A>[...]


A>2й или 3й вариант ничем не отличаются, "~this" это такая же лексема что и "dtor". Голосую за любой из них.


A>1й вариант плох тем что надо повторять компилятору то что он уже знает.

A>При переименовании класса надо менять имя в бОльшем количестве мест.
A>Также если код генерируется каким-то метакодом, это делает невозможным многие вещи, например
A>
A>DEFINE_CLASS_WITH_UNIQUE_NAME
A>{
A>    ...
A>    тут нельзя написать конструктор потому что мы не знаем имя класса
A>};
A>

A>или
A>
A>DEFINE_CLASS(Foo)
A>{
A>    ...
A>    DEFINE_CTOR(Foo) // тут опять надо зачем-то писать имя класса 
A>    {
A>        ...
A>    }
A>};
A>


Это еще почему? Что за генерация такая, что имя класса известно, а конструктор нельзя объявить?
А как тогда планируется в вызывающем коде создавать оюъект методом ctor() ?
Re[5]: Определение, вызов
От: Qbit86 Кипр
Дата: 07.04.11 13:09
Оценка: +1
Здравствуйте, blackhearted, Вы писали:

Q>>>Топикстартер предоставил синтаксис определения, а не вызова. Вызов, я так полагаю, по прежнему будет new MyClass(params).


B>а смысл писать вдобавок методы типа ctor()/this() , если всё равно будет вызываться new MyClass() ???


Чтобы избежать избыточности (и всех её негативных последствий) в определении класса.
Глаза у меня добрые, но рубашка — смирительная!
Re[3]: Способ именования конструкторов и деструкторов
От: blackhearted Украина  
Дата: 07.04.11 13:10
Оценка:
Здравствуйте, Abyx, Вы писали:

A>Здравствуйте, DarkGray, Вы писали:


DG>>чтение кода с любого места в произвольном направлении (обычно конфликтует с критерием дублирование информации)

A>1. ИДЕ должна показывать текущий контекст
A>2. та же проблема с методами — смотришь на метод, и не видишь чей он.

всегда только IDE?
Можно вообще уйти от фиксированных имён и аннотировать методы.
Что-то типа @Constructor. Тогда без IDE будет полный пипец. А такие ситуации довольно часты.
Re[6]: Определение, вызов
От: blackhearted Украина  
Дата: 07.04.11 13:12
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Здравствуйте, blackhearted, Вы писали:


Q>>>>Топикстартер предоставил синтаксис определения, а не вызова. Вызов, я так полагаю, по прежнему будет new MyClass(params).


B>>а смысл писать вдобавок методы типа ctor()/this() , если всё равно будет вызываться new MyClass() ???


Q>Чтобы избежать избыточности (и всех её негативных последствий) в определении класса.


и какие негативные последствия у

public class A
{
   public A();
}


по сравнению с


public class A
{
   public ctor();
}


?
Re[3]: Способ именования конструкторов и деструкторов
От: Qbit86 Кипр
Дата: 07.04.11 13:14
Оценка:
Здравствуйте, blackhearted, Вы писали:

B>Это еще почему? Что за генерация такая, что имя класса известно, а конструктор нельзя объявить?


Имя класса как раз может быть неизвестно.

B>А как тогда планируется в вызывающем коде создавать оюъект методом ctor()?


Это уже задача вызывающего кода. Он независим от библиотечного. Объявление класса и создание его экземпляра — это слабосвязанные вещи, они могут быть разделены в пространстве и времени. Создание происходит не вызовом ctor(params), а вызовом SomeGeneratedClass(params).
Глаза у меня добрые, но рубашка — смирительная!
Re[3]: Способ именования конструкторов и деструкторов
От: Abyx Россия  
Дата: 07.04.11 13:17
Оценка:
Здравствуйте, blackhearted, Вы писали:

[]

не оверквотьте.
In Zen We Trust
Re[7]: Определение, вызов
От: Qbit86 Кипр
Дата: 07.04.11 13:19
Оценка:
Здравствуйте, blackhearted, Вы писали:

Q>>Чтобы избежать избыточности (и всех её негативных последствий) в определении класса.

B>и какие негативные последствия у

B>
B>public class A
B>{
B>   public A();
B>}
B>


B>по сравнению с


B>
B>public class A
B>{
B>   public ctor();
B>}
B>

B>? :???:

Примерно те же, что и у отсутствия нормальной формы в базе данных :) Например, аномалия обновления — изменение имени класса в его определении приведёт к ошибке компиляции класса, так как имена конструкторов станут рассогласованы.
Глаза у меня добрые, но рубашка — смирительная!
Re[7]: Определение, вызов
От: jazzer Россия Skype: enerjazzer
Дата: 07.04.11 13:21
Оценка: :))) :)))
Здравствуйте, blackhearted, Вы писали:

B>и какие негативные последствия у


B>
B>public class A
B>{
B>   public A();
B>}
B>


B>по сравнению с



B>
B>public class A
B>{
B>   public ctor();
B>}
B>


B>?


Проще имя класса поменять.
Актуально в редакторах без Search/Replace (например, mspaint).
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[8]: Определение, вызов
От: blackhearted Украина  
Дата: 07.04.11 13:32
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Здравствуйте, blackhearted, Вы писали:


B>>и какие негативные последствия у


B>>
B>>public class A
B>>{
B>>   public A();
B>>}
B>>


B>>по сравнению с



B>>
B>>public class A
B>>{
B>>   public ctor();
B>>}
B>>


B>>?


J>Проще имя класса поменять.

J>Актуально в редакторах без Search/Replace (например, mspaint).

исходниик в пейнте редактировать?
Браво.
ту же все рассказывают, что IDE всё подсветит, покажет.
Re[9]: Определение, вызов
От: Anpek  
Дата: 07.04.11 13:34
Оценка: +1 :)
Здравствуйте, blackhearted, Вы писали:

B>исходниик в пейнте редактировать?

B>Браво.
B>ту же все рассказывают, что IDE всё подсветит, покажет.

Отредактировать исходник в пейнте — фигня вопрос. Покажите мне лучше компилятор, который на вход картинки принимает
Re[10]: Определение, вызов
От: Temoto  
Дата: 07.04.11 13:52
Оценка: :)
B>>исходниик в пейнте редактировать?
B>>Браво.
B>>ту же все рассказывают, что IDE всё подсветит, покажет.

A>Отредактировать исходник в пейнте — фигня вопрос. Покажите мне лучше компилятор, который на вход картинки принимает


http://www.dangermouse.net/esoteric/piet.html
Re: Способ именования конструкторов и деструкторов
От: Lloyd Россия  
Дата: 07.04.11 14:19
Оценка:
Здравствуйте, x-code, Вы писали:

XC>Учитывается понятность и внешний вид кода, отсутствие неоднозначностей, удобство рефакторинга и работы IDE и любые другие факторы, которые могут иметь место.


Первый. Конструктор — это просто функция, создающая объект. Почему для нее нужен отдельный способ именования?

Вот что действительно не нравится — так это синтаксис вызова одного конструктора из другого, через this(...). Constraint new в дженениках тоже не нравится. Все в разнобой как-то.
Re: Способ именования конструкторов и деструкторов
От: x-code  
Дата: 07.04.11 19:51
Оценка:
Здравствуйте, x-code, Вы писали:

Мой вариант, критикуйте
Каждый метод предваряется некоторым ключевым словом: def — для впервые объявленных функций, redef — для перегруженных, можно подобрать ключевые слова для виртуальных и переопределенных (virtual, override) и т.п. Примерно так:
def Func1() int {}
redef Func2(int x, y) char {}

ctor и dtor — ключевые слова такого же типа для конструкторов и деструкторов.
Если любой метод обязан иметь имя, то методы ctor и dtor могут как иметь произвольные имена, так и не иметь их. Имена, если они есть, подчиняются общим правилам именования методов. Опциональная возможность именования конструкторов и деструкторов дает дополнительную возможность самодокументирования кода. Варинат с именами:
ctor InitDefault() {}
ctor InitFromStr(string s) {}

или по-старинке:
ctor() {}
ctor(string s) {}

если имя есть, то на функцию-конструктор можно сослаться, можно вызывать ее для переинициализации объекта, взять ее адрес, можно вызвать один конструктор из другого и т.д.

В вызывающем коде инициализация классическая
new MyClass();
new MyClass("my text");


Имя класса освобождается от дополнительной нагрузки. Если предположить, что мы имеем дело с языком, в котором каждая программная сущность — объект, то снимается неоднозначность: имя класса — уникальный идентификатор объекта класса, имя конструктора — уникальный идентификатор объекта-функции.
Re[2]: Способ именования конструкторов и деструкторов
От: Кодёнок  
Дата: 08.04.11 06:25
Оценка: +1
Здравствуйте, x-code, Вы писали:

XC>new MyClass("my text");[/code]


Если уж на то пошло, то оператор new должен иметь вид

new<MyClass>("my text");


а destroy должен требовать скобок

[/c]
destroy(foo);
[/c]
Re[3]: Способ именования конструкторов и деструкторов
От: x-code  
Дата: 08.04.11 08:05
Оценка:
Здравствуйте, Кодёнок, Вы писали:

Кё>Если уж на то пошло, то оператор new должен иметь вид


Кё>
Кё>new<MyClass>("my text");
Кё>


Кё>а destroy должен требовать скобок


Кё>
Кё>destroy(foo);
Кё>


А почему так?
Re: Способ именования конструкторов и деструкторов
От: maykie Россия  
Дата: 08.04.11 08:23
Оценка:
Здравствуйте, x-code, Вы писали:




XC>3. отдельные ключевые слова = функции с предопределенными именами

XC>class MyClass
XC>{
XC> ctor();
XC> dtor();
XC>};

XC>Учитывается понятность и внешний вид кода, отсутствие неоднозначностей, удобство рефакторинга и работы IDE и любые другие факторы, которые могут иметь место.


Третий. Первый противоречит DRY, второй из-за того что если в языке всё передаётся по ссылке и разрешена перегрузка операторов, то будет неоднозначность с operator():
class Callback<X>{
operator (X x) {fp(x)};
this(FuncPointer fp)
{
    this.fp = fp;
}

this(Callback<X> other)
{
     this(other.fp); //вызов оператора() или конструктора?
}

в третьем неоднозначностей не будет.
Re[2]: Способ именования конструкторов и деструкторов
От: hardcase Пират http://nemerle.org
Дата: 08.04.11 13:43
Оценка:
Здравствуйте, maykie, Вы писали:

M>Третий. Первый противоречит DRY, второй из-за того что если в языке всё передаётся по ссылке и разрешена перегрузка операторов, то будет неоднозначность с operator():


При условии что в языке есть operator()
/* иЗвиНите зА неРовнЫй поЧерК */
Re[2]: Способ именования конструкторов и деструкторов
От: maxkar  
Дата: 08.04.11 18:41
Оценка: 5 (1)
Здравствуйте, x-code, Вы писали:

XC>Мой вариант, критикуйте

Без проблем
Модельный пример — прямоугольник. Публичное API ниже
class Rectangle {
  public function getLeft() : int;
  public function getRight() : int;
  public function getTop() : int;
  public function getBottom() : int;
  public function getWidth() : int;
  public function getHeight() : int;
  public function expandWidth(amount : int) : Rectangle; // Вовзращает прямоугольник с шириной, увеличенной на amount
}

Конструкторы специально не указаны. Мы их писать будем

Первая же претензия. Не указано, какую задачу решает конструктор. В mainstream (c++/java/c#) определение конструктора на самом деле определяет две сущности:

  1. Instance initialization method — инициализация экземпляра. Выполняет следующие действия:
    • Инициализация полей класса по-умолчанию
    • Выполнение Instance initialization method предка (предков)
    • Выполнение произвольного User-defined кода
  2. Instance creation method — создание экземпляра. Выполняет следующие действия:
    • Выделяет память под экземпляр класса
    • Инициализирует таблицу виртуальных методов
    • Вызывает initialization method
Вызов Instance initialization и instance creation выглядит по-разному. Вообще, вызов других instance intialization доступен только в друих методах инициализации. Ну а вызов instance creation — это new <initialization method>(args). Разница в том, что instance initialization ничего не возвращает, а instance construction в результате возвращает новый объект. Это потребуется дальше.

XC>Каждый метод предваряется некоторым ключевым словом: (skip)

A!!! Блин, они гвоздями к объекту прибиты? Ну неудобно это. И я против сегрегации методов. Возьмем тот же прямоугольник. Не, мало. Массив прямоугольников! rectangles. И отконвертируем их в массив высот этих прямоугольников. Напишем, Array.map(rectangles, ???). И что туда писать? Анонимную функцию? Или Rectangle.getHeight? Кстати, кто такой тогда будет Rectangle.getHeight?
Зато если написать внешнюю функцию RectangleUtils.getWidth(rectangle : Rectangle) : int, ее можно туда передать.

Второй пример на то же самое. Теперь мы будем не ширину получать, а expand'ить один прямоугольник на массив ширин (и получать массив прямоугольников). В этом случае, казалось бы, все хорошо:
Array.map(extents, rectangle.expandWidth). Но вот незадача, нет в классе возможности увеличить прямоугольник по вертикали. Ну напишем метод RectangleUtils.expandHeight(rectangle : Rectangle, amount : int) : Rectangle. Но вот в Array.map(extends, ?) ее не скормить . Вот почему такая сегрегация методов в зависимости от места определения? Не, если есть extension method, то, вероятно, все хорошо (хотя хз).

Без сегрегации методы не принадлежат объекту. Методы принадлежат "типу". Динамические (переопределяемые) методы — тоже типу, только механизм их выполнения сложнее. Тогда по определению получается, что методы не определяются в классе (а, например, в модуле). Это автоматически приравнивает рукописный expandHeight к "библиотечному" expandWidth. Ну и небольшой сахар для привязки к метода к экземпляру:
smth.method ====>
function(...args) {
  SmthType.method.apply([smth].concat(args));
}


Кстати, проблема с необходимостью отвязки/привязки метода к экземпляру как раз реальная, я ее периодически на практике вижу. Жить то можно, но не красиво как-то.

К чему все это. Если методы принадлежат типу (а не экземплярам), видимо, "конструкторы" тоже принадлежат типу. И точно так же, как с методами, хочется прекратить сегрегацию instance creation method, объявленных рядом с классом и объявленных где-то еще. Здесь я уже веду речь именно об instance creation, так как в основном именно это интересует программиста. И специально делить такие методы на два класса (конструкторы с ответственностями выше) и "просто методы создания" мне кажется плохой идеей.

XC>Если любой метод обязан иметь имя, то методы ctor и dtor могут как иметь произвольные имена, так и не иметь их. Имена, если они есть, подчиняются общим правилам именования методов.

Это хорошая идея.

XC>если имя есть, то на функцию-конструктор можно сослаться, можно вызывать ее для переинициализации объекта, взять ее адрес, можно вызвать один конструктор из другого и т.д.

Не а, нельзя . Как я показывал выше, есть instance creation method и instance initialization method. И нужно уметь их различать. Видимо, у них все же должны быть разные имена. Вот что такое MyClass.InitFromStr — это создание объекта или его инициализация? Если это создание объекта, такую функцию нельзя вызвать для переинициализации (она новый объект возвращает). А если это функция инициализации, то ее невозможно вызывать для создания объекта. Что-то не получается... Ну и та же проблема с вызовом конструкторов — не ясно, вызвыается конструктор или метод инициализации.

XC>Опциональная возможность именования конструкторов и деструкторов дает дополнительную возможность самодокументирования кода.

XC>В вызывающем коде инициализация классическая
Переставил фразы оригинала, читаемость мне нужна здесь. Ну создаем новый прямоугольник:
  new Rectangle(1, 2, 3, 4);

Упс... Что такое 3 и 4? Это ширина и высота или все же координаты второй точки? И, кстати, почему выбирая перегрузку по типам аргументов мы жестко выбираем вариант с щириной/высотой или с координатами точки? Ведь и ширина/высота, и координаты вершин вполне равноправны. Ну ладно, нужно давать возможность создавать объекты указывая имена "конструкторов". В этом случае мы можем указать new Rectangle.bySize(1, 2, 3, 4); или new Rectangle.byCorners(1, 2, 3, 4). Но вот опять незадача. В библиотеке (определении выше) оказался только Rectangle.bySize. А вот byCorners не оказался, а нам часто нужен именно он. Казалось бы, мелочи. Пишем RectangleUtils.rectangleByCorners(...) : Rectangle. И получаем два instance creation method: new Rectangle.bySize и RectangleUtils.rectangleByCorners(...). Блин, ну вот почему опять мы получили два метода, которые вызываются по-разному??? Причем они с точки зрения прямоугольника вполне равноправны (чем думал автор библиотеки — умолчим).

XC>Имя класса освобождается от дополнительной нагрузки. Если предположить, что мы имеем дело с языком, в котором каждая программная сущность — объект, то снимается неоднозначность: имя класса — уникальный идентификатор объекта класса, имя конструктора — уникальный идентификатор объекта-функции.


Почти хорошо. Только вот в ваших примерах выше имя конструктора получается привязано к классу (ну и имени класса). Т.е. сам по себе "объект-функция" как-то не очень существует. Еще раз повторю — идея различения функций по месту их определения мне не очень нравится. Да, это влияет на поиск имен. Но давайте привяжем поиск имен не к "классам", а к модулям. Импортируются модули, модуль может определять несколько типов, методы для разных типов и т.п. Т.е. множество доступных имен определяется множеством открытых (импортированных) модулей, но никак не классов (типов на самом деле). Если допустить еще возможность переопределения по типу возвращаемого значения, получается все очень красиво и на синтаксическом сахаре:
new Rectangle.bySize(...) ====>
[bySize returning Rectangle](...)

Ну правда же, меня не очень интересует, кто и в какой момент будет аллоцировать память. Либо вызов метода, либо метод делегирует вызов куда-то еще.
На самом деле получается еще интереснее. Пусть есть RectangleFactory с методом createRectangle. Ну и сахар на вызов instanceMethod выше. Тогда (предположим, конфликтов и других перегрузок методов нет)
new Rectangle.createRectangle(rf) ===>
[createRectangle returning Rectangle](rf) <====
rf.createRectangle()

Как вам такая конструкция

Чуть не забыл, конструкторы без имен. Тоже можно через сахар сделать:
class Rectangle {
  ctor (...args)
} ====>

class Rectangle {
  ctor new(...args) : Rectangle {...}
}

var x = Rectangle.new(....);


P.S. Вообще, если позволить вводит "внешнее" отношение is-a между типами и сделать "явное" переопределение методов (т.е. через передачу в конструктор, например) получаются очень интересные возможности. В частности, становится не нужно понятие "implements interface". И вроде бы с системой модулей для видимости имен плюс сахар выше (и еще чуть-чуть нового) получить все то, что дает современное "ООП". Там, правда, есть некоторые проблемы с identity и необходимым сахаром для того, чтобы переопределение методов было удобным, но если интересно — могу рассказать подробнее.
Re[3]: Способ именования конструкторов и деструкторов
От: DarkGray Россия http://blog.metatech.ru/post/ogni-razrabotki.aspx
Дата: 08.04.11 19:37
Оценка:
M> если интересно — могу рассказать подробнее.

интересно. расскажи, пожалуйста
Re[3]: Способ именования конструкторов и деструкторов
От: DarkGray Россия http://blog.metatech.ru/post/ogni-razrabotki.aspx
Дата: 08.04.11 19:49
Оценка:
M>В этом случае мы можем указать new Rectangle.bySize(1, 2, 3, 4); или new Rectangle.byCorners(1, 2, 3, 4).

с точки зрения DRY лучше делать:
new Rectangle(1, 2, Size(3,4))
new Rectangle(Corner(1,2), Corner(3,4))


М> Вот что такое MyClass.InitFromStr — это создание объекта или его инициализация? Если это создание объекта, такую функцию нельзя вызвать для переинициализации (она новый объект возвращает).


инициализация объекта с поведением по умолчанию: объект — создается, а не переинициализируется
поведение по умолчанию может быть переопределено на другое по месту при необходимости.
в C++ кстати именно такая парадигма заложена.
Re[4]: Способ именования конструкторов и деструкторов
От: maxkar  
Дата: 09.04.11 16:17
Оценка: 25 (4)
Здравствуйте, DarkGray, Вы писали:

DG>интересно. расскажи, пожалуйста


Ок. Disclamer. Все нижесказанное возникло в результате размышления над тем, что мне не удобно писать. Область — "толстые клиенты" в основном. В других случаях может быть по-другому. На полноту и непротиворечивость пока не претендую, в "описание языка" размышления не собирал.

В классических языках ООП решает слишком много задач, при этом не предоставляет средств для решения подобных задач. Решаемые задачи:
1. Установление отношения is-a между типами.
2. Обеспечение повторного использования реализации.
3. Полиморфизм поведения объектов одного и того же типа.
4. Управление доступом к различным методам, работающим над типом.
Проблема. Невозможно использовать каждый механизм по-отдельности. Кроме того, методы, работающие с одним и тем же типом вызываются совершенно по-разному в зависимости от того, определены они в самом «классе» или вне его (это я выше писал, с конструкторами та же проблема).

Первое и основное. Отношение is-a между типами. Вообще, на практике тип важен для того, чтобы определить, какие методы можно применять к объекту. Отношение A is-a B в данном случае определяет, что методы, применяемые к типу B могут применяться и к типу A. Именно это определяет и наследование, и реализация интерфейсов. Проблема в том, что подобное отношение формируется на этапе компиляции и в случае библиотек не может быть изменено. Хотя, например, хотелось бы сказать, что библиотечный String is-a каким-либо внутренним CharSequence. На практике для такой конвертации используется шаблон Adapter. Наличие целого шаблона говорит, что проблема не редкая. И наличие такого StringCharSequenceAdapter фактически говорит, что String may-be CharSequence by StringCharSequenceAdapter, только конвертацию нужно выполнять вручную в каждом месте использования. Почему бы не сказать компилятору
declare String is CharSequence with 
    function str → new StringCharSequenceAdapter(str)

В этом случае компилятор при необходимости будет создавать «конвертер» из одного типа в другой. Следует отметить, что это не приведение типа с созданием нового значения, это создание «обертки» над существующим объектом. В случае модификации одного из объектов второй соответствующим образом модифицируется. Если же требуется представлять строку двумя способами, придется все же в одном из случаев писать вызов метода, но такое происходит гораздо реже, чем простая конвертация.
При такой автоматической конвертации получается проблема — теряется identity объекта. Новосозданный объект отличается от исходного. Правда и в случае адаптера identity уже потерялась, но в данном случае потеря будет происходить чаще. Мне это не кажется проблемой, на практике я встречал только сравнение identity для объектов одного и того же типа. А для такого сценария проблема может быть решена компилятором — адаптер из одного типа в другой создается при первом использовании, а затем в качестве адаптера будет браться уже созданный экземпляр.

Теперь об интерфейсах. Что такое интерфейс в общем случае? Это фактически набор каких-либо методов. Собственно, это и есть запись (record), у которой все элементы являются функциями. Принципиально есть два варианта их применения. Первый — скормить экземпляр интерфейса в какую-нибудь библиотеку. Второй — использовать внутри программы для ограничения доступного списка методов. В обоих случаях, например,
declare FileOutputStream is OutputStream with 
    function stream → {
   close = close stream
   write = write stream
   ...
}
Ну или, с сахаром
declare FileOutputStream is OutputStream auto by-name

Помимо возможности введения внешних отношений, имеется также и возможность реализовать разные интерфейсы с конфликтующими именами методов (это не та проблема, которую стоит решать, но ее решение является бонусом).

Так что интерфейс — это фактически обычный тип. Еще один плюс — на record-style интерфейсах гораздо проще писать различные конвертеры. Фактически, конвертер выглядит как
let wrapSomeMethod iface = iface with { someMethod = wrap (iface.someMethod) }

По сравнению с полноценной реализацией новой обертки как-то гораздо красивее смотрится .

Можно автоматически генерировать "accessor methods" для интерфейса:
interface type Z = {methodA : int -> String; methodB : String -> int }
будет еще генерировать
let methodA (z : Z, i : int) -> z.methodA(i)
let methodB (z : Z, s : String) -> z.methodB(s)

Хотя если вызвать в стиле z.methodA(...), то такой генерации и не понадобится (для экземпляров классов это раскручивается в вызов метода с параметром-объектом).

Второе. Использование реализации. Обычная композиция. Ну с отношением is-a выше здесь вообще все похоже на классический ООП.
type derived = record
    base : parent,
    … какие-то еще данные
end

declare derived is base with 
    function der → der.base


Можно и сахар для одновременного объявления типа и конвертора сделать. Бонус:
type IOStream = record
  base : Stream
  inputStream : Istream
  outputStream : Ostream
end

declare IOStream is Stream with
    function str → str.base
delcare IOStream is Istream with
    function str → str.inputStream
declare IOStream is Ostream with
    function str → str.outputStream

let createFileIOStream fname = 
  let baseStream = createFileStream fname in
  { base : baseStream,
     inputStream : createInputStream(baseStream),
     outputStream : createOutputStream(baseStream)
  }


Можно сделать и отдельные экземпляры с двумя base. В этом случае неявно преобразовать IOStream в Stream не получится (конфликт различных преобразований). Да и плохая это будет идея, так как IOStream в этом случае все же не Stream а целых два Stream.

В качестве минуса — можно написать так, что понять, почему и как объект привелся к типу. На практике не видел, где могла бы возникнуть такая цепочка. Зато в качестве плюса можно сделать отношение is-a локально в каком-то фрагменте (хотя в этом случае identity точно потеряется и на при необходимости будет вызываться метод конвертации). Фактически, это отношение работает только в compile-time и служит для выбора метода, который будет вызван.

Третье. Полиморфизм. Это тоже не сложно. Правда предлагаю делать полиморфмизм явным. В любом случае, для изменения поведения какого-либо метода класса требуется явно передать требуемый метод. Ну просто потому, что для корректного переопределения требуется знать, в каких внутренних методах используется переопределяемый метод. Например, приведет ли переопределение метода Rectangle.getWidth() к изменению поведения метода Rectangle.getArea()? Еще одна проблема возникает, если нужно в различных комбинация переопределять поведение. Что интересно — при этом фактически новых типов не возникает — все методы совпадают с методами родителя, класс сейчас нужен только для того, чтобы реализация работала. Если явно (правда используется динамика):
let createSomethingWithA args = createSomething args { methodA : … }
let createSomethingWithB args = createSomething args { methodB : … }
let createSomethingWithAandB args = createSomething args { methodA : …; methodB : … }


Можно и более сложные случаи, когда createSomethingWithA тоже получает список «переопределений», как-то модифицирует его и создает актуальных экземпляр. Методы, работающие с экземпляром, просто обращаются к соответствующим полям. Если честно, полиморфизма на таком уровне мне было не нужно. Мне был нужен полиморфизм на уровне «в частных случаях переопределить поведение пары методов». И работал он на уровне тех самых интерфейсов в record-style. Т.е. можно было бы создать default implementation и затем заменить пару методов. Может быть, когда-нибудь так и сделаю. Там обработчики событий были

Для доступа к «защищенным» полям передавать соответствующие «интерфейсы». Собственно, плюс интерфейсов в стиле «record» здесь и проявляется. Адаптер в стиле record гораздо короче, чем «полноразмерный» класс.
Let callProtected x = x.protMethod {
  dispose : x.dispose
  draw : x.draw
}

Может быть еще определение этого типа. В принципе, все это можно делать и сейчас, но в mainstream код такой обертки гораздо тяжелее.

Ну и последнее. Управление доступом. При полиморфизме (переопределяемые protected methods) это все делается вручную и явно. Все остальное делается модульной системой (что-то вроде модулей в ocaml). Они определяют, какие методы в какой момент видны. Ну а дальше уже механизм разрешения методов. Плюс сахар для того, чтобы методы можно было брать как instance method, так и извне. Вероятно, для удобства написания методов потребуется и конструкция
with something {
  // здесь методы сначала разрешаются как методы или поля something и только потом из внешнего контекста.
}
ну и сахар 
defmethod method Type (args …)  … =>
let method instance args …
    with instance
        ….
.

Собирая все вместе.
//Определение класса
class x === type t
interface Y === type Y = record {
  methodA : <type>;
  methodB : <type> ...
}

//Конструторы
public X(...) === export createX(...)
internal/private x(...) === createX(...) //внутренний для модуля

// Наследование
Z extends X === { super : X, ... } 
и declare Z is-a X with function z -> z.super

приватное "наследование". Точно так же, но без declare Z is-a ...

в конструторе
{ ... super(a, b, c) ... }  === let super_instance = createX(a, b, c) ... { super : super_instance, ...}

// реализация интерфейса
Z implements Y ===
declare Z is-a Y with function z -> { methodA : z.methodA, methodB : z.methodD, ...}
или
declare Z is-a Y auto by-name // в рамках видимых в данном контексте методов

// методы
public function zzz (args) {...} ===

let function zzz this : Z args =
  with this { ...
  }

"Extension method" выглядит точно так же, только находится в другом модуле.

//переопределение методов
override function zzz (args) { ... } ===
явно через конструтор:

type X {
  overrides : ...
}

let createX(constructuor-args, overrides) = { overrides : concatOverwrides(overwrides, DEFAULT_IMPLEMENTATIONS), .... }

// без наследования. Обычно если нужно переопределять методы, типы менять не нужно.
let createZ(constructuor-args, overrides2) = createX(constructor-args, concatOverwrired(overries2, DEFAULT_Z_IMPLEMENTATIONS))

Бонусы конвертеров интерфейсов показал. Создание "экземпляров" интерфейсов тоже удобнее. Можно сложные варианты instance creation method делать (по сути это обычная функция). А вообще все это борьба с классами, которые упоминаются только в operator new а дальше используются с типом интерфейсов или своих базовых типов. Очень много их них заменяется на instance creation method с замыканиями. Это и реализации интерфейсов, и достаточно большая часть override protected. Вот. Именно это мне и нужно. Даже не смотря на небольшие проблемы с indentity. А если что-то еще из "ООП" осталось, им я даже готов пожертвовать так как не пользуюсь. Хотя примеры рассмотрел бы.
Re[2]: Способ именования конструкторов и деструкторов
От: VladD2 Российская Империя www.nemerle.org
Дата: 10.04.11 01:58
Оценка:
Здравствуйте, maykie, Вы писали:

M>Третий. Первый противоречит DRY, второй из-за того что если в языке всё передаётся по ссылке и разрешена перегрузка операторов, то будет неоднозначность с operator():

M>
M>class Callback<X>{
M>operator (X x) {fp(x)};
M>this(FuncPointer fp)
M>{
M>    this.fp = fp;
M>}

M>this(Callback<X> other)
M>{
M>     this(other.fp); //вызов оператора() или конструктора?
M>}
M>

M>в третьем неоднозначностей не будет.

Ничерта не понял. Что за байда у тебя с этим оператором? И какая ему разница с чем конфликтовать с this или с ctor?

В Nemerle используется this и есть перегрузка операторов. Никаких конфликтов не наблюдается. Более того this используется еще в ряде контекство, но опять таки никаких проблем не видно.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Способ именования конструкторов и деструкторов
От: VladD2 Российская Империя www.nemerle.org
Дата: 10.04.11 02:01
Оценка:
Здравствуйте, blackhearted, Вы писали:

B>всегда только IDE?

B>Можно вообще уйти от фиксированных имён и аннотировать методы.
B>Что-то типа @Constructor. Тогда без IDE будет полный пипец. А такие ситуации довольно часты.

Полный пипец это когда люди одну логику применяют для конструкторов, а другую для методов. По вашей логике методы надо тоже называть так чтобы из их имени было ясно к какому типу они относятся. Но почему-то это вас не волнует.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Способ именования конструкторов и деструкторов
От: VladD2 Российская Империя www.nemerle.org
Дата: 10.04.11 02:11
Оценка: +2 -1
Здравствуйте, DarkGray, Вы писали:

DG>чтение кода с любого места в произвольном направлении (обычно конфликтует с критерием дублирование информации)

DG>полезно при беглом чтении кода, анализе, частичном автоматизированном разборе и т.д.
DG>конструктор/деструктор могут быть достаточно отдалены от объявления самого класса, а для названий this/~this, ctor/dtor вообще нет никаких подсказок о каких конструкторах/деструкторах идет речь, что требует либо аккуратный скроллинг к объявлению класса (при беглом скроллинге можно перескочить на чужое объявление), либо подсказок от IDE. с методами проблема менее критична, т.к. название метода уже часто несет большую часть информации — что это за метод, и название класса необходимо только когда метод реально полиморфный.

Очередной пример извращенной логики теоретиков.

Заметь! Конструкторов в типах обычно очень не много. А вот методов туча! И почему-то тебя не волнует то, что эта туча не имеет "онтекста".

Логика твоя высосана из пальца. На практике никто никогда не читает код случайным образом ткнув мышью в файле.

Когда код пишется, то контекст понятен сам собой.

Когда код меняется, то сначала человек ищет тип и уже потом ищет в нем конструкторы (если ему нужно читать/менять именно их). Так что контекст ему опять же становится известен.

У имени совпадающего с именем типа есть одно преимущество — можно тупо нажать на Ctrl+F3 в студии и найти то же имя ниже в файле. Но это наличие в IDE возможности открыть список членов типа и перейти к нужному полностью покрывают эту "удобность". Так что преимущество весьма спорное.

А вот с точки зрения именно чтения куда как удобнее иметь стандартное имя для конструкторов. Это позволяет выделять их визуально, а значит не путать с простыми методами.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Определение, вызов
От: VladD2 Российская Империя www.nemerle.org
Дата: 10.04.11 02:14
Оценка: +1
Здравствуйте, Qbit86, Вы писали:

Q>Топикстартер предоставил синтаксис определения, а не вызова. Вызов, я так полагаю, по прежнему будет new MyClass(params).


"new", кстати, тоже лишнее
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Определение, вызов
От: adontz Грузия http://adontz.wordpress.com/
Дата: 10.04.11 02:52
Оценка:
Здравствуйте, VladD2, Вы писали:

Q>>Топикстартер предоставил синтаксис определения, а не вызова. Вызов, я так полагаю, по прежнему будет new MyClass(params).

VD>"new", кстати, тоже лишнее

public class MyClass
{
}

public class YourClass
{
    private static MyClass MyClass()
    {
       return null;
    }

    public YourClass()
    {
        // Syntax without 'new' keyword.
        // Is this invocation of Method or construction of object?
        MyClass x = MyClass();
    }
}
A journey of a thousand miles must begin with a single step © Lau Tsu
Re[5]: Способ именования конструкторов и деструкторов
От: FR  
Дата: 10.04.11 06:19
Оценка: 1 (1)
Здравствуйте, maxkar, Вы писали:

Интересно, но по моему слишком сложно и хрупко, и не вижу чем лучше той же простой по сути структурной типизации для объектов (OCaml).
Re[5]: Определение, вызов
От: _nn_ www.nemerleweb.com
Дата: 10.04.11 07:48
Оценка:
Здравствуйте, adontz, Вы писали:

A>Здравствуйте, VladD2, Вы писали:


Q>>>Топикстартер предоставил синтаксис определения, а не вызова. Вызов, я так полагаю, по прежнему будет new MyClass(params).

VD>>"new", кстати, тоже лишнее

  Is this invocation of Method or construction of object?
A>
A>public class MyClass
A>{
A>}

A>public class YourClass
A>{
A>    private static MyClass MyClass()
A>    {
A>       return null;
A>    }

A>    public YourClass()
A>    {
A>        // Syntax without 'new' keyword.
A>        // Is this invocation of Method or construction of object?
A>        MyClass x = MyClass();
A>    }
A>}


A>


В Python нет неоднозначности
class MyClass:
    def __init__(self):
        print "MyClass.__init__"

class YourClass:
    @staticmethod
    def MyClass():
        print "YourClass.MyClass"
        return None

    def __init__(self):
        x1 = MyClass()
        x2 = YourClass.MyClass()

y = YourClass()

MyClass.__init__
YourClass.MyClass

http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[5]: Определение, вызов
От: hardcase Пират http://nemerle.org
Дата: 10.04.11 08:54
Оценка:
Здравствуйте, adontz, Вы писали:

A>Здравствуйте, VladD2, Вы писали:


Q>>>Топикстартер предоставил синтаксис определения, а не вызова. Вызов, я так полагаю, по прежнему будет new MyClass(params).

VD>>"new", кстати, тоже лишнее

A>
A>public class MyClass
A>{
A>}

A>public class YourClass
A>{
A>    private static MyClass MyClass()
A>    {
A>       return null;
A>    }

A>    public YourClass()
A>    {
A>        // Syntax without 'new' keyword.
A>        // Is this invocation of Method or construction of object?
A>        MyClass x = MyClass();
A>    }
A>}


A>


Если считать что конструктор — это функция, возвращающая объект, то подобный код просто не будет компилироваться: компилятор расскажет о неоднозначности вызова MyClass().
/* иЗвиНите зА неРовнЫй поЧерК */
Re[2]: Способ именования конструкторов и деструкторов
От: FR  
Дата: 10.04.11 10:34
Оценка:
Здравствуйте, maykie, Вы писали:

M>Третий. Первый противоречит DRY, второй из-за того что если в языке всё передаётся по ссылке и разрешена перегрузка операторов, то будет неоднозначность с operator():


Ну такая неоднозначность легко разрешима, например в D вызов this(x) это всегда вызов конструктора, operator()
(который в D имеет вид result_type opCall(params) ) внутри класса можно вызывать только явно this.opCall(x);
Re[5]: Определение, вызов
От: VladD2 Российская Империя www.nemerle.org
Дата: 10.04.11 15:43
Оценка: +1
Здравствуйте, adontz, Вы писали:

A>
A>public class MyClass
A>{
A>}

A>public class YourClass
A>{
A>    private static MyClass MyClass()
A>    {
A>       return null;
A>    }

A>    public YourClass()
A>    {
A>        // Syntax without 'new' keyword.
A>        // Is this invocation of Method or construction of object?
A>        MyClass x = MyClass();
A>    }
A>}
A>


Пришла пора поставить Nemerle.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Способ именования конструкторов и деструкторов
От: ononim  
Дата: 10.04.11 15:54
Оценка: :)
4.
class MyClass
{
 ();
 ~();
};


Как много веселых ребят, и все делают велосипед...
Re[5]: Выговорился
От: Qbit86 Кипр
Дата: 10.04.11 18:20
Оценка:
Здравствуйте, maxkar, Вы очень много понаписали:

M>Все нижесказанное возникло в результате размышления над тем, что мне не удобно писать... В классических языках ООП решает слишком много задач... При такой автоматической конвертации получается проблема — теряется identity объекта... Что такое интерфейс в общем случае?.. Можно автоматически генерировать "accessor methods" для интерфейса... Можно и сахар для одновременного объявления типа и конвертора сделать... Можно сделать и отдельные экземпляры с двумя base... Для доступа к «защищенным» полям передавать соответствующие «интерфейсы»... Ну и последнее... Очень много их них заменяется на instance creation method с замыканиями...


Ничего не понял, но от греха подальше плюсанул. Твой комментарий тронул меня до глубины души.
Глаза у меня добрые, но рубашка — смирительная!
Re[3]: Способ именования конструкторов и деструкторов
От: x-code  
Дата: 11.04.11 07:49
Оценка:
Здравствуйте, maxkar, Вы писали:

M>Первая же претензия. Не указано, какую задачу решает конструктор. В mainstream (c++/java/c#) определение конструктора на самом деле определяет две сущности:

M>

    M>
  1. Instance initialization method — инициализация экземпляра. Выполняет следующие действия:
    M>
  2. Instance creation method — создание экземпляра. Выполняет следующие действия:
    M>
initialization. Creation лично у меня обычно ассоциируется со статическим оператором new (если нужно как-то по-особому выделить память для класса).

XC>>Каждый метод предваряется некоторым ключевым словом: (skip)

M>A!!! :maniac: Блин, они гвоздями к объекту прибиты? Ну неудобно это. И я против сегрегации методов. Возьмем тот же прямоугольник. Не, мало. Массив прямоугольников! rectangles. И отконвертируем их в массив высот этих прямоугольников. Напишем, Array.map(rectangles, ???). И что туда писать? Анонимную функцию? Или Rectangle.getHeight? Кстати, кто такой тогда будет Rectangle.getHeight?
M>Зато если написать внешнюю функцию RectangleUtils.getWidth(rectangle : Rectangle) : int, ее можно туда передать.

Если в языке разрешить использовать нестатические методы класса как статические с дополнительным аргументом this, это поможет?
class MyClass
{
 public int Func();
};
MyClass obj;
obj.Func();
MyClass.Func(obj); // вот так тоже можно

Это что-то вроде "обратных методов-расширений". Тогда любые методы будут "принадлежать типу", если это нужно.
Re[4]: Способ именования конструкторов и деструкторов
От: maxkar  
Дата: 11.04.11 08:24
Оценка:
Здравствуйте, x-code, Вы писали:

XC>Если в языке разрешить использовать нестатические методы класса как статические с дополнительным аргументом this, это поможет?


Да. И очень сильно. Ту проблему решит. Правда нужно будет решить, можно ли все методы, работающие с типом, обозвать его extensionMehtod. В идеале хотелось бы полную симметричность. Т.е. метод объекта может быть и "обратным extension-method", и любой метод, работающий с объектом (в качестве первого аргумента), может считаться его extension method. На примере — написан какой-то метод, изменяющий сложную структуру (тот же UI для толстого клиента, где живет состояние). С сигнатурой doSomethingWith(object). И хочется конкретный вызов метода с конкретным объектом передать в другой вызов (например, это будет обработчик нажатия на кнопку). Т.е. в чистом виде нужно
вместо 
... object = ...
const callback : Function = function() : void {
  doSomethingWithObject(object);
}
add(new Button(new Action(callback)));

удобно было бы
... object = ...
add(new Button(object.doSomethingWithObject));


Проблема стоит только для методов с одним аргументом. Для методов с двумя и более аргументами все прекрасно, так как работает currying и функция не вычисляется. А вот для одного аргумента так не получится. Можно отдельную функцию написать, которая такую оболочку делает, в принципе не проблема. Но здесь вопрос об оправданности разделения extension method и "просто метод, работающий с типом". Подробно этот вопрос не исследовал, может быть, возникнет противоречие с чем-нибудь еще. Возможно, будут проблемы с определением, какой же именно метод имелся в виду, нужно смотреть, позволяют ли такое extension method. Вообще, если организация функций помодульная (а не пообъектная) для разрешения неоднозначностей может быть что-то вроде
object.[some.module.Name:doSomethingWithObject]

С организацией по объектам/по классам такое не очень красиво смотрится.
Re[6]: Способ именования конструкторов и деструкторов
От: maxkar  
Дата: 11.04.11 09:32
Оценка: 21 (2)
Здравствуйте, FR, Вы писали:

FR>Здравствуйте, maxkar, Вы писали:


FR>Интересно, но по моему слишком сложно и хрупко, и не вижу чем лучше той же простой по сути структурной типизации для объектов (OCaml).


Замечание принято. Что хрупко — естественно, в стройную и надежную систему я пока собирать не пытался. Т.е. не определял, что нужно сделать синтаксическим сахаром, что — не нужно а что друг-другу противоречит. Это больше набор хотелок и возможных вариантов их получения. Поэтому же и сложно получается. А вышеизложенные соображения — это попытка ответить на вопрос "зачем вообще выделять объектный тип среди других типов и что нужно добавить к абстрактным типам данных, чтобы получить то же, что дает ООП".

Здесь сыграла важную роль книга "Practical API Design: Confessions of a Java Framework Architect". Там сформулирована мысль о том, что предоставлять функциональность библиотеки следует классами, а вот то, что библиотеке требуется от внешнего мира — интерфейсами. Обоснование — возможность добавлять новые методы в интерфейс, не ломая клиентского кода (который может тоже реализовать интерфейс, предоставляемый библиотекой). Попробовал на практике следовать указанной рекомендации — проблем не составляет. Только вот возникакет вопрос — а зачем тогда вообще нужно отдельное понятие интерфейса? Ведь, по сути, интерфейс в этом случае — простой record, который передается в библиотеку. Еще одним аргументом за record было наличине в интерфейсах методов вроде getSomething(), где это something не меняется во время работы с объектом. Например, какой-нибудь getCapacity() в библиотеке, которая не предполагает, что вместимость может изменяться со временем. В record'е неизменяемое значение смотрелось бы естественнее (в данном случае интерфейс — это именно то, что требует а не предоставляет библиотека). Можно даже и без record, но для количества в 3 и более методов все же лучше их называть.

Далее, практическое наблюдение. У меня по возможности проект собирается на основе множества небольших библиотек, занимающихся определенными "незначительными" аспектами. Можно их при сборке и в одну собрать, это не принципиально. Принципиально то, что на самом деле в этих библиотеках получаются не "классы" а "типы данных". Наследованием там вообще не пахнет (ну не нужно оно, а все что нужно для изменения поведения передается явно), а без этого разница между объектами и "просто типами" в синтаксисе вызова методов ну и возможности приведения к "интерфейсам". Ситуация усугубляется еще и тем, что у меня в языке (ActionScript 3.0 ) нет перегрузки конструкторов. Поэтому если возможностей создания объекта несколько, приходится писать instance creation methods. Ну а так как все instance creation methods равноправны, конструтор можно было бы не делать публичным. Методы объекта по сути — те же методы работы с abstract type. Ну разница в синтаксисе, я уже показал в другом сообщении, можно два варианта сделать эквивалентными (через точку и через метод, получающий this (обратный метод-расширение, как их назвал x-code)). На модульную структуру для организации функций все как раз гораздо удобнее ложится, чем попытки вспомнить, в какой же хелпер был положен extension method.

Ну и еще одно наблюдение. На небольшом проекте (что-то около 120 тысяч строк) при используемой организации (пишем удобные библиотеки, а не framework'и, навязывающие схему написания + интерфейсы — для входных данных в библиотеки, на выходе — классы, может быть — без публичных конструкторов) я вообще не вижу никаких преимуществ в классическом "ООП". Ну да, наследование используется в паре мест. Это из-за отсутствия алгебраических типов (type x = VariantA | VariantB | ...) и нормальных record (там как раз обработчики с частичной заменой, уже упоминал). На них вместо наследования с переопределением методов был бы someRecord with {...}. В остальных местах при необходимости композиция (для поддержки событий, UI все же, без него — никак). Никаких неудобств не испытываю. Вот и возник вопрос — а что еще есть в ООП такого, что делает его удобным для кодирования? Может, я чего-то не понимаю Ну вот из того, что нашел — это is-a отношение, которое было не нужно. Ну можно задать его извне. Не помню, можно ли так делать в haskell, или все же нужно при описании типа указывать. Все остальные практические задачи делаются прямолинейно. Нужно что-то переопределять в поведении — передаем метод, который будет вести себя по-разному. Нужно переиспользовать функциональность — используем композицию. Кстати, у себя вроде бы не встречал, что при композиции выставляется весь интерфейс "родительского" объекта. Для событий выставляются только интерфейсы подписки/удаления. Для UI-компонентов выставляется визуальная часть, getUI(). Класс же, по сути, обеспечивает взаимодействие между моделькой и UI. Можно было вообще на замыканиях делать, но просто кода много было бы внутри замыкания, не удобно. Проще выделить новый "тип".

Что касается структурной типизации. Ага, вполне устроило бы. Но только опять же, не понятно, зачем мне там классы... Почему бы не ввести структурную типизацию для record'ов или их подмножества (немодифицируемые). Все те 120 тысяч строк я бы сделал на OCaml'е на абстрактных типах данных (и вообще типах, локальных для модуля, где это можно). По сути они именно так и написаны, только в тексте изложены на языке объектной модели, которую приходится использовать. Не вижу пока никаких преимуществ, которые дали бы "классы". Кстати, что интересно, в объектах ocaml'а ведь нет неявного отношения child is-a parent, его писать нужно явно. А то, куда передаются интерфейсы, по сути использует именно хитрый вариант типизации "нужны методы A, B, C". Вот их и можно попытаться найти в некоторых случаях из текущего контекста (по именам и т.п.), зачем для этого вводить еще один метатип "объект"? Список методов в указанном контексте известен, можно выбирать (объект, для которого выбирать и строить можно и указать явно в какой-то конструкции). Чего действительно не хватало бы:

Вот. Надеюсь в таком контектсте станет понятнее, откуда вообще пошли те идеи. Я не пытался изобразить что-то особо крутое, я просто примерял ООП и "просто типы" к решению практических задач. Ну и анализировал, где и на что больше всего похож код, загнанный в рамки платформы и чего хотелось бы от этой самой платформы.
Re[7]: Разбиение комментариев
От: Qbit86 Кипр
Дата: 11.04.11 09:53
Оценка:
Здравствуйте, maxkar, Вы опять много понаписали:

M>Замечание принято. Что хрупко — естественно, в стройную и надежную систему я пока собирать не пытался... Здесь сыграла важную роль книга "Practical API Design: Confessions of a Java Framework Architect"... Далее, практическое наблюдение... Ну и еще одно наблюдение... Что касается структурной типизации... Вот. Надеюсь в таком контектсте станет понятнее, откуда вообще пошли те идеи...


Ты не мог бы разбивать свои комментарии на несколько частей? Так будет проще воспринимать, улавливать основную идею, выделять независимые ветки обсуждения, независимо оценивать. Ещё было бы удобно, если бы комментарии содержательно озаглавливались, так их проще искать в ветке обсуждения.
Глаза у меня добрые, но рубашка — смирительная!
Re[8]: Разбиение комментариев
От: x-code  
Дата: 11.04.11 10:34
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Ты не мог бы разбивать свои комментарии на несколько частей? Так будет проще воспринимать, улавливать основную идею, выделять независимые ветки обсуждения, независимо оценивать. Ещё было бы удобно, если бы комментарии содержательно озаглавливались, так их проще искать в ветке обсуждения.


Присоединяюсь к просьбе. Понятно, что когда идеи живут в голове автора, то все просто и понятно, но ИМХО при изложении многое потерялось... Остается впечатление — что-то интересное, но вот что именно?:)
По крайней мере нестандартные синтаксические конструкции хорошо бы дополнить комментариями — что есть что.
Re[6]: Выговорился
От: Кодёнок  
Дата: 11.04.11 11:27
Оценка:
Здравствуйте, Qbit86, Вы писали:

M>>Все нижесказанное возникло в результате размышления над тем, что мне не удобно писать... В классических языках ООП решает слишком много задач... При такой автоматической конвертации получается проблема — теряется identity объекта... Что такое интерфейс в общем случае?.. Можно автоматически генерировать "accessor methods" для интерфейса... Можно и сахар для одновременного объявления типа и конвертора сделать... Можно сделать и отдельные экземпляры с двумя base... Для доступа к «защищенным» полям передавать соответствующие «интерфейсы»... Ну и последнее... Очень много их них заменяется на instance creation method с замыканиями...


Q>Ничего не понял, но от греха подальше плюсанул. Твой комментарий тронул меня до глубины души.


Вы все еще читаете посты с шизофренической пунктуацией?
Re[2]: Способ именования конструкторов и деструкторов
От: _nn_ www.nemerleweb.com
Дата: 11.04.11 15:18
Оценка:
Здравствуйте, ononim, Вы писали:

O>
O>4.
O>class MyClass
O>{
O> ();
O> ~();
O>};
O>


O>


А может проще:
class Stock(val name:String, val symbol:String, var price:Double, var change:Double){
}


class MyClass() {}


И все делается само
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[3]: Способ именования конструкторов и деструкторов
От: ononim  
Дата: 11.04.11 17:03
Оценка:
__>И все делается само
а если хочется два конструктора с разными параметрами?
Как много веселых ребят, и все делают велосипед...
Re[4]: Способ именования конструкторов и деструкторов
От: . Великобритания  
Дата: 11.04.11 19:16
Оценка:
On 11/04/11 18:03, ononim wrote:

> __>И все делается само

> а если хочется два конструктора с разными параметрами?
Мало ли что хочется, может лучше не надо?..Интересно, но в Скале это почти так. Красиво, имхо.
Posted via RSDN NNTP Server 2.1 beta
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[5]: Способ именования конструкторов и деструкторов
От: x-code  
Дата: 12.04.11 09:35
Оценка: 5 (1)
Здравствуйте, maxkar, Вы писали:

M>Проблема стоит только для методов с одним аргументом. Для методов с двумя и более аргументами все прекрасно, так как работает currying и функция не вычисляется. А вот для одного аргумента так не получится. Можно отдельную функцию написать, которая такую оболочку делает, в принципе не проблема.


Когда я анализировал эту проблему, пришел к выводу, что для передачи функций как аргументов корректнее использовать синтаксис, отличный от синтаксиса вызова функции. Для формирования функциональных объектов с частично сформированными аргументами вместо круглых скобок используются фигурные. Это кстати вписывается в интуитивно понятную схему — фигурные формируют данные (массивы, структуры), круглые — вычисляют (порядок действий, аргументы функций).
Например (я применяю наиболее оптимальный синтаксис для типов функциональных объектов "int=>int", "(char,float)=>void" и т.п.)
int Foo1(int x, int y);
int Bar1(int=>int f);
int Foo2(int z);
int Bar2(void=>int f);

Bar1(Foo1{100});   // x = 100, y остается аргументом 
Bar1(Foo1{_,200}); // y = 200, x остается аргументом 
Bar1(Foo1{.y=300});// y = 300, x остается аргументом 

Bar2(Foo2{300});


Впрочем, этот вопрос уже слишком далеко отошел от конструкторов/деструкторов, по которым у меня действительно были сомнения:) Благодаря rsdn кстати я и пришел к своему решению. Если будет интерес, буду создавать время от времени такие темы с обсуждением различных синтаксических решений, а вообще я надеюсь что скоро открою сайт со всеми этими материалами.
Re: Способ именования конструкторов и деструкторов
От: Tilir Россия http://tilir.livejournal.com
Дата: 13.04.11 06:28
Оценка:
Здравствуйте, x-code, Вы писали:

XC>Как думаете, какой способ предпочтительнее с точки зрения дизайна языка программирования?


Тут надо сначала разобраться с тем, что означает слово "конструктор". В языках типа C# и Delphi, "конструктор" это нечто совершенно ненужное, грубо говоря первая функция которая педалит объект, выделяемый в динамической памяти (и только там). Как её назвать -- да как угодно. Хоть Create/Destroy, разницы-то.

В языке C++ конструктор это очень мощная абстракция, он может запускаться и отрабатывать в неопределённые моменты времени, поскольку объект может быть определён на стеке, может быть нелокальным статическим, может быть каким угодно ещё. То есть по сути там программист никогда *не вызывает* конструктор и деструктор сам кроме тех редких случаев когда создаёт объект динамически. Программист их *объявлет*, вызовом же занимается компилятор. И здесь лучше не портить жизнь конвенциальностью -- имя конструктора пусть совпадает с именем объекта, имя деструктора с ним же, но с хвостиком. К тому же это сохраняет естественный синтаксис

A(const A&); // объявили
...
A a(a1); // попросили компилятор вызвать


сравните с:

CopyConstructor(const A&); // объявили
...
A a(a1); // попросили.. хмм... ЧТО?



Объявление пишется так же, как и вызов в первом случае. Во втором -- упс и опаньки.

Но (и это делает ваш вопрос совсем некорректным) есть куча языков где инскрипция "конструктор" понимается ВООБЩЕ офигеть не так. В языке Smalltalk, например, классы это first-class objects

Object subclass: #Person 
    instanceVariableNames: 'firstName lastName' 
    category: 'OnSmalltalk'


и создаваться эта радость будет примерно так:

person := (Person new)
                  firstName: 'Ramon'; 
                  lastName: 'Leon'; 
                  yourself.


где new это просто семантическая стрелка "поместите в".

Но мы можем переопределить конструктор:

Person class>>firstName: aFirstName lastName: aLastName
    ^(self new)
        firstName: aFirstName ;
        lastName: aLastName;
        yourself.


и в этом случае создавать уже вот так:

person := Person firstName: 'Ramon' lastName: 'Leon'.


Как вам такой "синтаксис для конструктора"?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.