Основное правило заключается в том, что в программировании на FP функции выполняются так же, как объекты в OO-программировании. Вы можете вызывать их методы (ну, в любом случае, метод "call"), и они отвечают согласно некоторым инкапсулированным внутренним правилам. В частности, каждый приличный язык FP позволяет вам иметь «переменные экземпляра» в вашей функции с замыканиями / лексическими ограничениями.
var make_OO_style_counter = function(){
return {
counter: 0
increment: function(){
this.counter += 1
return this.counter;
}
}
};
var make_FP_style_counter = function(){
var counter = 0;
return fucntion(){
counter += 1
return counter;
}
};
Теперь следующий вопрос: что вы подразумеваете под интерфейсом? Один из подходов заключается в использовании номинальных интерфейсов (он соответствует интерфейсу, если он говорит, что это так) - обычно он во многом зависит от того, какой язык вы используете, поэтому оставим его для последнего. Другим способом определения интерфейса является структурный способ, позволяющий увидеть, какие параметры получать и возвращать. Это тот интерфейс, который вы обычно видите в динамических языках типа утка, и он очень хорошо подходит для всех FP: интерфейс - это просто типы входных параметров для наших функций и типы, которые они возвращают, поэтому все функции, соответствующие правильные типы соответствуют интерфейсу!
Следовательно, наиболее простой способ представления объекта, соответствующего интерфейсу, состоит в том, чтобы просто иметь группу функций. Обычно вы обходите безобразие передачи функций по отдельности, упаковывая их в какую-то запись:
var my_blarfable = {
get_name: function(){ ... },
set_name: function(){ ... },
get_id: function(){ ... }
}
do_something(my_blarfable)
Использование обнаженных функций или записей функций будет иметь большое значение для решения большинства ваших общих проблем «без жира» без тонны образцов. Если вам нужно что-то более продвинутое, иногда языки предоставляют вам дополнительные возможности. Один пример, который упоминали люди, это классы типа Haskell. Классы типов по существу связывают тип с одной из этих записей функций и позволяют вам писать вещи, чтобы словари были неявными и автоматически передавались внутренним функциям по мере необходимости.
-- Explicit dictionary version
-- no setters because haskell doesn't like mutable state.
data BlargDict = BlargDict {
blarg_name :: String,
blarg_id :: Integer
}
do_something :: BlargDict -> IO()
do_something blarg_dict = do
print (blarg_name blarg_dict)
print (blarg_id blarg_dict)
-- Typeclass version
class Blargable a where
blag_name :: a -> String
blag_id :: a -> String
do_something :: Blargable a => a -> IO
do_something blarg = do
print (blarg_name blarg)
print (blarg_id blarg)
Однако следует отметить одну важную вещь, касающуюся классов типов: словари связаны с типами, а не со значениями (например, что происходит в словаре и версиях ОО). Это означает, что система типов не позволяет смешивать «типы» [1]. Если вы хотите получить список "blargables" или двоичную функцию, заменяющую blargables, то классы типов будут ограничивать все одним и тем же типом, в то время как подход с использованием словаря позволит вам иметь blargables различного происхождения (какая версия лучше зависит от того, кто вы есть) делать)
[1] Существуют продвинутые способы создания «экзистенциальных типов», но, как правило, это не стоит проблем.