нужна ли перегрузка функций

yroslavasako

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

Marinavo_0507

про паттерны напиши ещё что-нибудь

yroslavasako

ну pattern matching - хороший вариант реализации сабжа

Marinavo_0507

не про эти паттерны!

yroslavasako

про плюсовые темплейты? Так они вообще порнография, там даже вариативность указать нельзя

agaaaa

Это такой костыль для языков, которые не умеют продвинутую типизацию и создавать суммы типов на лету.
Это ты о какой функциональности языков?

Serab

про плюсовые темплейты?
паттерны же говорит, а не темплейты, кам он

apl13

Ну или то есть если тебя не заебет писать
setDoubleFromInt(f, divideIntByFloat(addIntToUnsignedIntToProduceInt(a, b y

вместо
f = (a + b) / y

то да, пора серьезно побороться против перегрузки.

yroslavasako

Ну или то есть если тебя не заебет писать
вопрос был о перегрузке функций, а не операторов.

apl13

Хорошо. sqrtOfFloat и std::vectorOfInt::stdVectorOfIntFromTwoStdListOfCharIterators открыты перед тобой!

yroslavasako

поясни, что ты имеешь в виду?

doublemother

Попробую ответить.
Он очень тонко намекает на перегруженную функцию sqrt, шаблонный конструктор std::vector и, кажется, ещё на то, что ты идиот.

okunek

А аллокаторы?

yroslavasako

Хм. А кто сказал, что перегрузка функций - это самый удобный путь? Я же говорил об абстрактном языке. В абстрактном языке есть высокоуровневые типы и соответственно возможность задавать для аргумента этого типа одну-единственную функцию sqrt.
Изначальный вопрос касался, разумеется, нормальных OOP языков и тех функций, у которых принципиально разный список аргументов. Например, есть аргументы по умолчанию. И вопрос в том, как автоматически строить алгебраические типы для таких функций.
сейчас у нас приняты две перегруженные функции:
func(Int,Int)
func(Int)
Что эквивалентно одной функции
func( (Int,Int)|Int) где | обозначает анонимный тип полученный сложением данных.
Вопрос звучит просто: быть может стоит отказаться от перегрузки функций в пользу удобных анонимных алгебраических типов? Есть ли такие языки, которые уже отказались?

doublemother

Хм. А кто сказал, что перегрузка функций - это самый удобный путь? Я же говорил об абстрактном языке. В абстрактном языке есть высокоуровневые типы и соответственно возможность задавать для аргумента этого типа одну-единственную функцию sqrt.
Как это решает проблему разного извлечения корня из int и float?
Изначальный вопрос касался, разумеется, нормальных OOP языков и тех функций, у которых принципиально разный список аргументов. Например, есть аргументы по умолчанию. И вопрос в том, как автоматически строить алгебраические типы для таких функций.
сейчас у нас приняты две перегруженные функции:
func(Int,Int)
func(Int)
Что эквивалентно одной функции
func( (Int,Int)|Int) где | обозначает анонимный тип полученный сложением данных.
Вопрос звучит просто: быть может стоит отказаться от перегрузки функций в пользу удобных анонимных алгебраических типов? Есть ли такие языки, которые уже отказались?
В чём профит иметь в одной функции реализацию (возможно, абсолютно различную) и всякие match/switch для всех возможных вариантов типа, вместо того чтобы делать это с удобством в отдельных функциях и добавлять реализации для новых типов легко и непринуждённо, не затрагивая уже написанный код?

yroslavasako

Как это решает проблему разного извлечения корня из int и float?

class Rootable a where
root :: a -> a

instance Rootable Integer where
root = floor . sqrt . fromIntegral

instance Rootable Float where
root = sqrt

yroslavasako

В чём профит иметь в одной функции реализацию (возможно, абсолютно различную) и всякие match/switch для всех возможных вариантов типа, вместо того чтобы делать это с удобством в отдельных функциях и добавлять реализации для новых типов легко и непринуждённо, не затрагивая уже написанный код?
в этом случае метод можно использовать как функцию, соответственно делать для неё различные композиции.
Код с match ничем не длиннее чем десять разных функций. Правда и не короче

doublemother

class Rootable a where root :: a -> a instance Rootable Integer where root = floor . sqrt . fromIntegral instance Rootable Float where root = sqrt
Молодец, только это не называется "одна единственная функция" и "высокоуровневый тип". Это называется "интерфейс" и различные имплементации этого интерфейса для разных типов. От перегрузки принципиально ничем не отличается, ну кроме того, что слово "интерфейс" обычно употребляется в ООЯ, а не в хаскелле.

schipuchka1

сейчас у нас приняты две перегруженные функции:
func(Int,Int)
func(Int)
Что эквивалентно одной функции
func( (Int,Int)|Int) где | обозначает анонимный тип полученный сложением данных.
Мне одному кажется, что это стандартом не особо как перегрузка определяется?

doublemother

Код с match ничем не длиннее чем десять разных функций. Правда и не короче
Не длиннее, а неудобнее. Во-первых, ты плодишь ненужную вложенность и отступы, сваливая код в кучу. Во-вторых, зачем-то возлагаешь задачу диспетчеризации с компилятора на свой код. Про расширяемость я выше уже сказал.

apl13

Как это решает проблему разного извлечения корня из int и float?
class Yobable a where
yoba :: a -> a

instance Yobable Integer where
yoba = floor . sqrt . fromIntegral

instance Yobable Float where
yoba = sqrt
Я ничего не пропустил? Ты предлагаешь избавиться от перегрузки, прибегнув к перегрузке? :ooo:

yroslavasako

Рисовать интерфейсы без высокоуровневых типов весьма неудобно.
Смотри на стандартный "интерфейс" Ordered в скала (если в хаскеле кто не заметил переменную типа - покажу на примере другого языка)
http://github.com/scala/scala/blob/v2.9.2/src/library/scala...
заметь, что без высокоуровневых типов - никуда.

yroslavasako

Я ничего не пропустил? Ты предлагаешь избавиться от перегрузки, прибегнув к перегрузке?
ты многое пропустил. Ты пропустил пост, на который я отвечал. Мне было заявлено, что без перегрузки никак нельзя сделать обобщённые операции. Я сказал, что обобщённые операции легко сделать, если позволить конструировать сложные типы.

doublemother

Смотри на стандартный "интерфейс" Ordered в скала (если в хаскеле кто не заметил переменную типа - покажу на примере другого языка)
Ну это самая обычная примесь, trait, они сейчас даже в пхп есть. Их стоит воспринимать как абстрактный класс и да, они полезны. Тем не менее, они никак не помогут тебе написать sqrt, если только ты не будешь сам писать типы, с которыми sqrt работает.

doublemother

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

yroslavasako

Он очень тонко намекает на перегруженную функцию sqrt, шаблонный конструктор std::vector и, кажется, ещё на то, что ты идиот.
Так вот, sqrt не нужна перегрузка. Надо просто писать sqrt[A](x : Rootable[A]) : x
Это - не перегрузка.

yroslavasako

Тем не менее, они никак не помогут тебе написать sqrt, если только ты не будешь сам писать типы, с которыми sqrt работает.
и ни один язык не напишет за тебя библиотеку чисел.

yroslavasako

Ну это самая обычная примесь, trait, они сейчас даже в пхп есть.
Хм, посмотри чуть правее примеси. Там ты обнаружишь параметризацию примеси через дополнительный тип. Именно этим и интересен пример. Попробуй реализуй это без параметризации типов.

doublemother

Хм, посмотри чуть правее примеси. Там ты обнаружишь параметризацию примеси через дополнительный тип. Именно этим и интересен пример. Попробуй реализуй это без параметризации типов.
Ты видишь параметризацию, и в упор не видишь того, что метод compare(A that) всё равно пишешь ты сам по отдельности для каждого класса. Твоя примесь только навешивает интерфейс для удобства сравнения. Ты так или иначе будешь писать отдельный sqrt для каждого типа чисел, а потом писать какую-нибудь страшную обёртку, которая будет проверять, базовый ли это тип (и вызывать его специфический метод извлечения корня sqrtOfFloat или он реализует придуманный тобой интерфейс Rootable, и тогда можно вызвать его родной тип sqrt. Ну или писать что-нибудь ещё более убогое типа sqrt(new RootableInt(42.getValue

doublemother

Так вот, sqrt не нужна перегрузка. Надо просто писать sqrt[A](x : Rootable[A]) : x
Базовый тип не реализует Rootable. Он также не реализует Squarable, Tanganable, Fuckable и ни один другой интерфейс, который кто-нибудь может изобрести для своих нужд.

yroslavasako

Базовые числовые типы - это вопрос core библиотек языка программирования. У многих всё что нужно уже есть. И ничто не мешает языку без перегрузки функций иметь базовую библиотеку с числами и sqrt ничуть не менее удобную, чем базовая библиотека другого языка, содержащая перегрузку функций.
Вопрос именно в том, нужно ли современным языкам (не наследию древнего и мрачного прошлого) реализовывать перегрузку функций, когда без не менее удобно и более концептуально.

doublemother

У многих всё что нужно уже есть. И ничто не мешает языку без перегрузки функций иметь базовую библиотеку с числами и sqrt
То есть, до тебя так и не доходит, что ты всё равно не сможешь в базовых типах реализовать все интерфейсы, которые тебе в будущем понадобятся? До тебя не доходит, что кроме стандартной библиотеки могут быть сторонние модули, чьи типы ты тоже не можешь модифицировать?

yroslavasako

я так и не могу понять твоей аргументации. Каким образом перегрузка функций (внутри, между прочим, описанного класса) может помочь в добавлении в этот класс новой функциональности

doublemother

перегрузка функций (внутри, между прочим, описанного класса)
Давно у нас sqrt внутри какого-то класса находится?

yroslavasako

Я думаю, в ООП уже давно. В процедурных языках - никогда

Serab

что-то мало конструктива. Предлагаю так: конкртеный пример перегруженной функции и переделка этого примера без перегрузки, потому что я, например, в ваших функциональных штуках недостаточно разбираюсь. И пример с корнем недостаточно в тему тут, потому что там смысл самой операции не изменяется, давайте что-нибудь посвежее.

doublemother

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

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;
}
};

, твой выход.

yroslavasako

Решение 1, техническая прямая замена конкатенации строк.

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
}
}

Serab

ну вот эти switch'и я как бэ в рот ебаль.
В чем пример с этими строками опять же плох: тут один параметр всегда и по его типу все ясно. Фишка же перегрузки как раз в том, что там компилятор:
1) на стадии компиляции
2) подберет подходящую функцию автоматически.
А если этого нет, то приходится писать самому этот разбор... А если там предлагается какая-то хрень со стороны языка, которая будет помогать диспетчерить между некоторым набором функций, то тут уже мысли расходятся... это получается какая-то рантайм-перегрузка, но это еще требует неплохого рефлекшена в языке.
Да, и давай определимся, о какого рода языках идет речь, вон в питоне нет перегрузки, люди живут, да.

kokoc88

Решение 1, техническая прямая замена конкатенации строк.
То есть вместо перегрузки предлагается сделать десяток телодвижений для замены её на полиморфизм?
Это типо самое новое веяние, вместо пары строк написать два десятка?

Serab

кстати да, пример со строками дважды плох, потому что тут все решается приведением типов, как и написал.

yroslavasako

То есть вместо перегрузки предлагается сделать десяток телодвижений для замены её на полиморфизм?
Это типо самое новое веяние, вместо пары строк написать два десятка?
Во-первых, где ты там видел полиморфизм? Там чёткая замена на switch.
Во-вторых, со switch короче, потому что один набор типо-параметров - одна строка + общая шапка, что явно не длинее, чем если бы я делал на каждый типо-параметр шапку и тело функции, к тому же я использую тот факт, что любая из перегруженных функций возвращает this и записываю её одной строкой композицией ".andThen(_ => this)", которую в противно случае пришлось бы писать в каждое перегруженное тело

Serab

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

yroslavasako

Тогда я наследую оригинальный класс и использую комбинацию над switch - orElse:
super.doSomething.orElse( additional_func )

kokoc88

Во-первых, где ты там видел полиморфизм? Там чёткая замена на switch.
Там я его и увидел, а ты что, не видишь?
Во-вторых, со switch короче, потому что один набор типо-параметров - одна строка + общая шапка, что явно не длинее, чем если бы я делал на каждый типо-параметр шапку и тело функции, к тому же я использую тот факт, что любая из перегруженных функций возвращает this и записываю её одной строкой композицией ".andThen(_ => this)", которую в противно случае пришлось бы писать в каждое перегруженное тело

Ты забываешь, что в термин "перегрузка функций" также входит разное количество аргументов и разные возвращаемые значения. Это в этом мультяшном примере у тебя получился код почти такой же длины, как и исходный.

yroslavasako

Там я его и увидел, а ты что, не видишь?
давай точную цитату, потому что я в затруднении

kokoc88

давай точную цитату, потому что я в затруднении
Ты моделируешь полиморзифм своей конструкцией match. Но это совсем не замена перегрузке функций. Одно из назначений - это замена double dispatch для отделения операций над деревом объектов; в языках типа C#, Java или C++ мы вынуждены для этого использовать паттерн Visitor. Конечно, match более мощный, чем этот паттерн.
На второе замечание тоже ответь, что ты будешь делать в случае перегрузки с разным количеством аргументов и разными типами возвращаемых значений?

yroslavasako

На второе замечание тоже ответь, что ты будешь делать в случае перегрузки с разным количеством аргументов и разными типами возвращаемых значений?
с этим всё туго. Если можно прокатить через типовый параметр и вариативность - то так и буду. Иначе придётся возвращать Any, что некрасиво. Скала, кстати, не умеет перегрузку функций с одинаковым определением списка аргументов и различными выходными значениями. И я понимаю, почему так и в самом деле не стоит делать.
Я не моделирую полиморфизм. Мне просто задали такой частный случай. Если там будут другие аргументы, в частности указанные мною дефолтные и прочие представления - они все лягут в матч, но из этого не происходит полиморфизм.

kokoc88

с этим всё туго. Если можно прокатить через типовый параметр и вариативность - то так и буду. Иначе придётся возвращать Any, что некрасиво. Скала, кстати, не умеет перегрузку функций с одинаковым определением списка аргументов и различными выходными значениями. И я понимаю, почему так и в самом деле не стоит делать.
В таком случае напрашивается вывод: ты не знал, что такое перегрузка функций, и зачем она нужна. Ничего красивого и сокращённого в общем случае у тебя не получится. Ты будешь вынужден написать ещё больше кода, чем в случае перегрузки.
Я не моделирую полиморфизм. Мне просто задали такой частный случай. Если там будут другие аргументы, в частности указанные мною дефолтные и прочие представления - они все лягут в матч, но из этого не происходит полиморфизм.
И тем не менее, в этом случае ты его смоделировал: для двух несвязанных типов высосал из пальца общий тип и прилепил к нему pattern matching в роли паттерна Visitor.

yroslavasako

И тем не менее, в этом случае ты его смоделировал: для двух несвязанных типов высосал из пальца общий тип
Посмотри на второй пример, развёрнутый. Там не полиморфизм, а алгебраические типы (в скале они делаются через жопу и это официальный ответ разработчиков)
Я не часто встречал необходимость иметь функцию с одним именем и разными возвращаемыми значениями, различия которых не покрываются параметризацией типов. Вот функции с разным набором аргументов и одним и тем же типом возвращаемого значения - часто.

Serab

Да даже когда один параметр, там не просто switch, бывает же, что несколько перегрузок подходит под один вызов, надо выбрать именно наиболее подходящую, а свичи твои со временем сломаются, поддерживать их заебесся.
Короче вердикт форум.локала: закапывайте :) По крайней мере в этом виде.

yroslavasako

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

Serab

они учитывают взаимосвязи между типами? наследование, пользовательские операции преобразования. А что если позже добавится операция преобразования, которой не было?

yroslavasako

давай лучше пример

Serab

ну ок, вот пример: функция Serialize(Archive &ar, SomeType t)
ожидается, что юзер перегружает ее для всего, что хочет сериализовать.
А вызвается она из какого-нибудь шаблончика.
Так вот: эти типы SomeType будут появляться в будущем, ты о них заранее не знаешь. Добавлять в них методы тоже может оказаться нельзя, потому что они могут быть не подвластными пользователю. Специализация не подходит, потому что может захотеться
Serialize(Archive &ar, SomeTemplate<T> t)
а частичной специализации нету.
Еще один из способов сделать возможной такую сериализацию: добавление каста к одному из типов, для которого уже существует сериализация, например, к одному из стандартных.

Serab

ну я там не все понимаю, можно бы и словами. И вообще, что-то я не вижу там твоих свичей. Т.е. если есть слово serialize, то ты просто находишь сериализатор и даешь ссылку, а если более простой случай, то делаешь свич. Неясно, почему любую перегрузку можно будет так заменить.

apl13

Мне было заявлено, что без перегрузки никак нельзя сделать обобщённые операции. Я сказал, что обобщённые операции легко сделать, если позволить конструировать сложные типы.
:facepalm:
Что в твоем понимании "перегрузка"?
Почему перегрузка типа «инт флоор(флоат); инт флоор(доубле);» отличается от «class (Igolkable a, Lapkable a) => Yozhik a where {prichesat' :: a -> a}», что аж с первой нужно бороться, а вторую приветствовать? Вторая для пацанов, а первая нет?

yroslavasako

Там просто решена эта проблема вообще без перегрузки, поскольку перегрузка не нужна. Фактически любой тип T кастится к Serializable[T], у которого есть все необходимые методы.

apl13

Фактически любой тип T кастится к Serializable[T], пакуется в посылку, отсылается в Америку, там его кастят обратно, применяют перегруженный метод, через месяц присылают обратно, все нормально будет. :aaa:

yroslavasako

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

Serab

ну тут уже возникает стандартный трэйд-офф между статикой и динамикой, может в динамически типизированных языках такой подход и более оправдан.

kokoc88

ну тут уже возникает стандартный трэйд-офф между статикой и динамикой, может в динамически типизированных языках такой подход и более оправдан.
Он пишет про Scala.

Serab

я просто говорю, что это не однозначно лучше, есть свои минусы.

apl13

Ну от перегрузки ты все равно никуда не уйдешь.
1. Если функция формально определена как действующая на одном типе, для применения ее к другому типу тебе нужен перегруженный конструктор.
2. Перегруженный конструктор заколотит в ящик копию объекта. В глубине функции, после вскрытия ящика, нужно все равно перегруженное действие.
Так?
Можешь объявить, что перегрузка - это только Луи-Наполеон Бонапарт, а все остально перегрузкой не является. Тогда будет ясно, что с перегрузкой было покончено еще в XIX веке. Путин так с экономическими и политическими проблемами борется. Дешево и эффективо.

yroslavasako

Как я уже говорил, можно заменить перегрузку автогенерируемыми алгебраическими типами (посмотри что это такое в хаскеле)

doublemother

Передайте кто-нибудь Дену, что кажется айванга просто хочет (настолько, что это доставляет ему нестерпимую боль) что-то такое:
//Так нельзя сделать, потому что f будет иметь сигнатуру double(*double)
auto f = &sqrt;
f(42); // 42 здесь просто скастуется в дабл
f(38.5);

yroslavasako

Айвенго не хочет кастов. Как ты меня уже замучил тем, что майк называет полиморфизмом. Для разнообразия хоть один пример с разным списком параметров приведи.

doublemother

Айвенго не хочет кастов. Как ты меня уже замучил тем, что майк называет полиморфизмом. Для разнообразия хоть один пример с разным списком параметров приведи.
Кажется, я ошибся, айванга даже сам не понимает, чего хочет.
Слушай, ты правда не понимаешь, что в моём примере можно было написать
auto f = &someverynicefunc;

char x = f("", "idiot");
bool y = f(&sqrt);
int j = f(std::vector({"idiots", "are", "everywhere"};

и смысл примера не изменился бы ни на йоту?

yroslavasako

У тебя был один "истинный" тип, остальные к нему приводится. Я хочу создания промежуточного анонимного типа, к которому приводятся все остальные на равных. И разумеется при сохранении типизации

kokoc88

Для разнообразия хоть один пример с разным списком параметров приведи.
Давай, перепиши в точности, не пропуская строк кода, пример:

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: Сменил тип возвращаемого значения в первых двух функциях.

yroslavasako

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


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
}
}
}

kokoc88

Не вижу никаких оправданий тому, что в одной области видимости в одно называние складываются столь разные по смыслу функции.
Не выискивай отмазок - эти функции абсолютно одинаковы по своему смыслу.
Но и изначально не понятно зачем так извращаться, складывая в общую область видимости инты и даблы, когда эти функции должны быть специфичны для типа.
То есть ты утверждаешь, что совеременное веяние в написании setDoubleFromInt(f, divideIntByFloat(addIntToUnsignedIntToProduceInt(a, b y?
code
Кода стало больше, он стал менее понятен; кроме этого такой рефакторинг, кажется, убьёт ранее работавший код; и ещё find usages перестанет работать.

Marinavo_0507

Добавлять в них методы тоже может оказаться нельзя, потому что они могут быть не подвластными пользователю.
ну в этом проблема мейнстримных языков, чё
правда, кажется даркгрей писал несколько лет назад, что в микрософте это исправили
или не даркгрей - тогда значит не в микрософте
не помню уже

Serab

ну в 3й шарп добавили extension методы, да.

agaaaa

Ну это не совсем то. С его помощью нельзя реализовать ранее не реализованный интерфейс.

Dasar

Ты не можешь его сохранить и применить дальше. То есть перегрузка функций не даёт возможность рассматривать перегруженный метод как объект. Потому что у тебя вместо одного метода их десяток и ты не знаешь заранее какой нужно. Ты можешь выбрать один из них, указав явно типы аргументов, и тут же отбросишь остальные, потому что ты форсировал диспетчеризацию. А отложить её - никак.
этот тезис не очевидный.
перегруженная функция sqrt: int sqrt(int) и float sqrt(float) задает функцию T sqrt(T) where T:int|float, которую можно рассматривать как объект, для данного объекта можно откладывать вывод типов на потом и т.д.

yroslavasako

Так я и говорю, что следует заменить простую перегрузку на T sqrt(T) where T:int|float. Только вот эта штука не работает при шизофрении вроде
int sqrt(float) и float sqrt(int)

Dasar

следует заменить простую перегрузку на 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

Serab

компилятор железный и не устает - пусть сам и заменяет.
ключевая мысль имхо

doublemother

всегда можно обобщить до
code:
int | float sqrt (int | float)
или что тоже самое:
code:
T1 sqrt (T2) where T1:int | float, T2:int | float
Только ты так не можешь ограничить возможные конвертации только набором:
# int -> int
# int -> float
# float -> float

Dasar

Только ты так не можешь ограничить возможные конвертации только набором:
да, в таком синтаксисе не получится
если использовать синтаксис из твоего сообщения (или аналог то уже получится:

T2 sqrt(T1)
where T1, T2:(int, float) | (int, int) | (float, float)

doublemother

то уже получится
Угу) А потом в функции придётся всё равно самому диспетчеризацией заниматься)
Нет, такие алгебраические типы — это конечно круто, очень удобно и в сочетании с pattern matching'ом даёт много удобства и надёжности, но, блджад, при работе с данными, а не при вызове функций.

Dasar

Угу) А потом в функции придётся всё равно самому диспетчеризацией заниматься)
всю эту фигню пусть делает компилятор, чем ему еще заниматься?..
зы
с я согласен в том, что такие обобщенные методы очень часто полезны, особенно когда используется большое кол-во обобщенных алгоритмов.
но я считаю, что такие обобщения компилятор может и сам сделать на основе обычных перегруженных методов.
но, блджад, при работе с данными, а не при вызове функций.
это не понял, расшифруй, пожалуйста

yroslavasako

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

doublemother

это не понял, расшифруй, пожалуйста
Ну, смотри, если описывать метод в той нотации, которую ты приводишь, то это имеет смысл, если ты после такого объявления вынужден в самом методе писать матчинг:
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;;

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

doublemother

Ну, смотри, если описывать метод в той нотации, которую ты приводишь, то это имеет смысл, если ты после такого объявления вынужден в самом методе писать матчинг:
Да, это ещё имеет смысл, если ты делаешь такую запись только в интерфейсе, а в реализации по-прежнему пишешь отдельные методы. Но, имхо, в плане читаемости это не слишком удобно.

Dasar

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

sqrt
int -> int {..}
int -> int {..}
float -> float {..}

или

int sqrt(int){..}
float sqrt(int){..}
float sqrt(float){...}

всё остальное (типы для обобщенной функции, перегруженные функции на основе паттерн-матчинга, патерн-матчинг функцию на основе перегрузки и т.д.) может вывести компилятор автоматически
Оставить комментарий
Имя или ник:
Комментарий: