¿Cuál es la alternativa de programación funcional a una interfaz?

15

Si quiero programar en un estilo "funcional", ¿con qué reemplazaría una interfaz?

interface IFace
{
   string Name { get; set; }
   int Id { get; }
}
class Foo : IFace { ... }

Tal vez un Tuple<>?

Tuple<Func<string> /*get_Name*/, Action<String> /*set_Name*/, Func<int> /*get_Id*/> Foo;

La única razón por la que estoy usando una interfaz en primer lugar es porque siempre quiero ciertas propiedades / métodos disponibles.


Editar: algunos detalles más sobre lo que estoy pensando / intentando.

Digamos que tengo un método que tiene tres funciones:

static class Blarf
{
   public static void DoSomething(Func<string> getName, Action<string> setName, Func<int> getId);
}

Con una instancia de Barpuedo usar este método:

class Bar
{
   public string GetName();
   public void SetName(string value);

   public int GetId();
}
...
var bar = new Bar();
Blarf.DoSomething(bar.GetName, bar.SetName, bar.GetId);

Pero eso es un poco doloroso, ya que tengo que mencionar bartres veces en una sola llamada. Además, realmente no pretendo que las personas que llaman suministren funciones de diferentes instancias

Blarf.DoSomething(bar1.GetName, bar2.SetName, bar3.GetId); // NO!

En C #, una interfacees una forma de lidiar con esto; pero eso parece un enfoque muy orientado a objetos. Me pregunto si hay una solución más funcional: 1) pasar el grupo de funciones juntas, y 2) asegurarse de que las funciones estén correctamente relacionadas entre sí.

Ðаn
fuente
No lo harías Las interfaces para los tipos de datos están perfectamente bien (aunque favorecería los objetos inmutables).
Telastyn
1
El Capítulo 2 del SICP es más o menos sobre esto.
user16764
77
Después de volver a leer su pregunta, tengo curiosidad por saber qué funcionalidad específica está tratando de lograr. Lo que parece estar preguntando es cómo hacer un estilo de programación con efectos secundarios contra una instancia en un estilo funcional, lo que no tiene sentido ...
Jimmy Hoffa
La respuesta dependería del idioma. En Clojure puede usar clojure.org/protocols , donde la única área blanda son los tipos de parámetros en los que las funciones deben operar, son un objeto, eso es todo lo que sabe.
Trabajo
1
Hágalo simple: una estructura que contiene esos punteros de método, más una función para inicializarla desde una instancia de objeto. ¿Por qué Haskell? ;)
mlvljr

Respuestas:

6

No trate la programación funcional como una capa delgada sobre la programación imperativa; Hay mucho más que una simple diferencia sintáctica.

En este caso, tiene un GetIDmétodo que implica la unicidad de los objetos. Este no es un buen enfoque para escribir programas funcionales. Quizás podría decirnos el problema que está tratando de resolver y podríamos darle consejos más significativos.

dan_waterworth
fuente
3
"Debes pensar en ruso".
En
Lo suficientemente justo; Mi verdadero problema no es particularmente interesante. Ya tengo las cosas funcionando bien en C # ( github.com/JDanielSmith/Projects/tree/master/PictureOfTheDay si realmente quieres ver el código), pero sería divertido hacerlo en un estilo más funcional mientras todavía usando C #.
El
1
@ Ðаn ¿Es realmente una cita de Firefox (película) (porque es increíble si lo es)? ¿O se usa en otro lugar?
icc97
2
Donde, como estoy de acuerdo, es un cambio de paradigma completo de la Programación Imperitiva a la Funcional, hay muchos órdenes de magnitud más líneas de código escritas de manera imperitiva. Por lo tanto, muchos de los casos de esquina escritos para sistemas enormes se habrán encontrado con programación imperitiva. Hay muchas buenas prácticas en la programación imperitiva y saber si esas habilidades se pueden traducir o si no son un problema en FP es una buena pregunta. Es completamente posible escribir código horrible en FP, por lo que este tipo de preguntas también deberían resaltar las partes buenas de FP.
icc97
11

Haskell y sus derivados tienen clases de tipo que son similares a las interfaces. Aunque parece que está preguntando sobre cómo hacer la encapsulación, que es una pregunta con respecto a los sistemas de tipos. El sistema de tipos de Milner hindley es común en los lenguajes funcionales, y tiene tipos de datos que hacen esto por usted de diferentes maneras según los idiomas.

Jimmy Hoffa
fuente
55
+1 para las clases de tipos: la principal diferencia entre una clase de tipo Haskell y una interfaz Java es que la clase de tipo se asocia con el tipo después de que ambas se declaran por separado. Puede usar un tipo antiguo a través de una nueva "interfaz" tan fácilmente como puede usar una "interfaz" antigua para acceder a un nuevo tipo. Para ocultar datos, oculta la implementación del tipo en un módulo. Al menos según Bertrand Meyer de la fama de Eiffel , una clase de OOP es una especie de módulo.
Steve314
5

Hay algunas formas de permitir que una función maneje múltiples entradas.

Primero y más común: polimorfismo paramétrico.

Esto permite que una función actúe sobre tipos arbitrarios:

--Haskell Example
id :: a -> a --Here 'a' is just some arbitrary type
id myRandomThing = myRandomThing

head :: [a] -> a
head (listItem:list) = listItem

Lo cual es bueno, pero no le da el despacho dinámico que tienen las interfaces OO. Para esto Haskell tiene clases de tipos, Scala tiene implicits, etc.

class Addable a where
   (<+>) :: a -> a -> a
instance Addable Int where
   a <+> b = a + b
instance Addable [a] where
   a <+> b = a ++ b

--Now we can get that do something similar to OO (kinda...)
addStuff :: (Addable a) => [a] -> a
-- Notice how we limit 'a' here to be something Addable
addStuff (x:[]) = x
addStuff (x:xs) = x <+> addStuff xs
-- In better Haskell form
addStuff' = foldl1 <+>

Entre estos dos mecanismos, puede expresar todo tipo de comportamientos complejos e interesantes en sus tipos.

Daniel Gratzer
fuente
1
Puede agregar pistas de resaltado sintax cuando el idioma de la respuesta no coincida con el idioma de la pregunta. Vea mi edición sugerida, por ejemplo.
Hugomg
1

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.

hugomg
fuente
0

Creo que va a ser específico del idioma. Vengo de un fondo lispy. En muchos casos, las interfaces con estado rompen el modelo funcional hasta cierto punto. Entonces CLOS, por ejemplo, es donde LISP es menos funcional y más cercano a un lenguaje imperativo. En general, los parámetros de función requeridos combinados con métodos de nivel superior son probablemente lo que está buscando.

;; returns a function of the type #'(lambda (x y z &optional a b c)

(defun get-higher-level-method-impl (some-type-of-qualifier) 
    (cond ((eq 'foo) #'the-foo-version)
          ((eq 'bar) #'the-bar-version)))
ipaul
fuente