Функциональные типы и объекты

Теперь, когда более или менее раскрыты основные дизайнерские решения для функций в Neo, можно рассмотреть устройство функциональных типов и объектов. Рассмотрим следующие вопросы:
- Выбор синтаксиса для функциональных типов
- Лямбды и блоки
- Синтаксис для частичного применения

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

Если некоторая функция имеет в качестве аргумента делегат, то в нее может быть передан
- собственно делегат
- функция
- метод класса
- лямбда-функция
- функтор

При этом все перечисленные объекты преобразуются и передаются именно в форме делегата. По способу передачи делегаты подобны объектам-строкам (всевозможным std::string, CString, TString и т.д.), то есть могут передаваться как по значению, так и по ссылке.

Функциональный тип однозначно описывается двумя списками: списком аргументов и списком возвращаемых значений. Среди множества вариантов я выбрал наиболее компактный: два списка, разделенные оператором лямбды "=>".
void=>void t1;
int=>int t2;
(int,int)=>float t3;
(char,bool)=>(double,string) t4;
Внутри самого объекта может быть что угодно: просто указатель на функцию, указатель на метод и this класса, дополнительные параметры функции и/или метода класса. Если рассматривать, к примеру, переменную t2 типа int=>int, то этой переменной можно присвоить:
- обычную функцию, например такую
def Foo(int x) int { return x+10; }
t2 = Foo;
- метод класса
class MyClass {
public def Method(int x) int { return x*2; }
};
MyClass obj;
t2 = obj.Method;
- лямбда-функцию
t2 = x => x+123;

Как видно, все эти функции совместимы по сигнатуре с "int=>int". Что произойдет, если попытаться присвоить функцональной переменной другую, несовместимую по сигнатуре функцию?

Если функция имеет меньше аргументов, чем наш функциональный тип, то присваивание возможно всегда: аргументы, передаваемые в функциональный тип, просто не будут использоваться.
def Bar() : int { return 100; }
t2 = Bar;
t2(33); // в Bar аргумент не передается

Если же функция имеет больше аргументов, то возникает необходимость осуществить частичное применение функции - то есть указать часть аргументов, которые будут сохранены в самом функциональном объекте (аналогично тому, как сохраняется указатель this). В различных языках эта возможность синтаксически реализована по-разному. В основом, используют символ-заполнитель "_" (подчеркивание) для указания аргументов, которые должны остаться аргументами. Например, в Nemerle

def f = WriteLine(_);
f("Частичное применение функции"); 


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

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

def Baz(int x, y, z) int { return x+2*y - 3*z; }
t2 = Baz{_, 20,22};

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

Частичное применение для возвращаемых значений рассмотрю в другой раз.

No comments:

Post a Comment