Немного размышлений о синтаксисе функций
От: x-code  
Дата: 05.03.11 10:16
Оценка:
Вопрос касается скорее "эргономики синтаксиса языка", чем философии... Два варианта:
int func(int x)

vs
def func(int x) int

Второй вариант немного длиннее, но ИМХО более удобен, в частности, для объявления локальных функций (внутри других функций), т.к. функции однозначно выделяются в коде по первому ключевому слову (что особенно удобно для code outline и прочих фич IDE). Еще этот вариант хорош тем, что красиво реализуются всякие множественые возвращаемые значения и т.п.
Ключевое слово "def" — насколько оно отражает суть понятия "функция"? Например в синтаксисе Go используется ключевое слово "func" (а в паскале вообще "function" и "procedure", но это уже ИМХО перебор)
Также, если это метод класса, вместо "def" можно использовать другие ключевые слова типа override, virtual и т.п. по смыслу.
Еще есть всякие варианты использования между списком аргументов и списком возвращаемых значений специальных символов-разделителей типа : -> => но ИМХО в объявлении обычной функции (не лямбды) они избыточны.
Что думаете?
Re: Немного размышлений о синтаксисе функций
От: Temoto  
Дата: 05.03.11 13:21
Оценка:
XC>Вопрос касается скорее "эргономики синтаксиса языка", чем философии... Два варианта:
XC>
int func(int x)

XC>vs
XC>
def func(int x) int

XC>Второй вариант немного длиннее, но ИМХО более удобен, в частности, для объявления локальных функций (внутри других функций), т.к. функции однозначно выделяются в коде по первому ключевому слову (что особенно удобно для code outline и прочих фич IDE). Еще этот вариант хорош тем, что красиво реализуются всякие множественые возвращаемые значения и т.п.
XC>Ключевое слово "def" — насколько оно отражает суть понятия "функция"? Например в синтаксисе Go используется ключевое слово "func" (а в паскале вообще "function" и "procedure", но это уже ИМХО перебор)
XC>Также, если это метод класса, вместо "def" можно использовать другие ключевые слова типа override, virtual и т.п. по смыслу.
XC>Еще есть всякие варианты использования между списком аргументов и списком возвращаемых значений специальных символов-разделителей типа : -> => но ИМХО в объявлении обычной функции (не лямбды) они избыточны.
XC>Что думаете?

Думаю, что не стоит разделять объявление лямбд и "обычных" функций. (в кавычках, потому что лямбды это и есть обычные функции, просто слово другое, синоним)

name = def (int x) int — один, универсальный синтаксис для определения функций в любом скопе: хоть глобальном, хоть внутри другой функции.

def будет расшифровываться либо как DEFine, либо как DEfine Function. В первом случае потребуется объяснить почему кроме функций больше ничего не объявляется ключевым словом def, во втором случае сокращение слишком натянуто, правило по которому составлено сокращение слишком сложное, например, почему не DefinE Function. В обоих случаях "def" довольно слабо отражает понятие "функция".
Re: Немного размышлений о синтаксисе функций
От: Honeyman  
Дата: 05.03.11 16:02
Оценка:
Здравствуйте, x-code, Вы писали:

XC>Вопрос касается скорее "эргономики синтаксиса языка", чем философии... Два варианта:
int func(int x)
vs
def func(int x) int

XC>Второй вариант немного длиннее, но ИМХО более удобен, в частности, для объявления локальных функций (внутри других функций), т.к. функции однозначно выделяются в коде по первому ключевому слову (что особенно удобно для code outline и прочих фич IDE). Еще этот вариант хорош тем, что красиво реализуются всякие множественые возвращаемые значения и т.п.
XC>Ключевое слово "def" — насколько оно отражает суть понятия "функция"? Например в синтаксисе Go используется ключевое слово "func" (а в паскале вообще "function" и "procedure", но это уже ИМХО перебор)

Третий вариант был бы ещё лучше:
func f1(x: int): int
...
proc f1(x: int): int

Два разных ключевых слова, func — для чистых функций (без сайд-эффектов), proc — для функций с сайд-эффектами. При желании, можно вынести из множества func ещё более строгое подмножество expr (выражение, в котором отсутствует последовательность вычисления, т.е., «нет операторов “;”», но, возможно, присутствуют let-связи). Понятно, что в норме proc-ы не могут использоваться func-ами и expr-ами, а func-и — expr-ами.

Python-овский def, к примеру, соответствовал бы в такой нотации proc-у, а Python-овская lambda — примерно expr-у (с поправкой на то, что Python не запрещает из expr/lambda вызывать proc/def).
Re[2]: Немного размышлений о синтаксисе функций
От: cvetkov  
Дата: 05.03.11 16:21
Оценка:
Здравствуйте, Honeyman, Вы писали:

H>При желании, можно вынести из множества func ещё более строгое подмножество expr (выражение, в котором отсутствует последовательность вычисления, т.е., «нет операторов “;”», но, возможно, присутствуют let-связи).

а зачем?
Re[2]: Немного размышлений о синтаксисе функций
От: rfq  
Дата: 05.03.11 18:19
Оценка: 9 (1)
Здравствуйте, Temoto, Вы писали:

T>Думаю, что не стоит разделять объявление лямбд и "обычных" функций. (в кавычках, потому что лямбды это и есть обычные функции, просто слово другое, синоним)


T>name = def (int x) int — один, универсальный синтаксис для определения функций в любом скопе: хоть глобальном, хоть внутри другой функции.


Согласен.

Вспоминается Автокод Эльбрус:

_ф32 а_плюс_1 := _функ(_ф64 операция, _ф32 лев, _ф32 прав)(операция(лев, прав)) (_функ(_ф32 a, _ф32 b) (a+b), a, 1);

Но конечно можно было и в более традиционном виде:

% описание константы типа процедура:
_функция вызов(_ф64 операция, _ф32 лев, _ф32 прав)(
операция(лев, прав)
);

% присваивание переменной значения типа "изображение процедуры":
_ф64 сложение :=_функ(_ф32 a, _ф32 b) (a+b);

_ф32 а_плюс_1 := вызов(сложение , a, 1);

Для процедур, не возвращающих значения, использовались ключевые слова _процедура и _проц.
Re[2]: Немного размышлений о синтаксисе функций
От: x-code  
Дата: 05.03.11 19:05
Оценка:
Здравствуйте, Temoto, Вы писали:

T>Думаю, что не стоит разделять объявление лямбд и "обычных" функций. (в кавычках, потому что лямбды это и есть обычные функции, просто слово другое, синоним)


T>name = def (int x) int — один, универсальный синтаксис для определения функций в любом скопе: хоть глобальном, хоть внутри другой функции.


Насчет унифицированного определения в любом скопе согласен (такой унификации кстати вроде бы нет в существующих языках), а лямбды и обычные функции все-же стоит разделять: функции (как глобальные так и локальные) просто описываются, а лямбды — как описываются, так и вызываются в месте своего описания.
Re[3]: Немного размышлений о синтаксисе функций
От: VladD2 Российская Империя www.nemerle.org
Дата: 05.03.11 21:38
Оценка:
Здравствуйте, x-code, Вы писали:

XC>функции (как глобальные так и локальные) просто описываются, а лямбды — как описываются, так и вызываются в месте своего описания.


Это заблуждение становится весьма распространенным.

На самом деле лямбды не вызываются в месте описания. На языке понятном большинству императивных программистов результатом лямбды является указатель на функцию (или делегат). На языке функциональщиков лямбда возвращает функциональное значение (или функциональный объект). Его можно передать в качестве параметра, поместить в поле/переменную, а можно и вызвать сразу. Причем вызов сразу особого смысла не имеет. Намного чаще лямбды используются именно как значения.

В прочем, все это никак не влияет на то, что лямбды и обычные функции можно и нужно отличать.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Немного размышлений о синтаксисе функций
От: hi_octane Беларусь  
Дата: 05.03.11 21:56
Оценка:
XC>Что думаете?
Хорошо, но сильно нехватает ещё синтаксиса для нескольких возвращаемых значений, и имён к возвращаемым значениям. Может так:

def func(x : int, y : double) (a : string, b : bool)
{
}


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

А ещё забыт обобщённый возврат функциональных типов (делегатов если угодно), может так:
def func2(x : int, y : double) (a : string, b : bool) (m : decimal) //вернёт функцию принимающую (a,b) и возвращающую на них (m)
{  
}

def func3 = func2(1, 1.0);

func3( | /*<- тут умный автокомплит подскажет нам что func3 принимает параметры a : string, и b : bool, и возвращает m : decimal */ )

// ну или даже так:
Console.WriteLine( func3(1, 2.0)("aaa", true) );


В общем нужно параметры приравнять к каким-то структурным типам, и обобщить это всё. Тогда Linq/Функции/Делегаты/Структуры/Встроенные типы, станут чем-то одинаковым и очень удобным.
Re: Немного размышлений о синтаксисе функций
От: VladD2 Российская Империя www.nemerle.org
Дата: 05.03.11 22:20
Оценка:
Здравствуйте, x-code, Вы писали:

XC>Вопрос касается скорее "эргономики синтаксиса языка", чем философии... Два варианта:

XC>
int func(int x)

XC>vs
XC>
def func(int x) int

XC>Второй вариант немного длиннее, но ИМХО более удобен,

На самом деле второй вариант не логичен. Параметры обявляются в С-ишном стиле, а возвращаемое значение — нет.
Тогда нужно уж быть последовательным и выбрать стиль:
def int func(int x) // стиль C

или:
def func(x int) int // стиль Go

Во втором случае так же имеет смысл определять переменные так же через def:
def x int = 1; // переменная с явно указанным типом
def x = 1; // с выводом типов

иначе будут неоднозначности которые придется разруливать нетривиальным образом.

Ну, а на мой взгляд очень не плох немерловый стиль:
def func(x : int) : int // стиль Nemerle
def x : int = 1; // переменная с явно указанным типом
def x = 1; // с выводом типов

который является смесью ML-ного и С-ишного стиля (от последнего взята скобочная нотация).
Приятной особенностью такого подхода является то, что двоеточие четко указывает, что далее идет описание типа. Такой код очень хорошо читается взглядом, так как не требует распознавания сложных паттернов. Кроме того это позволяет добавить в язык так называемое уточнение типов. Код "выражение : тип" можно использовать в любом месте кода, чтобы указать компилятору что за тип должен быть у выражения. Это очень удобно для языка поддерживающего вывод типов (к каковым на сегодня уже относится C# и почти относится C++). Получается эдакая стройная универсальная система.

Когда я в первый раз увидел Nemerle, то меня покоробило использование такой "паскалевской" нотации. Но со временем я понял, что это действительно удобно и хорошо (и что это не паскалевская, а ML-ная нотация ).

XC> в частности, для объявления локальных функций (внутри других функций), т.к. функции однозначно выделяются в коде по первому ключевому слову (что особенно удобно для code outline и прочих фич IDE).


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

Во-вторых, такое упрощение жизни парсера не особо то нужно в наши дни. Даже не очень продвинутые алгоритмы парсинга с легкостью выделяют конструкции по "заглядыванию вперед". Для рукописных парсеров или основанных на алгоритмах поддерживающих синтаксические предикаты (ANTLR, PEG и т.п.) это вообще не проблема. К тому же, как было указано в "во-первых" ключевое слово все же не всегда позволяет выявить функцию. Тогда уж лучше использовать префикс fun (или его аналог), чтобы четко видеть, что определяется функцию.

XC>Еще этот вариант хорош тем, что красиво реализуются всякие множественые возвращаемые значения и т.п.


Да не особо. К тому же множественные значения могут быть описаны в виде кортежа, что не создаст проблем и с сишным синтаксисом:
def int * string func(int x) // стиль C


XC>Ключевое слово "def" — насколько оно отражает суть понятия "функция"?


Да не на сколько.

XC>Например в синтаксисе Go используется ключевое слово "func" (а в паскале вообще "function" и "procedure", но это уже ИМХО перебор)


На самом деле длинна ключевого слова не так уж важна для отдельной фунции. Все равно она обычно описывается как минимум на одной отдельной строке. Вот в случае лямбды краткость записи критична. И тут лучше использовать C#-пный:
x => x * x
(x, y) => x * y

или Haskell-евский:
\x -> x * x
\x y -> x * y

стиль.
Для С-подобного языка (со скобочной нотацией) лучше подойдет C#-пный стиль (можно с использованием оператора ->, если он еще не занят в языке), так как список параметров можно рассматривать как кортеж (что делает лямбды очень стройными).

XC>Также, если это метод класса, вместо "def" можно использовать другие ключевые слова типа override, virtual и т.п. по смыслу.


В смысле? Вместо "def" или вместе с "def"?

XC>Еще есть всякие варианты использования между списком аргументов и списком возвращаемых значений специальных символов-разделителей типа : -> => но ИМХО в объявлении обычной функции (не лямбды) они избыточны.


Ну, почему? В принципе неплохим решением является унификация лямбд и функций.
()     => z     // лямбда
x      => x * x // лямбда
(x)    => x * x // лямбда
(x, y) => x * y // лямбда

func1()     => z     // именованная функция
func2(x)    => x * x // именованная функция
func3(x, y) => x * y // именованная функция

// или 

def func1()     => z     // именованная функция
def func2(x)    => x * x // именованная функция
def func3(x, y) => x * y // именованная функция

еще варианты:
()     { z }     // лямбда
x      { x * x } // лямбда
(x)    { x * x } // лямбда
(x, y) { x * y } // лямбда

func1()     { z }     // именованная функция
func2(x)    { x * x } // именованная функция
func3(x, y) { x * y } // именованная функция

// или 

def func1()     { z }     // именованная функция
def func2(x)    { x * x } // именованная функция
def func3(x, y) { x * y } // именованная функция

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

XC>Что думаете?


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

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

Если цель быть ближе к С-шному синтаксису, то все эти изыски не очень хороши. Если цель создать унифицированный и удобный для чтения синтаксис, то вариантов очень много.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Немного размышлений о синтаксисе функций
От: dotneter  
Дата: 06.03.11 08:19
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>В прочем, все это никак не влияет на то, что лямбды и обычные функции можно и нужно отличать.

В чем же их концептуальное различие?
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Talk is cheap. Show me the code.
Re[5]: Немного размышлений о синтаксисе функций
От: hardcase Пират http://nemerle.org
Дата: 06.03.11 09:48
Оценка:
Здравствуйте, dotneter, Вы писали:

VD>>В прочем, все это никак не влияет на то, что лямбды и обычные функции можно и нужно отличать.

D>В чем же их концептуальное различие?

У лямбды нет имени и без Y-комбинатора нельзя выполнить рекурсивный вызов.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[6]: Немного размышлений о синтаксисе функций
От: dotneter  
Дата: 06.03.11 10:04
Оценка:
Здравствуйте, hardcase, Вы писали:

H>Здравствуйте, dotneter, Вы писали:


VD>>>В прочем, все это никак не влияет на то, что лямбды и обычные функции можно и нужно отличать.

D>>В чем же их концептуальное различие?

H>У лямбды нет имени и без Y-комбинатора нельзя выполнить рекурсивный вызов.

inc = x -> x + 1
и вот у нас появилось имя, зачем нужно понятие функции если его можно постоить на базисе лямбды и переменной?
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Talk is cheap. Show me the code.
Re[5]: Немного размышлений о синтаксисе функций
От: VladD2 Российская Империя www.nemerle.org
Дата: 06.03.11 10:07
Оценка:
Здравствуйте, dotneter, Вы писали:

VD>>В прочем, все это никак не влияет на то, что лямбды и обычные функции можно и нужно отличать.

D>В чем же их концептуальное различие?

В назначении. Лямбды, в основном, предназначены для передачи небольших кусков кода в другие функции. Функции же для декомпозиции и инкапсуляции кода. А уж глобальные функции и методы зачастую отличаются еще и семантически.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Немного размышлений о синтаксисе функций
От: Mamut Швеция http://dmitriid.com
Дата: 06.03.11 10:09
Оценка:
VD>>>В прочем, все это никак не влияет на то, что лямбды и обычные функции можно и нужно отличать.
D>>В чем же их концептуальное различие?

H>У лямбды нет имени и без Y-комбинатора нельзя выполнить рекурсивный вызов.


В свете этого:

На языке функциональщиков лямбда возвращает функциональное значение (или функциональный объект). Его можно передать в качестве параметра, поместить в поле/переменную, а можно и вызвать сразу. Причем вызов сразу особого смысла не имеет. Намного чаще лямбды используются именно как значения.


Зачем их различать?


dmitriid.comGitHubLinkedIn
Re[6]: Немного размышлений о синтаксисе функций
От: dotneter  
Дата: 06.03.11 10:25
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, dotneter, Вы писали:


VD>>>В прочем, все это никак не влияет на то, что лямбды и обычные функции можно и нужно отличать.

D>>В чем же их концептуальное различие?

VD>В назначении. Лямбды, в основном, предназначены для передачи небольших кусков кода в другие функции. Функции же для декомпозиции и инкапсуляции кода. А уж глобальные функции и методы зачастую отличаются еще и семантически.

Как же тогда живется схеме у которой afaik функции — сахар поверх
(define inc (lambda (x) (+ x 1))
Talk is cheap. Show me the code.
Re[7]: Немного размышлений о синтаксисе функций
От: FR  
Дата: 06.03.11 10:29
Оценка:
Здравствуйте, dotneter, Вы писали:

H>>У лямбды нет имени и без Y-комбинатора нельзя выполнить рекурсивный вызов.

D>inc = x -> x + 1
D>и вот у нас появилось имя, зачем нужно понятие функции если его можно постоить на базисе лямбды и переменной?

В ML языках по сути так и есть let просто связывает лямбду с именем.
Re[8]: Немного размышлений о синтаксисе функций
От: hardcase Пират http://nemerle.org
Дата: 06.03.11 10:34
Оценка:
Здравствуйте, FR, Вы писали:

FR>В ML языках по сути так и есть let просто связывает лямбду с именем.


А чтобы была рекурсия нужен костыль "let rec". Ну-ну.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[9]: Немного размышлений о синтаксисе функций
От: dotneter  
Дата: 06.03.11 10:42
Оценка: +2
Здравствуйте, hardcase, Вы писали:

H>Здравствуйте, FR, Вы писали:


FR>>В ML языках по сути так и есть let просто связывает лямбду с именем.


H>А чтобы была рекурсия нужен костыль "let rec". Ну-ну.

Это уже вопрос реализации.
var factorial = function (n){ 
if (n == 0)
   return 1;
  else
   return n * factorial(n-1);
}
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Talk is cheap. Show me the code.
Re[10]: Немного размышлений о синтаксисе функций
От: hardcase Пират http://nemerle.org
Дата: 06.03.11 11:05
Оценка:
Здравствуйте, dotneter, Вы писали:

D>Здравствуйте, hardcase, Вы писали:


H>>Здравствуйте, FR, Вы писали:


FR>>>В ML языках по сути так и есть let просто связывает лямбду с именем.


H>>А чтобы была рекурсия нужен костыль "let rec". Ну-ну.

D>Это уже вопрос реализации.

Это хороший вопрос.

D>
D>var factorial = function (n){ 
D>if (n == 0)
D>   return 1;
D>  else
D>   return n * factorial(n-1);
D>}
D>


А как поступать в этом случае?

var factorial = function (x) { return x; }
var factorial = function (n) { 
if (n == 0)
   return 1;
  else
   return n * factorial(n-1); // на что ссылаемся?
}


ML без сигнатуры rec возьмет первое определение, с rec — второе.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[9]: Немного размышлений о синтаксисе функций
От: FR  
Дата: 06.03.11 11:08
Оценка:
Здравствуйте, hardcase, Вы писали:

FR>>В ML языках по сути так и есть let просто связывает лямбду с именем.


H>А чтобы была рекурсия нужен костыль "let rec". Ну-ну.


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