La regla general básica es que en las funciones de programación FP hacen el mismo trabajo que los objetos en la programación OO. Puede llamar a sus métodos (bueno, el método de "llamada" de todos modos) y responden de acuerdo con algunas reglas internas encapsuladas. En particular, cada lenguaje FP decente le permite tener "variables de instancia" en su función con cierres / alcance léxico.
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;
}
};
Ahora la siguiente pregunta es, ¿qué quieres decir con una interfaz? Un enfoque es el uso de interfaces nominales (se ajusta a la interfaz si así lo dice); este generalmente depende mucho del idioma que esté utilizando, así que vamos a dejarlo para este último. La otra forma de definir una interfaz es la forma estructural, ver qué parámetros reciben y devuelven. Este es el tipo de interfaz que tiende a ver en lenguajes dinámicos de tipo pato y se adapta muy bien a todos los FP: una interfaz son solo los tipos de parámetros de entrada para nuestras funciones y los tipos que devuelven, por lo que todas las funciones coinciden con los tipos correctos se ajustan a la interfaz!
Por lo tanto, la forma más sencilla de representar un objeto que coincida con una interfaz es simplemente tener un grupo de funciones. Por lo general, evita la fealdad de pasar las funciones por separado al empaquetarlas en algún tipo de registro:
var my_blarfable = {
get_name: function(){ ... },
set_name: function(){ ... },
get_id: function(){ ... }
}
do_something(my_blarfable)
El uso de funciones desnudas o registros de funciones será de gran ayuda para resolver la mayoría de sus problemas comunes de una manera "libre de grasa" sin toneladas de repetitivo. Si necesita algo más avanzado que eso, a veces los idiomas le brindan funciones adicionales. Un ejemplo que la gente mencionó son las clases de tipo Haskell. Las clases de tipos esencialmente asocian un tipo con uno de esos registros de funciones y le permite escribir cosas para que los diccionarios sean implícitos y pasen automáticamente a las funciones internas según corresponda.
-- 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)
Sin embargo, una cosa importante a tener en cuenta sobre las clases de tipos es que los diccionarios están asociados con los tipos, y no con los valores (como lo que sucede en el diccionario y las versiones OO). Esto significa que el sistema de tipos no le permite mezclar "tipos" [1]. Si desea una lista de "blargables" o una función binaria que lleve a blargables, las clases de tipos restringirán que todo sea del mismo tipo, mientras que el enfoque del diccionario le permitirá tener blargables de diferentes orígenes (qué versión es mejor depende mucho de lo que sea) haciendo)
[1] Hay formas avanzadas de hacer "tipos existenciales" pero generalmente no vale la pena.