A regra básica é que, nas funções de programação FP, o mesmo trabalho que os objetos na programação OO. Você pode chamar os métodos deles (bem, o método "call" de qualquer maneira) e eles respondem de acordo com algumas regras internas encapsuladas. Em particular, cada linguagem FP decente permite que você tenha "variáveis de instância" em sua função com fechamentos / escopo lexical.
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;
}
};
Agora, a próxima pergunta é o que você quer dizer com interface? Uma abordagem é usar interfaces nominais (em conformidade com a interface, se for o caso) - essa geralmente depende muito de qual idioma você está usando, então deixe para a última. A outra maneira de definir uma interface é a maneira estrutural, vendo quais parâmetros a coisa recebe e retorna. Esse é o tipo de interface que você costuma ver em linguagens dinâmicas e tipadas por patos e se encaixa muito bem com todo o FP: uma interface é apenas o tipo de parâmetro de entrada para nossas funções e o tipo que eles retornam. Todas as funções correspondem ao tipos corretos se encaixam na interface!
Portanto, a maneira mais direta de representar um objeto que corresponde a uma interface é simplesmente ter um grupo de funções. Você costuma contornar a feia de passar as funções separadamente, embalando-as em algum tipo de registro:
var my_blarfable = {
get_name: function(){ ... },
set_name: function(){ ... },
get_id: function(){ ... }
}
do_something(my_blarfable)
O uso de funções simples ou registros de funções ajudará bastante a solucionar a maioria dos problemas comuns de maneira "livre de gordura", sem toneladas de clichê. Se você precisar de algo mais avançado que isso, às vezes os idiomas oferecem recursos extras. Um exemplo mencionado pelas pessoas são as classes do tipo Haskell. As classes de tipo essencialmente associam um tipo a um desses registros de funções e permitem escrever coisas para que os dicionários sejam implícitos e passados automaticamente para as funções internas, conforme apropriado.
-- 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)
Uma coisa importante a ser observada sobre as classes de tipo, porém, é que os dicionários estão associados aos tipos, e não aos valores (como o que acontece no dicionário e nas versões OO). Isso significa que o sistema de tipos não permite misturar "tipos" [1]. Se você deseja uma lista de "blargables" ou uma função binária que leve a blargables, as classes de tipo restringirão tudo para ser do mesmo tipo, enquanto a abordagem do dicionário permitirá que você tenha blargables de diferentes origens (qual versão é melhor depende muito do que você é fazendo)
[1] Existem maneiras avançadas de fazer "tipos existenciais", mas geralmente não vale a pena.