[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: programming languages (was: kylix2) IMHO: OFFTOPIC





Dmitry Astapov wrote:

Какой тип у foreach и function_obj? Подсказка: "void *" - это
несерьезно.

 AS> В С++ эти элементы имеют определенные типы! Они, правда, подводятся
 AS> под шаблоны, но проверка типов там строгая, и трюки с void * там не
 AS> пройдут - придется применять специальные операторы-функции:
 AS> static_cast или dynamic_cast! Вот точное, концептуальное определение
 AS> данной функции (из STL):

 AS> template<class In, class Op> Op for_each( In first, In last, Op f){

 AS> 	while (first != last) f(*first++);
 AS> 	return f;

 AS> }

Вопрос: где зафиксирована информация о том, что f должна принимать
аргумент того же типа, который имеет (*first++)?

В определении Op (класса объекта f).

first имеет класс "итератора", вроде bidirectional_iterator в случае с list. Внутри него перегружен его оператор operator*(), который, при обычном систаксисе С, возвращает значение переменной, указываемой first. Итератор в C++ - это усложненный и более "умный" указатель (pointer), который может даже проверять свое значение (т.н. "итераторы с проверкой"), если это необходимо - после дополнительного программирования.

*first таким образом возвращает (да, именно возвращает, так как в данном случае мы имеем дело с оператором-функцией) объект, на который указывает данный итератор. Чаще всего (и в данном мной примере из книжки в частности) это делается для удобной работы с контейнерами (list, queue, map, vactor, ...).

*first++ заставлет итератор указать на следующий элемент в последовательности (контейнере) и возвратить значение элемента. Это обыденный трюк с приоритетами операторов. Оператор * (получение значения) имеет самый низкий приоритет в С/С++.

Значение элемента всегда имеет строго определенный тип. В приведенном ниже примере *(lc.begin()) будет иметь тип Club, так как lc.begin() указывает на первый элемент списка lc, состоящего из объектов типа Club.

Например (из "Языка программирования С++"):

void extract(const list<Club>& lc, list<Person*>& off){
//...размещаем персонал из 'lс' в 'off'

for_each(lc.begin(), lc.end(), Extract_officers(off));

}

class Extract_officers {

list<Person *> &lst;

public:
	explicit Extract_officers(list<Person *>& arg): lst(x){}

	void operator()(const Club& c){
		copy(c.officers.begin(),
		c.officers.end(), back_inserter(lst));
	}

};

Попробуем сделать так:

void extract(const list<Punks>& lp, list<Person*>& off){
//...размещаем персонал из 'lс' в 'off'

for_each(lp.begin(), lp.end(), Extract_officers(off));

}

- будет выдана ошибка (типа: *first не принадлежит к классу Club) и программа компилироваться не будет.


 AS> Проверка типов является одним из приоритетов С++. Это, так сказать,
 AS> "строго типизированный язык".
Однако наличие неявных приведений типов и динамической типизации все
портит. Для понимания того, как можно жить иначе, можно почитать о том, что
есть Hindley-Milner type system и как она используется в Clean, Haskell или
OCaml.

Хм... Дело в том, что при использовании static_cast и dynamic_cast всегда происходит проверка соотвествия типов. На обе функции наложены строгие ограничения. В частности, с помощью static_cast нельзя преобразовывать виртуальные базовые классы в их производные классы. В случае с dynamic_cast та же проверка просто откладывается на этап выполнения. Конечно, это "hack", то есть возможность на грани излишней, хотя она все-таки, очевидно необходима в некоторых случаях. Тут нельзя как в С сделать преобразования вида:

char c = 'a';
void *f = (void*)&c;

// CoolWidget - виртуальный базовый класс для CoolButton.
CoolButton r = new CoolButton();
CoolWidget* ps = &r;

// Ошибка: нельзя выполнить приведение из виртуального базового класса
pr = static_cast<CoolButton*>(ps);


Каким образом происходит проверка того, что function_obj принимает аргумент
именно того типа, который содержится в списке?
Можно ли сделать:
foreach(list_iterator.first, list_iterator.last,
  foreach(other_list_iterator.first, other_list_iterator.last, function_obj));
(это синтаксически неверно, но идея должна быть понятна. Если для этого
нужно более 3 строк кода, то хорошо ли это?)


 AS> Почему нельзя? Можно! И синтаксически все правильно - мы говорим о С++
 AS> в данном случае (см. выше: ">  AS> В С++ это делается так:")
 AS> (извините, незаметно переменил подтему). Именно для этого и писалась
 AS> эта стандартная алгоритмическая функция, чтобы можно было
 AS> объектно-ориентированными средствами языка имитировать кострукции вида:

 AS> for(Iterator counter; counter != container.end(); counter++){
 AS> 	for(Iterator counter2; counter2 != container[1].end(); counter2++){
 AS> 		// do smth...
 AS> 	}
 AS> }
И где же ответ на мои вопросы? Я повторю:
1)Каким образом происходит проверка того, что function_obj принимает аргумент
именно того типа, который содержится в списке?

Это уже компетенция компилятора, я думаю... ;-) То есть компилятор ДОЛЖЕН проверять это, и он это делает. Это провеяет модуль STL (Standard Templates Library), который "инстанцирует" из шаблона list классы вида list<int>, list<MyClass>, ... Если ты берешь объект my_list класса list<MyClass> и пытаешься сделать с ним вот это:

for_each(my_list.begin(), my_list.end(), addvaluesbyone());

И при этом класс addvaluesbyone не перегружает оператор () или этот перегруженный оператор не принимает объекты типа MyClass, а только int или float, или user_defined, то будет выдана ошибка компиляции.

Каким конкретно образом это происходит, я, пардон, не знаю и пока что не хочу знать. Главное, что это происходит, и, в частности, я уверен, производится g++ из GCC.

2)Этот вопрос я сформулировал неверно. Постараюсь изменить. Есть ли способ
сделать type-safe композицию двух произвольных функций?
Т.е. написать функцию compose типа
forall c a b. (b -> c) -> (a -> b) -> a -> c

которая реализует композицию фунций (в том смысле, в котором это определено
в математике, (compose f g)(x) = f(g(x)))
Сколько строк кода на это потребуется? Что будет с проверкой типов?


Ах вот оно что! Ты хочешь реализацию сложной функции f(g(x))?

Вот реализация на С:

float f(float (*g_func)(float x)){
	
	float a = (*g_func)(x);
	return sin(sqrt(a));

}

float g(float x){
	
	return do_something(x);

}

Совершенно type-safe. Другое дело, что с такой специлизацией это в универсальную библиотеку С не в ставишь, но для конкретных мелких задач вполне подойдет.

На С++ это можно записать совершенно также, но можно применить и ООП-метод.

Например:

template <class T>
class composite_func {

T rep;

composite_func g;

public:

	explicit composite_func(composite_func& gf, T arg = 0.0):
		g(gf), rep(arg){}

	explicit composite_func(T arg = 0.0):
		rep(arg){}

	T operator()(T arg){
		//do something with arg using g
		return arg;
	}

	T operator()(void){
		//do something with rep using g
		return rep;
	}
};

Теперь composite_func можно использовать как обычную функцию. Только инстанциация должна проходить так, например: composite_func<float>();

Такую функцию можно использовать во всех стандартных алгоритмах STL C++. Например:

x = 3.1415;
vector<float> vc(100);
generate(vc.begin(), vc.end(), composite_func(composite_func(x));



Наконец, замечу, что С++ все-таки язык системного программирования, и он оптимизирован именно под системные нужды - скорость, низкоуровневость (привязанность к системным особенностям), ... Я не могу сказать, что он лучше всех, сверх-универсален, "и ваще". :-)

Еще я советую где-нибудь достать христоматийную книгу от Бьерна Страуструпа "Язык программирования С++". У меня дома лежит русский перевод от издательства "БИНОМ" 1999 года. Оригинал был издан Addison-Wesley Longman. Очень полезная книга для тех кто сомневается в достоинствах С++ или плохо с ним знаком.

С другой стороны, при всех достоинствах С++, его сложность, обусловленная большой формализованностью и низкоуровневостью (унаследованных от С) не позволяют использовать его во всех задачах одинаково эффективно... Для простых задач (например, CGI-скрипты) я, например, предпочитаю Python, Perl (в меньшей степени).

Вообще, у меня создалось впечатление, что мы с тобой говорим на разных языках. Это даже не уверенность, а очевидность. Я тебе рассказываю про С++, С, а ты мне про Haskell, Clean... Я очень плохо понимаю Haskell и вижу, что ты не особо знаком с C++... Получается прикольно. :-) Впрочем, я уверен, что это пойдет нам обоим на пользу. И искренне на это надеюсь. :-)

--
Andrei Sosnin
http://zzx.ath.cx

 <!-- : it all depends on your vision : -->



Reply to: