нужна ли перегрузка функций
про паттерны напиши ещё что-нибудь
ну pattern matching - хороший вариант реализации сабжа
не про эти паттерны!
про плюсовые темплейты? Так они вообще порнография, там даже вариативность указать нельзя
Это такой костыль для языков, которые не умеют продвинутую типизацию и создавать суммы типов на лету.Это ты о какой функциональности языков?
про плюсовые темплейты?паттерны же говорит, а не темплейты, кам он
setDoubleFromInt(f, divideIntByFloat(addIntToUnsignedIntToProduceInt(a, b y
вместо
f = (a + b) / y
то да, пора серьезно побороться против перегрузки.
Ну или то есть если тебя не заебет писатьвопрос был о перегрузке функций, а не операторов.
Хорошо. sqrtOfFloat и std::stdVectorOfIntFromTwoStdListOfCharIterators открыты перед тобой!
поясни, что ты имеешь в виду?
Он очень тонко намекает на перегруженную функцию sqrt, шаблонный конструктор std::vector и, кажется, ещё на то, что ты идиот.
А аллокаторы?
Изначальный вопрос касался, разумеется, нормальных OOP языков и тех функций, у которых принципиально разный список аргументов. Например, есть аргументы по умолчанию. И вопрос в том, как автоматически строить алгебраические типы для таких функций.
сейчас у нас приняты две перегруженные функции:
func(Int,Int)
func(Int)
Что эквивалентно одной функции
func( (Int,Int)|Int) где | обозначает анонимный тип полученный сложением данных.
Вопрос звучит просто: быть может стоит отказаться от перегрузки функций в пользу удобных анонимных алгебраических типов? Есть ли такие языки, которые уже отказались?
Хм. А кто сказал, что перегрузка функций - это самый удобный путь? Я же говорил об абстрактном языке. В абстрактном языке есть высокоуровневые типы и соответственно возможность задавать для аргумента этого типа одну-единственную функцию sqrt.Как это решает проблему разного извлечения корня из int и float?
Изначальный вопрос касался, разумеется, нормальных OOP языков и тех функций, у которых принципиально разный список аргументов. Например, есть аргументы по умолчанию. И вопрос в том, как автоматически строить алгебраические типы для таких функций.В чём профит иметь в одной функции реализацию (возможно, абсолютно различную) и всякие match/switch для всех возможных вариантов типа, вместо того чтобы делать это с удобством в отдельных функциях и добавлять реализации для новых типов легко и непринуждённо, не затрагивая уже написанный код?
сейчас у нас приняты две перегруженные функции:
func(Int,Int)
func(Int)
Что эквивалентно одной функции
func( (Int,Int)|Int) где | обозначает анонимный тип полученный сложением данных.
Вопрос звучит просто: быть может стоит отказаться от перегрузки функций в пользу удобных анонимных алгебраических типов? Есть ли такие языки, которые уже отказались?
Как это решает проблему разного извлечения корня из int и float?
class Rootable a where
root :: a -> a
instance Rootable Integer where
root = floor . sqrt . fromIntegral
instance Rootable Float where
root = sqrt
В чём профит иметь в одной функции реализацию (возможно, абсолютно различную) и всякие match/switch для всех возможных вариантов типа, вместо того чтобы делать это с удобством в отдельных функциях и добавлять реализации для новых типов легко и непринуждённо, не затрагивая уже написанный код?в этом случае метод можно использовать как функцию, соответственно делать для неё различные композиции.
Код с match ничем не длиннее чем десять разных функций. Правда и не короче
class Rootable a where root :: a -> a instance Rootable Integer where root = floor . sqrt . fromIntegral instance Rootable Float where root = sqrtМолодец, только это не называется "одна единственная функция" и "высокоуровневый тип". Это называется "интерфейс" и различные имплементации этого интерфейса для разных типов. От перегрузки принципиально ничем не отличается, ну кроме того, что слово "интерфейс" обычно употребляется в ООЯ, а не в хаскелле.
сейчас у нас приняты две перегруженные функции:Мне одному кажется, что это стандартом не особо как перегрузка определяется?
func(Int,Int)
func(Int)
Что эквивалентно одной функции
func( (Int,Int)|Int) где | обозначает анонимный тип полученный сложением данных.
Код с match ничем не длиннее чем десять разных функций. Правда и не корочеНе длиннее, а неудобнее. Во-первых, ты плодишь ненужную вложенность и отступы, сваливая код в кучу. Во-вторых, зачем-то возлагаешь задачу диспетчеризации с компилятора на свой код. Про расширяемость я выше уже сказал.
Я ничего не пропустил? Ты предлагаешь избавиться от перегрузки, прибегнув к перегрузке?Как это решает проблему разного извлечения корня из int и float?class Yobable a where
yoba :: a -> a
instance Yobable Integer where
yoba = floor . sqrt . fromIntegral
instance Yobable Float where
yoba = sqrt
Смотри на стандартный "интерфейс" Ordered в скала (если в хаскеле кто не заметил переменную типа - покажу на примере другого языка)
http://github.com/scala/scala/blob/v2.9.2/src/library/scala...
заметь, что без высокоуровневых типов - никуда.
Я ничего не пропустил? Ты предлагаешь избавиться от перегрузки, прибегнув к перегрузке?ты многое пропустил. Ты пропустил пост, на который я отвечал. Мне было заявлено, что без перегрузки никак нельзя сделать обобщённые операции. Я сказал, что обобщённые операции легко сделать, если позволить конструировать сложные типы.
Смотри на стандартный "интерфейс" Ordered в скала (если в хаскеле кто не заметил переменную типа - покажу на примере другого языка)Ну это самая обычная примесь, trait, они сейчас даже в пхп есть. Их стоит воспринимать как абстрактный класс и да, они полезны. Тем не менее, они никак не помогут тебе написать sqrt, если только ты не будешь сам писать типы, с которыми sqrt работает.
Мне было заявлено, что без перегрузки никак нельзя сделать обобщённые операции.Пруфлинк на мой пост, пожалуйста, в студию. Иначе я тоже уверюсь в мысли, что ты идиот.
Он очень тонко намекает на перегруженную функцию sqrt, шаблонный конструктор std::vector и, кажется, ещё на то, что ты идиот.Так вот, sqrt не нужна перегрузка. Надо просто писать sqrt[A](x : Rootable[A]) : x
Это - не перегрузка.
Тем не менее, они никак не помогут тебе написать sqrt, если только ты не будешь сам писать типы, с которыми sqrt работает.и ни один язык не напишет за тебя библиотеку чисел.
Ну это самая обычная примесь, trait, они сейчас даже в пхп есть.Хм, посмотри чуть правее примеси. Там ты обнаружишь параметризацию примеси через дополнительный тип. Именно этим и интересен пример. Попробуй реализуй это без параметризации типов.
Хм, посмотри чуть правее примеси. Там ты обнаружишь параметризацию примеси через дополнительный тип. Именно этим и интересен пример. Попробуй реализуй это без параметризации типов.Ты видишь параметризацию, и в упор не видишь того, что метод compare(A that) всё равно пишешь ты сам по отдельности для каждого класса. Твоя примесь только навешивает интерфейс для удобства сравнения. Ты так или иначе будешь писать отдельный sqrt для каждого типа чисел, а потом писать какую-нибудь страшную обёртку, которая будет проверять, базовый ли это тип (и вызывать его специфический метод извлечения корня sqrtOfFloat или он реализует придуманный тобой интерфейс Rootable, и тогда можно вызвать его родной тип sqrt. Ну или писать что-нибудь ещё более убогое типа sqrt(new RootableInt(42.getValue
Так вот, sqrt не нужна перегрузка. Надо просто писать sqrt[A](x : Rootable[A]) : xБазовый тип не реализует Rootable. Он также не реализует Squarable, Tanganable, Fuckable и ни один другой интерфейс, который кто-нибудь может изобрести для своих нужд.
Вопрос именно в том, нужно ли современным языкам (не наследию древнего и мрачного прошлого) реализовывать перегрузку функций, когда без не менее удобно и более концептуально.
У многих всё что нужно уже есть. И ничто не мешает языку без перегрузки функций иметь базовую библиотеку с числами и sqrtТо есть, до тебя так и не доходит, что ты всё равно не сможешь в базовых типах реализовать все интерфейсы, которые тебе в будущем понадобятся? До тебя не доходит, что кроме стандартной библиотеки могут быть сторонние модули, чьи типы ты тоже не можешь модифицировать?
я так и не могу понять твоей аргументации. Каким образом перегрузка функций (внутри, между прочим, описанного класса) может помочь в добавлении в этот класс новой функциональности
перегрузка функций (внутри, между прочим, описанного класса)Давно у нас sqrt внутри какого-то класса находится?
Я думаю, в ООП уже давно. В процедурных языках - никогда
что-то мало конструктива. Предлагаю так: конкртеный пример перегруженной функции и переделка этого примера без перегрузки, потому что я, например, в ваших функциональных штуках недостаточно разбираюсь. И пример с корнем недостаточно в тему тут, потому что там смысл самой операции не изменяется, давайте что-нибудь посвежее.
class QString; // класс строки из Qt, хранится в UTF-16
class MyString // моя уличная строка, хранится в UTF-8
{
...
public:
inline MyString& add(const MyString &s)
{
d_ptr->data.append(s->d_ptr.data);
return *this;
}
inline MyString& add(const QString &s)
{
QByteArray data = s.toUtf8;
d_ptr->data.append(data.data;
return *this;
}
};
, твой выход.
class QString // класс строки из Qt. хранится в UTF-16
def Q2Data(st : QString) : ByteData // любой способ преобразования
class MyString {
private var data : ByteData
def add(st : Any) = st match ({
case x : QString => data.append(Q2Data(x
case x : MyString => data.append(x.data)
case _ => throw Exception
}).andThen(_ => this)
}
Скала как раз несовершенна последним пунктом матча - при переходе к матчу теряется информация о типе.
По хорошему надо писать так, но это однозначно дольше.
class QString // класс строки из Qt. хранится в UTF-16
def Q2Data(st : QString) : ByteData // любой способ преобразования
sealed trait ArgumentData
final case class QStringData(string : QString)
final case class MyStringData(string : MyString)
class MyString {
private var data : ByteData
def add(st : ArgumentData) = st match ({
case QStringData(x) => data.append(Q2Data(x
case MyStringData(x) => data.append(x.data)
}).andThen(_ => this)
}
Теперь всё стало типобезопасно. Но появилось куча дополнительной работы. Я вручную задал алгебраический тип. Пойнт топика был в возможности подобные типы задавать не вручную, а делать анонимными, создавая на ходу.
Касательно твоего же примера, у него в скале намного проще решение, не требующее перегрузки всех и каждого метода и позволяющее неявно кастить типы - в качестве костыля к той функциональности, нехватку которой я описал выше.
class QString // класс строки из Qt. хранится в UTF-16
implicit def qstring2mystring(st : QString) : MyString // неявное преобразование
class MyString {
private var data : ByteData
def add(st : MyString) = {
data.append(st.data)
this
}
}
В чем пример с этими строками опять же плох: тут один параметр всегда и по его типу все ясно. Фишка же перегрузки как раз в том, что там компилятор:
1) на стадии компиляции
2) подберет подходящую функцию автоматически.
А если этого нет, то приходится писать самому этот разбор... А если там предлагается какая-то хрень со стороны языка, которая будет помогать диспетчерить между некоторым набором функций, то тут уже мысли расходятся... это получается какая-то рантайм-перегрузка, но это еще требует неплохого рефлекшена в языке.
Да, и давай определимся, о какого рода языках идет речь, вон в питоне нет перегрузки, люди живут, да.
Решение 1, техническая прямая замена конкатенации строк.То есть вместо перегрузки предлагается сделать десяток телодвижений для замены её на полиморфизм?
Это типо самое новое веяние, вместо пары строк написать два десятка?
кстати да, пример со строками дважды плох, потому что тут все решается приведением типов, как и написал.
То есть вместо перегрузки предлагается сделать десяток телодвижений для замены её на полиморфизм?Во-первых, где ты там видел полиморфизм? Там чёткая замена на switch.
Это типо самое новое веяние, вместо пары строк написать два десятка?
Во-вторых, со switch короче, потому что один набор типо-параметров - одна строка + общая шапка, что явно не длинее, чем если бы я делал на каждый типо-параметр шапку и тело функции, к тому же я использую тот факт, что любая из перегруженных функций возвращает this и записываю её одной строкой композицией ".andThen(_ => this)", которую в противно случае пришлось бы писать в каждое перегруженное тело
ладно, вот тебе ишо: иногда новые функции добавляются позже и у тебя нету доступа к исходной функции, ты просто добавляешь еще одну в то же пространство и компилятор начинает ее находить, это типа специализации, тут уже начинается горожение костылей.
super.doSomething.orElse( additional_func )
Во-первых, где ты там видел полиморфизм? Там чёткая замена на switch.Там я его и увидел, а ты что, не видишь?
Во-вторых, со switch короче, потому что один набор типо-параметров - одна строка + общая шапка, что явно не длинее, чем если бы я делал на каждый типо-параметр шапку и тело функции, к тому же я использую тот факт, что любая из перегруженных функций возвращает this и записываю её одной строкой композицией ".andThen(_ => this)", которую в противно случае пришлось бы писать в каждое перегруженное тело
Ты забываешь, что в термин "перегрузка функций" также входит разное количество аргументов и разные возвращаемые значения. Это в этом мультяшном примере у тебя получился код почти такой же длины, как и исходный.
Там я его и увидел, а ты что, не видишь?давай точную цитату, потому что я в затруднении
давай точную цитату, потому что я в затрудненииТы моделируешь полиморзифм своей конструкцией match. Но это совсем не замена перегрузке функций. Одно из назначений - это замена double dispatch для отделения операций над деревом объектов; в языках типа C#, Java или C++ мы вынуждены для этого использовать паттерн Visitor. Конечно, match более мощный, чем этот паттерн.
На второе замечание тоже ответь, что ты будешь делать в случае перегрузки с разным количеством аргументов и разными типами возвращаемых значений?
На второе замечание тоже ответь, что ты будешь делать в случае перегрузки с разным количеством аргументов и разными типами возвращаемых значений?с этим всё туго. Если можно прокатить через типовый параметр и вариативность - то так и буду. Иначе придётся возвращать Any, что некрасиво. Скала, кстати, не умеет перегрузку функций с одинаковым определением списка аргументов и различными выходными значениями. И я понимаю, почему так и в самом деле не стоит делать.
Я не моделирую полиморфизм. Мне просто задали такой частный случай. Если там будут другие аргументы, в частности указанные мною дефолтные и прочие представления - они все лягут в матч, но из этого не происходит полиморфизм.
с этим всё туго. Если можно прокатить через типовый параметр и вариативность - то так и буду. Иначе придётся возвращать Any, что некрасиво. Скала, кстати, не умеет перегрузку функций с одинаковым определением списка аргументов и различными выходными значениями. И я понимаю, почему так и в самом деле не стоит делать.В таком случае напрашивается вывод: ты не знал, что такое перегрузка функций, и зачем она нужна. Ничего красивого и сокращённого в общем случае у тебя не получится. Ты будешь вынужден написать ещё больше кода, чем в случае перегрузки.
Я не моделирую полиморфизм. Мне просто задали такой частный случай. Если там будут другие аргументы, в частности указанные мною дефолтные и прочие представления - они все лягут в матч, но из этого не происходит полиморфизм.И тем не менее, в этом случае ты его смоделировал: для двух несвязанных типов высосал из пальца общий тип и прилепил к нему pattern matching в роли паттерна Visitor.
И тем не менее, в этом случае ты его смоделировал: для двух несвязанных типов высосал из пальца общий типПосмотри на второй пример, развёрнутый. Там не полиморфизм, а алгебраические типы (в скале они делаются через жопу и это официальный ответ разработчиков)
Я не часто встречал необходимость иметь функцию с одним именем и разными возвращаемыми значениями, различия которых не покрываются параметризацией типов. Вот функции с разным набором аргументов и одним и тем же типом возвращаемого значения - часто.
Короче вердикт форум.локала: закапывайте По крайней мере в этом виде.
а свичи твои со временем сломаются, поддерживать их заебесся.с чего бы это? свичи точно так же матчат типы, вообще не вижу разницы
они учитывают взаимосвязи между типами? наследование, пользовательские операции преобразования. А что если позже добавится операция преобразования, которой не было?
давай лучше пример
ожидается, что юзер перегружает ее для всего, что хочет сериализовать.
А вызвается она из какого-нибудь шаблончика.
Так вот: эти типы SomeType будут появляться в будущем, ты о них заранее не знаешь. Добавлять в них методы тоже может оказаться нельзя, потому что они могут быть не подвластными пользователю. Специализация не подходит, потому что может захотеться
Serialize(Archive &ar, SomeTemplate<T> t)
а частичной специализации нету.
Еще один из способов сделать возможной такую сериализацию: добавление каста к одному из типов, для которого уже существует сериализация, например, к одному из стандартных.
ну я там не все понимаю, можно бы и словами. И вообще, что-то я не вижу там твоих свичей. Т.е. если есть слово serialize, то ты просто находишь сериализатор и даешь ссылку, а если более простой случай, то делаешь свич. Неясно, почему любую перегрузку можно будет так заменить.
Мне было заявлено, что без перегрузки никак нельзя сделать обобщённые операции. Я сказал, что обобщённые операции легко сделать, если позволить конструировать сложные типы.
Что в твоем понимании "перегрузка"?
Почему перегрузка типа «инт флоор(флоат); инт флоор(доубле);» отличается от «class (Igolkable a, Lapkable a) => Yozhik a where {prichesat' :: a -> a}», что аж с первой нужно бороться, а вторую приветствовать? Вторая для пацанов, а первая нет?
Там просто решена эта проблема вообще без перегрузки, поскольку перегрузка не нужна. Фактически любой тип T кастится к Serializable[T], у которого есть все необходимые методы.
Фактически любой тип T кастится к Serializable[T], пакуется в посылку, отсылается в Америку, там его кастят обратно, применяют перегруженный метод, через месяц присылают обратно, все нормально будет.
Поэтому хочется всю эту магию с диспетчеризацией перенести в рамки нормальной типизации.
ну тут уже возникает стандартный трэйд-офф между статикой и динамикой, может в динамически типизированных языках такой подход и более оправдан.
ну тут уже возникает стандартный трэйд-офф между статикой и динамикой, может в динамически типизированных языках такой подход и более оправдан.Он пишет про Scala.
я просто говорю, что это не однозначно лучше, есть свои минусы.
1. Если функция формально определена как действующая на одном типе, для применения ее к другому типу тебе нужен перегруженный конструктор.
2. Перегруженный конструктор заколотит в ящик копию объекта. В глубине функции, после вскрытия ящика, нужно все равно перегруженное действие.
Так?
Можешь объявить, что перегрузка - это только Луи-Наполеон Бонапарт, а все остально перегрузкой не является. Тогда будет ясно, что с перегрузкой было покончено еще в XIX веке. Путин так с экономическими и политическими проблемами борется. Дешево и эффективо.
Как я уже говорил, можно заменить перегрузку автогенерируемыми алгебраическими типами (посмотри что это такое в хаскеле)
//Так нельзя сделать, потому что f будет иметь сигнатуру double(*double)
auto f = &sqrt;
f(42); // 42 здесь просто скастуется в дабл
f(38.5);
Айвенго не хочет кастов. Как ты меня уже замучил тем, что майк называет полиморфизмом. Для разнообразия хоть один пример с разным списком параметров приведи.
Айвенго не хочет кастов. Как ты меня уже замучил тем, что майк называет полиморфизмом. Для разнообразия хоть один пример с разным списком параметров приведи.Кажется, я ошибся, айванга даже сам не понимает, чего хочет.
Слушай, ты правда не понимаешь, что в моём примере можно было написать
auto f = &someverynicefunc;
char x = f("", "idiot");
bool y = f(&sqrt);
int j = f(std::vector({"idiots", "are", "everywhere"};
и смысл примера не изменился бы ни на йоту?
У тебя был один "истинный" тип, остальные к нему приводится. Я хочу создания промежуточного анонимного типа, к которому приводятся все остальные на равных. И разумеется при сохранении типизации
Для разнообразия хоть один пример с разным списком параметров приведи.Давай, перепиши в точности, не пропуская строк кода, пример:
double Round(double val, int precision, round_interface* ri)
{
code line 1;
code line 2;
code line 3;
code line 4;
code line 5;
}
double Round(float val, int precision, round_interface* ri)
{
code line 6;
code line 7;
code line 8;
code line 9;
code line 10;
}
int Round(double val, int precision)
{
code line 11;
code line 12;
code line 13;
code line 14;
code line 15;
}
int Round(float val, int precision)
{
code line 16;
code line 17;
code line 18;
code line 19;
code line 20;
}
EDIT: Сменил тип возвращаемого значения в первых двух функциях.
trait Roundable[+T]{
def round( from : Any ) : T
}
implicit object DoubleRoundable extends Roundable[Double] {
def round ( from : Any) : Double = from match {
case (value : Double, precision : Int, inteface : round_interface) => {
line6
line7
line8
line9
line10
}
case (value : Float, precision : Int, interface : round_interface) => {
line1
line2
line3
line4
line5
}
}
}
implicit object IntRoundable extends Roundable[Double] {
def round(from : Any) : Double = from match {
case (value : Double, precision : Int) => {
line16
line17
line18
line19
line20
}
case (value : Float, precision : Int) => {
line11
line12
line13
line14
line15
}
}
}
Не вижу никаких оправданий тому, что в одной области видимости в одно называние складываются столь разные по смыслу функции.Не выискивай отмазок - эти функции абсолютно одинаковы по своему смыслу.
Но и изначально не понятно зачем так извращаться, складывая в общую область видимости инты и даблы, когда эти функции должны быть специфичны для типа.То есть ты утверждаешь, что совеременное веяние в написании setDoubleFromInt(f, divideIntByFloat(addIntToUnsignedIntToProduceInt(a, b y?
codeКода стало больше, он стал менее понятен; кроме этого такой рефакторинг, кажется, убьёт ранее работавший код; и ещё find usages перестанет работать.
Добавлять в них методы тоже может оказаться нельзя, потому что они могут быть не подвластными пользователю.ну в этом проблема мейнстримных языков, чё
правда, кажется даркгрей писал несколько лет назад, что в микрософте это исправили
или не даркгрей - тогда значит не в микрософте
не помню уже
ну в 3й шарп добавили extension методы, да.
Ну это не совсем то. С его помощью нельзя реализовать ранее не реализованный интерфейс.
Ты не можешь его сохранить и применить дальше. То есть перегрузка функций не даёт возможность рассматривать перегруженный метод как объект. Потому что у тебя вместо одного метода их десяток и ты не знаешь заранее какой нужно. Ты можешь выбрать один из них, указав явно типы аргументов, и тут же отбросишь остальные, потому что ты форсировал диспетчеризацию. А отложить её - никак.этот тезис не очевидный.
перегруженная функция sqrt: int sqrt(int) и float sqrt(float) задает функцию T sqrt(T) where T:int|float, которую можно рассматривать как объект, для данного объекта можно откладывать вывод типов на потом и т.д.
int sqrt(float) и float sqrt(int)
следует заменить простую перегрузку на T sqrt(T) where T:int|floatкомпилятор железный и не устает - пусть сам и заменяет.
Только вот эта штука не работает при шизофрении вродевсегда можно обобщить до
int sqrt(float) и float sqrt(int)
int | float sqrt (int | float)
или что тоже самое:
T1 sqrt (T2) where T1:int | float, T2:int | float
компилятор железный и не устает - пусть сам и заменяет.ключевая мысль имхо
всегда можно обобщить доТолько ты так не можешь ограничить возможные конвертации только набором:
code:
int | float sqrt (int | float)
или что тоже самое:
code:
T1 sqrt (T2) where T1:int | float, T2:int | float
# int -> int
# int -> float
# float -> float
Только ты так не можешь ограничить возможные конвертации только набором:да, в таком синтаксисе не получится
если использовать синтаксис из твоего сообщения (или аналог то уже получится:
T2 sqrt(T1)
where T1, T2:(int, float) | (int, int) | (float, float)
то уже получитсяУгу) А потом в функции придётся всё равно самому диспетчеризацией заниматься)
Нет, такие алгебраические типы — это конечно круто, очень удобно и в сочетании с pattern matching'ом даёт много удобства и надёжности, но, блджад, при работе с данными, а не при вызове функций.
Угу) А потом в функции придётся всё равно самому диспетчеризацией заниматься)всю эту фигню пусть делает компилятор, чем ему еще заниматься?..
зы
с я согласен в том, что такие обобщенные методы очень часто полезны, особенно когда используется большое кол-во обобщенных алгоритмов.
но я считаю, что такие обобщения компилятор может и сам сделать на основе обычных перегруженных методов.
но, блджад, при работе с данными, а не при вызове функций.это не понял, расшифруй, пожалуйста
но я считаю, что такие обобщения компилятор может и сам сделать на основе обычных перегруженных методов.ну я тоже считаю, что это вопрос языка. Сам язык должен подразумевать что перегруженные методы - это сахар для обобщённых функций
это не понял, расшифруй, пожалуйстаНу, смотри, если описывать метод в той нотации, которую ты приводишь, то это имеет смысл, если ты после такого объявления вынужден в самом методе писать матчинг:
type MyType = IntInt of (int, int) | IntFloat of (int, float) | FloatFloat of (float, float);;
T1 mymethod (T2 arg) where T1, T2: MyType {
match (T1, T2) with
| IntInt -> { ... }
| IntFloat -> { ... }
| FloatFloat -> { ... }
}
Это стрёмно, неудобно и лучше оставить диспетчеризацию компилятору, как ты сам и говоришь.
При работе с данными же это удобно, потому что обычно стоит вопрос выполнить разные действия именно в зависимости от типа данных, а не от типа метода, который ты хочешь применить:
type MyType = Node of (Leaf, Leaf) | Leaf of int;;
let walk tree = function
| Node (x, y) -> walk x; walk y
| Leaf (x) -> print x;;
Ну, то есть, обычно люди выполняют какие-то действия в зависимости от предполагаемых данных, а не от предполагаемых действий.
Ну, смотри, если описывать метод в той нотации, которую ты приводишь, то это имеет смысл, если ты после такого объявления вынужден в самом методе писать матчинг:Да, это ещё имеет смысл, если ты делаешь такую запись только в интерфейсе, а в реализации по-прежнему пишешь отдельные методы. Но, имхо, в плане читаемости это не слишком удобно.
Ну, смотри, если описывать метод в той нотации, которую ты приводишь, то это имеет смысл, если ты после такого объявления вынужден в самом методе писать матчинг:я, вообще, не понимаю зачем нужно писать то, что компилятор может вывести сам.
паттерн-матчинг можно автоматически сконвертить в перегрузку
перегрузку можно автоматически сконвертить в паттерн-матчинг
из патерн-матчинга или перегрузки можно автоматически построить обобщенный вариант функции
в данном случае, достаточно написать:
sqrt
int -> int {..}
int -> int {..}
float -> float {..}
или
int sqrt(int){..}
float sqrt(int){..}
float sqrt(float){...}
всё остальное (типы для обобщенной функции, перегруженные функции на основе паттерн-матчинга, патерн-матчинг функцию на основе перегрузки и т.д.) может вывести компилятор автоматически
Оставить комментарий
yroslavasako
Мне вот кажется, что перегрузка функция (несколько функций с одинаковыми именами и разным набором аргументов - о перегрузке через разные модификаторы здесь говорить не будем) немножко устарела как концепция. Это такой костыль для языков, которые не умеют продвинутую типизацию и создавать суммы типов на лету.