Или, например, вместо void использовать везде FakeVoid из computation expressions.
Так вот, нельзя ли автоматизировать все это?
Например, добавить структуру
public struct VoidStruct
{
public static Value : VoidStruct = VoidStruct();
}
И научить компилятор делать замены в коде.
Т.е., например:
ExecInContext(_ => {/*...*/});//если тело лямбды имеет тип void
ExecInContext.[VoidStruct](_ => {/*...*/; VoidStruct.Value; });//заменять так
Для случая генериков с указанием "where T:class" использовать класс VoidClass:
public abstract class VoidClass
{
public static Value : VoidClass = null;
private this() {}
}
ExecInContext_Class[T](f : SomeContext -> T) : T
where T : class
{
using(def ctx = SomeContext())
f(ctx);
}
ExecInContext_Class(_ => {/*...*/});//если тело лямбды имеет тип void
ExecInContext_Class.[VoidClass](_ => {/*...*/; VoidClass.Value;});//заменять так
Есть проблема, если в качестве параметра f передается не лямбда, создаваемая по месту, а, например, некая уже существующая функция T->void:
def f : SomeContext->void = ctx => {/*...*/};
//тут какой-то код, для которого требуется, чтобы f была с типом SomeContext->void
ExecInContext(f);//тоже придется оборачивать
В этом случае создается новый объект и передается в качестве параметра.
В случае делегатов это может привести, например, к невозможности отписки от событий.
Здравствуйте, artelk, Вы писали:
A>Так вот, нельзя ли автоматизировать все это?
В F# пошли этим путем и придумали тип unit аналогичный FakeVoid. Результатом стало ухудшение совместимости с дотнетными библиотеками (и так хреновая) и непроизводительные затраты, так как даже возвращение пустой структуры влияет на производительность вызова.
На мой взгляд, можно сделать следующее: FakeVoid можно ввести в состав стандартной библиотеки и, возможно, реализовать некие функции, макросы или операторы для упрощения генерации оберток.
Можно так же сделать закладку в компиляторе, чтобы приведение типов от X -> void к X -> Y автоматически вставляла лямбду типа X -> FakeVoid.
А идея вот встраивание этого дела в язык, без поддержки со стороны рантайма, мне хорошей не кажется.
Можно было бы попробовать для каждой функции генерировать перегрузку для void, но тогда это не будет работать со внешними библиотеками и может привести к генерации кучи ненужных дубликатов.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>На мой взгляд, можно сделать следующее: FakeVoid можно ввести в состав стандартной библиотеки и, возможно, реализовать некие функции, макросы или операторы для упрощения генерации оберток.
Здравствуйте, catbert, Вы писали:
VD>>На мой взгляд, можно сделать следующее: FakeVoid можно ввести в состав стандартной библиотеки и, возможно, реализовать некие функции, макросы или операторы для упрощения генерации оберток.
C>Только назвать его unit.
— Зачем?
— Тсссс! Чтобы никто не догадался!
(с) Операция "Ы".
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, _nn_, Вы писали:
__>А нельзя ли сделать макру которая сама сгенерирует код ?
Вообще конечно можно, но это просто воркэраунд бага в реализации. Идеология подразумевает, что int -> int и int -> void вполне равноправные функции различающиеся только вторым типом.
И совершенное логично, что если первый тип можно использовать в дженериках, то и второй тоже должно быть можно. Так что если надо ехать сейчас, то конечно макра это выход. А вообще, надо что-то изобретать. Например, то, что предложил Влад.
Ага, это первое, что приходит в голову. Только такое дублирование делать не совсем "честно".
Например, если повесить этот макроатрибут на метод интерфейса, то возникнут 2 разных метода, каждый из которых придеться реализовать в наследниках отдельно.
Или вот: часто вместе с генерик классом делается негенерик класс с тем же именем, в который добавляются некие хэлперные статические методы или методы расширения и т.п. Т.е. навестить такой макроатрибут на генерик класс не получится — он сгенерирует негенерик класс с тем же именем и будет неоднозначность.
Генерики могут иметь более одного параметра:
[AddVoidOverload]
F[T1, T2]():void{}
Тут нужно сгенерировать 3 перегрузки:
1. Для F[T1, void] — с сигнатурой F[T1]()
2. Для F[void, T2] — с сигнатурой F[T2]()
3. Для F[void, void] — с сигнатурой F()
Итого, вместе с изначальным вариантом, 2^N перегрузок, где N — число параметров.
Проблема в том, что перегрузки 1. и 2. имеют одинаковую сигнатуру. И нужно добавлять какие-то неочевидные префиксы/постфиксы к названиям методов, чтоб разрешить эту неоднозначность...
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, artelk, Вы писали:
A>>Так вот, нельзя ли автоматизировать все это?
VD>В F# пошли этим путем и придумали тип unit аналогичный FakeVoid.
Скорее FakeVoid — аналог unit
Имхо, лучше его сделать пустым tuple-ом, тем более, что и задается он как "()".
VD>Результатом стало ухудшение совместимости с дотнетными библиотеками (и так хреновая) и непроизводительные затраты, так как даже возвращение пустой структуры влияет на производительность вызова.
То, что возврат пустой структуры это тоже затраты — просто ужас. Не ожидал такой подставы.
Я не предлагаю везде вместо void вставлять unit и на каждый чих вставлять обертки. Обертки будут только когда параметр генерика выводится или явно указывается как void. И причем только для лямбд, возвращающих этот void. И причем даже не всегда. Если лямбда задается по месту при вызове функции, в которую она передается, то оборачивать ее не нужно — достаточно в конец тела лямбды добавить "VoidStruct.Value;".
Дело в том, что, на сегодняшний день, в любом случае, чтобы не копипастить код, приходится писать что-то типа:
Вот и хочется, чтобы язык, по возможности, скрывал этот маразм рантайма.
VD>На мой взгляд, можно сделать следующее: FakeVoid можно ввести в состав стандартной библиотеки и, возможно, реализовать некие функции, макросы или операторы для упрощения генерации оберток.
Тут слишком дофига частных случаев будет при генерации. См. http://rsdn.ru/forum/nemerle/4522085.1.aspx
VD>Можно так же сделать закладку в компиляторе, чтобы приведение типов от X -> void к X -> Y автоматически вставляла лямбду типа X -> FakeVoid.
Ага
VD>А идея вот встраивание этого дела в язык, без поддержки со стороны рантайма, мне хорошей не кажется.
Почти все полезное в Nemerle (чего нет в C#) делается без поддержки рантайма: варианты, неизменяемые переменные, лямбда-функции (спасибо, что не через делегаты)...
VD>Можно было бы попробовать для каждой функции генерировать перегрузку для void, но тогда это не будет работать со внешними библиотеками и может привести к генерации кучи ненужных дубликатов.
Так и есть.
Здравствуйте, Ziaw, Вы писали:
Z>Вообще конечно можно, но это просто воркэраунд бага в реализации. Идеология подразумевает, что int -> int и int -> void вполне равноправные функции различающиеся только вторым типом.
Ну, да. Бага в дизайне дотнета.
Надо поискать в коннекте нет ли подобных предложений, если есть поддержать, если нет завести новое. Поддержка void-а в дженериках была бы полезна всем языкам дотнета. Даже шарпу.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, artelk, Вы писали:
A>А нельзя ли как-нибудь малой кровью добавить возможность параметризовать генерики типом void, раз уж даже такие вещи можно делать:
Предлагаю внести класс Unit в стандартную поставку Nemerle и разрешить void в обобщенных типах.
Кто за ?
VD>Надо поискать в коннекте нет ли подобных предложений, если есть поддержать, если нет завести новое. Поддержка void-а в дженериках была бы полезна всем языкам дотнета. Даже шарпу.