Насколько логично относиться к 'void' или 'Unit' как к обычному типу ?
Скажем, логично ли требовать чтобы код с обычными типами работал и с void ?
К примеру с типами все ясно
function f(a : number) : number {..}
function g() : number {..}
function h(c : number, d : number) {..}
var x = f();
f(g()); // OK
f(x); // OK
h(f(), g()); // OK
А с void ?
function f(a : void) {..}
function g(b : void) {..}
function h(c : void, d : void) {..}
var x = f();
f(g()); // OK ?
f(x); // OK ?
h(f(), g()); // OK ?
Здравствуйте, _NN_, Вы писали:
_NN>Насколько логично относиться к 'void' или 'Unit' как к обычному типу ? _NN>Скажем, логично ли требовать чтобы код с обычными типами работал и с void ?
_NN>А с void ? _NN>
_NN>function f(a : void) {..}
_NN>function g(b : void) {..}
_NN>function h(c : void, d : void) {..}
_NN>var x = f();
_NN>f(g()); // OK ?
_NN>f(x); // OK ?
_NN>h(f(), g()); // OK ?
_NN>
В языках с алгебраическими типами данных Unit — обычный тип, в котором имеется ровно одно значение. В Хаскелле, скажем, это значение () с типом (). То есть я могу написать
> let f () = ()
> :t f
f :: () -> ()
> let h () () = 42
> :t h
h :: Num a => () -> () -> a
> let x = f ()
> f x
()
> h (f ()) (f ())
42
Здравствуйте, _NN_, Вы писали: _NN>Насколько логично относиться к 'void' или 'Unit' как к обычному типу ? _NN>Скажем, логично ли требовать чтобы код с обычными типами работал и с void ?
Вполне логично, это означает "нет результата", пример использования в Scala:
def u():Unit = {}
def i():Int = 1
val t = if(...) f() else i()
t match{
case _:Unit => println("Unit")
case i:Int => println(i)}
Между тем,что я думаю,тем,что я хочу сказать,тем,что я,как мне кажется,говорю,и тем,что вы хотите услышать,тем,что как вам кажется,вы слышите,тем,что вы понимаете,стоит десять вариантов возникновения непонимания.Но всё-таки давайте попробуем...(Э.Уэллс)
Здравствуйте, _NN_, Вы писали:
_NN>Насколько логично относиться к 'void' или 'Unit' как к обычному типу ? _NN>Скажем, логично ли требовать чтобы код с обычными типами работал и с void ?
void и unit это разные вещи.
void — это не тип, а скорее "кортеж без элементов". Поэтому, если фунция не имеет аргументов, в нее синтаксически нельзя передавать какие-бы то ни было аргументы, пусть даже void, точно также как в функцию с одним аргументом синтаксически нельзя передать два и более аргумента.
unit — это полноценный тип, имеющий единственное значние. Это значит, что если одна функция принимает тип unit, а другая возвращает, то вполне можно передать результат возврата — в точности как и с любым другим типом.
При попытке передать void и unit вместо например integer должны быть разные ошибки компиляции: передача void — это то же самое как если бы аргумент вообще не указали
function foo(int x, int y, int z){}
foo(1,,3);
а unit — указали, но другого (несовместимого) типа.
_NN>Насколько логично относиться к 'void' или 'Unit' как к обычному типу ? _NN>Скажем, логично ли требовать чтобы код с обычными типами работал и с void ?
_NN>А с void ? _NN>
_NN>function f(a : void) {..}
_NN>function g(b : void) {..}
_NN>function h(c : void, d : void) {..}
_NN>var x = f();
_NN>f(g()); // OK ?
_NN>f(x); // OK ?
_NN>h(f(), g()); // OK ?
_NN>
ОК, ОК, ОК.
Пока не рассматриваем случай Nullable<void>/void? который уже bool получается, и при этом попадает под требование работы с void как с обычными типами
Очевидно что a,b,c,d реально не используются — будет удобно, если любое значение которое не используется, будет приводиться к void и не компостировать мозг своим типом.
Основное удобство void как типа в Generic'ах. А то в C# эти Action<T> и Func<T> уже достали.
Как пример удобства — допустим у нас есть готовый Dictionary<TKey, TValue> лёгким движением руки через
var hashSet = new Dictionary<int, void>(); эта коллекция превращается в Set. При этом метод hashSet.Add(key : TKey, value : TValue), внезапно начинает компилироваться с одним аргументом hashSet.Add(5), так как void в недостающие места компилятор и сам подсунуть может. Красота
Как элемент мозгового штурма, а что если в языке object будет наследником от void?
Здравствуйте, NeoCode, Вы писали:
NC>Здравствуйте, _NN_, Вы писали:
_NN>>Насколько логично относиться к 'void' или 'Unit' как к обычному типу ? _NN>>Скажем, логично ли требовать чтобы код с обычными типами работал и с void ?
NC>void и unit это разные вещи.
Боюсь люди получат тут разрыв шаблона если будет два пустых значения
NC>void — это не тип, а скорее "кортеж без элементов". Поэтому, если фунция не имеет аргументов, в нее синтаксически нельзя передавать какие-бы то ни было аргументы, пусть даже void, точно также как в функцию с одним аргументом синтаксически нельзя передать два и более аргумента.
NC>unit — это полноценный тип, имеющий единственное значние. Это значит, что если одна функция принимает тип unit, а другая возвращает, то вполне можно передать результат возврата — в точности как и с любым другим типом.
А почему тогда нельзя такое же провести с void ?
Компилятор за нас спрячет детали и выбросит пустые аргументы сам.
Здравствуйте, _NN_, Вы писали:
NC>>void и unit это разные вещи. _NN>Боюсь люди получат тут разрыв шаблона если будет два пустых значения
Есть еще http://en.wikipedia.org/wiki/Bottom_type
_NN>А почему тогда нельзя такое же провести с void ? _NN>Компилятор за нас спрячет детали и выбросит пустые аргументы сам.
Думаю я немного ошибся: http://en.wikipedia.org/wiki/Unit_type
unit — это "улучшенный void", и он же "0-tuple". Но все равно мне не нравится идея передавать в функцию, принимающую void, "результат" другой функции, возвращающей void.
В некоторых языках есть возможность возвращать несколько возвращаемых значений (кортеж). Т.е. возможно например такое
Если такое возможно, то захочется передавать возвращаемый кортеж вместо нескольких аргументов в другую функцию:
function bar(char c, int x, float f, string s) {}
bar('q',foo(),"hello");
кто-то скажет что это красиво, а я считаю что такое только запутает.
Еще хуже, возможность передачи void в void формально (по аналогии) открывает возможность передачи void куда угодно помимо основного списка аргументов. Т.е. в функцию bar() можно вызвать так
Здравствуйте, NeoCode, Вы писали:
NC>В некоторых языках есть возможность возвращать несколько возвращаемых значений (кортеж). Т.е. возможно например такое NC>
function foo() : int, float {return 10, 3.14;}
NC>x,y = foo();[/code] NC>Если такое возможно, то захочется передавать возвращаемый кортеж вместо нескольких аргументов в другую функцию: NC>
function bar(char c, int x, float f, string s) {}
NC>bar('q',foo(),"hello");
NC>кто-то скажет что это красиво, а я считаю что такое только запутает.
Тут в bar передаётся не (char*int*float*string), а (char*(int*float)*string) — это другой тип. Зачем их отождествлять?
Здравствуйте, Don Reba, Вы писали:
DR>Тут в bar передаётся не (char*int*float*string), а (char*(int*float)*string) — это другой тип. Зачем их отождествлять?
Согласен. А например тип int эквивалентен типу int*void? или void*int*void? Или это все-же разные типы?
Здравствуйте, Don Reba, Вы писали:
NC>>Согласен. А например тип int эквивалентен типу int*void? или void*int*void? Или это все-же разные типы? DR>Думаю, все разные. Иначе, как ты уже показал, получается путаница.
Путаница в том, что если void — это отсутствие параметра, а мы туда все-же передаем "возврат" другой функции, возвращающей void,
то что мешает нам передать void в функцию вместо второго, третьего и т.д. отсутствующих параметров?
function v() {}
function foo() {}
function bar(int) {}
foo(); // OK
foo(v()); // OK?
bar(100); // OK
bar(100, v(), v(), v()); // OK???
Здравствуйте, NeoCode, Вы писали:
NC>Путаница в том, что если void — это отсутствие параметра, а мы туда все-же передаем "возврат" другой функции, возвращающей void,
А вот не надо его определять как отсутствие параметра. От этого только одни проблемы и неудобства.
Вспомните математику: функция есть отображение из чего-то в чего-то, не бывает в принципе функций, которые ничего не принимают или ничего не возвращают. Когда вместо void используется unit, он же (), его можно и пустым туплом считать, все получается логично и удобно. А иначе возникает масса специальных случаев, мешающих писать универсальный/обобщенный код.
Здравствуйте, NeoCode, Вы писали:
NC>Думаю я немного ошибся: http://en.wikipedia.org/wiki/Unit_type NC>unit — это "улучшенный void", и он же "0-tuple". Но все равно мне не нравится идея передавать в функцию, принимающую void, "результат" другой функции, возвращающей void.
По-моему, вы слишком много думаете о конкретных типах. Ценность "единого void" — в обобщённом коде.
Вот простой пример: мы хотим сделать тип "типизированный словарь", который хранит отображение ключ->значение.
При этом подразумевается уникальность ключа и бла-бла-бла.
Если у нас есть полноценная поддержка типа void, то тип "множество значений" получается как тривиальный частный случай: Set<TKey> == Dictionary<TKey, void>. А если нет (как, скажем, в дотнете), то нам нужно городить два совершенно несвязанных интерфейса для ISet<TKey> и IDictionary<TKey, TValue>.
Опять же, всякие ФВП приходится писать дважды — для функций, возвращающих значение, и для невозвращающих значение.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, hi_octane, Вы писали:
_>Пока не рассматриваем случай Nullable<void>/void? который уже bool получается, и при этом попадает под требование работы с void как с обычными типами
А в чем проблема ? Это ведь не единичный случай для странного кода
_>Очевидно что a,b,c,d реально не используются — будет удобно, если любое значение которое не используется, будет приводиться к void и не компостировать мозг своим типом.
_>Основное удобство void как типа в Generic'ах. А то в C# эти Action<T> и Func<T> уже достали.
Функциональный тип всех бы спас ( void -> void ) .
_>Как пример удобства — допустим у нас есть готовый Dictionary<TKey, TValue> лёгким движением руки через _>var hashSet = new Dictionary<int, void>(); эта коллекция превращается в Set. При этом метод hashSet.Add(key : TKey, value : TValue), внезапно начинает компилироваться с одним аргументом hashSet.Add(5), так как void в недостающие места компилятор и сам подсунуть может. Красота
По сути ничем это не отличается от анонимного блока возвращающего void, только без дополнительных скобок: F( { G(), G(), G() } );
_>Как элемент мозгового штурма, а что если в языке object будет наследником от void?
А какая разница кто кого наследует.
Это детали реализации, главное чтобы компилятор справился.
Здравствуйте, NeoCode, Вы писали:
NC>Здравствуйте, _NN_, Вы писали:
NC>>>void и unit это разные вещи. _NN>>Боюсь люди получат тут разрыв шаблона если будет два пустых значения
NC>Есть еще http://en.wikipedia.org/wiki/Bottom_type
Я в курсе
NC>кто-то скажет что это красиво, а я считаю что такое только запутает.
Автораскрытие кортежей это плохо.
Зато явно очень удобно (см. Python )
NC>Еще хуже, возможность передачи void в void формально (по аналогии) открывает возможность передачи void куда угодно помимо основного списка аргументов. Т.е. в функцию bar() можно вызвать так NC>
Здравствуйте, Sinclair, Вы писали:
S>По-моему, вы слишком много думаете о конкретных типах. Ценность "единого void" — в обобщённом коде. S>Вот простой пример: мы хотим сделать тип "типизированный словарь", который хранит отображение ключ->значение. S>При этом подразумевается уникальность ключа и бла-бла-бла. S>Если у нас есть полноценная поддержка типа void, то тип "множество значений" получается как тривиальный частный случай: Set<TKey> == Dictionary<TKey, void>. А если нет (как, скажем, в дотнете), то нам нужно городить два совершенно несвязанных интерфейса для ISet<TKey> и IDictionary<TKey, TValue>. S>Опять же, всякие ФВП приходится писать дважды — для функций, возвращающих значение, и для невозвращающих значение.
Я не против void как полноценного типа, только за. Просто хочется понять, как будут выглядеть многочисленные частные случаи и к чему это приведет.
В частности, допустим, функция объявлена например как
function foo(void, int, void);
(такое может получиться, например, из шаблонной функции при подстановке void в параметры шаблона)
Должен ли компилятор "сворачивать" ее к виду "foo(int)"?
Если должен (а я так понимаю из примера с Map и Set, ради этого все и делается), то имеем ли мы право вызвать ее в "оригинальном" виде
foo(v(), 100, v());
или только в "сокращенном"?
По идее, в оригинальном виде тоже можно (полноценный тип же). Но из этого следует возможность расширить еще еще дальше, т.е. втыкать void в любые функции? Или не следует?
Здравствуйте, NeoCode, Вы писали:
NC>Я не против void как полноценного типа, только за. Просто хочется понять, как будут выглядеть многочисленные частные случаи и к чему это приведет. NC>В частности, допустим, функция объявлена например как NC>
function foo(void, int, void);
NC>(такое может получиться, например, из шаблонной функции при подстановке void в параметры шаблона) NC>Должен ли компилятор "сворачивать" ее к виду "foo(int)"?
Я считаю, что должен.
NC>Если должен (а я так понимаю из примера с Map и Set, ради этого все и делается), то имеем ли мы право вызвать ее в "оригинальном" виде NC>
foo(v(), 100, v());
NC>или только в "сокращенном"? NC>По идее, в оригинальном виде тоже можно (полноценный тип же). Но из этого следует возможность расширить еще еще дальше, т.е. втыкать void в любые функции? Или не следует?
Не вижу проблемы втыкать void.
Более того его и сейчас можно воткнуть вполне легально:
В данном случае синтаксически явно (скобками) выделен аргумент, внутри которого может быть что угодно. В языках, в которых блоки кода могут возвращать значения, можно воткнуть в аргмент целый кусок кода с циклами, условыми переходами и вероятно даже объявлениями классов. Против этого я ничего не имею.
В случае void становятся возможными вставки void просто в основной список аргументов, т.е. со стороны это выглядит именно как передача полноценных аргументов
Здравствуйте, NeoCode, Вы писали: NC>Я не против void как полноценного типа, только за. Просто хочется понять, как будут выглядеть многочисленные частные случаи и к чему это приведет. NC>В частности, допустим, функция объявлена например как NC>
function foo(void, int, void);
NC>(такое может получиться, например, из шаблонной функции при подстановке void в параметры шаблона) NC>Должен ли компилятор "сворачивать" ее к виду "foo(int)"?
Желательно — это специальный сахар.
NC>Если должен (а я так понимаю из примера с Map и Set, ради этого все и делается), то имеем ли мы право вызвать ее в "оригинальном" виде NC>
foo(v(), 100, v());
Да. После раскрытия шаблона у нас будет получаться примерно такой же код, поэтому я не вижу причин заставлять его не работать. NC>По идее, в оригинальном виде тоже можно (полноценный тип же). Но из этого следует возможность расширить еще еще дальше, т.е. втыкать void в любые функции? Или не следует?
Нет, не следует. Попытки скормить default(void) в неожиданные места должны приводить к ошибкам типизации.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, hi_octane, Вы писали: _>Как элемент мозгового штурма, а что если в языке object будет наследником от void?
Тогда void не будет безопасно приводится ни к какому другому типу, что сведёт на нет профит от него.
И это не логично, получается что переменные типа void могут хранить ссылку на любой объект, т.е. не являться "void".
Между тем,что я думаю,тем,что я хочу сказать,тем,что я,как мне кажется,говорю,и тем,что вы хотите услышать,тем,что как вам кажется,вы слышите,тем,что вы понимаете,стоит десять вариантов возникновения непонимания.Но всё-таки давайте попробуем...(Э.Уэллс)