Тип `()`

Здравствуйте, уважаемые разработчики! @Admin @ibond

Студенты регулярно вытаются написать такую штуку:

seq.Select(x -> Print(x));

Я каждый раз начинаю объяснять, почему так не работает — и сам не понимаю. Ведь для процедур есть типы наподобие T -> (). По идее, () должен быть самостоятельным типом. И тогда Select в примере выше должен возвращать sequence of ().

Проблемы тут две, но обе, как мне кажется, не фатальные. Первая: типа () нет. И потому sequence of () компилятору непонятно. Вторая: если такой тип разрешить, можно ли создавать значения такого типа и как они должны выглядеть? Это менее тривиальный вопрос. В Хаскеле аналогичный тип () имеет вполне законное единственное значение (). Может быть, ввести таковое и разрешить Select.

Может быть, есть другие пути узаконить тип sequence of ()… Например, сделать его подтипом () (это так, фантазия) ну и считать, что получается процедура. Конечно, всё это не ложится на .NET, где Select принимает Func, а функции, возвращающие void, это другой тип — Action. Ну тогда, может просто ввести отдельный метод расширения ForEach, который будет принимать процедуры (Action'ы)? Но только так, чтобы ему можно было передавать лямбда-процедуры. Это действительно приятней для части людей (мне в том числе), чем обычный foreach писать.

1 лайк

Ну, какие проблемы - берете и пишете:

begin
  var seq := Arr(1,2,3);
  seq.Foreach(procedure(x) -> Print(x));
end.

О, боже! А без procedure никак?

Хотя, спасибо, конечно.

Но про тип () я бы на будущее подумал: предрекаю, что необходимость в нём будет возникать…

Пока никак.

x -> Print(x)

трактуется синтаксисом как функция. На семантике возникает ошибка.

Можно на синтаксисе это никак не трактовать, а на семантике принимать решение, но у нас это пока не реализовано.

А что такое тип ()? Это void? Но тогда значения этого типа создавать нельзя

Спасибо большое за детальный ответ! Будем верить в лучшее…

В функциональном программировании тип void хорошо известен и есть консенсус о том, что название это плохое, потому что тип void, исходя из теоретико-множественных соображений, должен быть одноэлементным типом, а не пустым. Так что в ФП он чаще называется unit type, а не void type (а пустой тип называется bottom, то есть минимальный элемент решётки типов по включению — он тоже используется, но для других целей). Смысл такой: не бывает функций в пустое множество (конструкция int -> void в таком смысле противоречива). А вот в одноэлементное — бывают, причём в связи с тем, что тип (множество) () содержит один элемент, то вполне допустим синтаксический сахар, позволяющий не указывать конкретное возвращаемое значение (оно очевидно). Так в C-подобных языках в void-функциях можно не писать return, а можно где-то в середине писать return. С точки зрения ФП (математики) логичней было бы писать return () (здесь () это единственное значение типа ()). Но в C этот один элемент типа void, который с точки зрения математики всё равно есть, почему-то нельзя называть — этим он похож (внезапно) на иудейского бога.

Из-за этих чисто религиозных соображений и получаются такие проблемы, как необходимость отдельно писать Select, отдельно ForEach. В ФП нет таких проблем и там и то, и то делается с помощью map.

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

type нечто = (нечто);

Что дальше? Приведите код

Я написал в первом посте что дальше. Можно было бы писать Select на процедуре (по идее).

Нет. Процедура - это не функция, возвращающая нечто. Для этого надо пол-компилятора поменять

Понятно, спасибо… Просто тогда тип T -> () вводит в заблуждение.