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

14

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 que ciertas propiedades / métodos estén disponibles.

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

Diga, 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 Bar puedo 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 molesto, ya que tengo que mencionar bar tres veces en una sola llamada. Además, realmente no pretendo que las personas que llaman provean funciones de diferentes instancias

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

En C #, un interface es 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 juntos el grupo de funciones, y 2) garantizar que las funciones estén relacionadas entre sí correctamente.

    
pregunta Ðаn 03.03.2013 - 05:17
fuente

5 respuestas

6

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

En este caso, tiene un método GetID , que implica singularidad de objetos. Este no es un buen enfoque para escribir programas funcionales. Quizás pueda decirnos el problema que está tratando de resolver y podríamos darle un consejo más significativo.

    
respondido por el dan_waterworth 04.03.2013 - 08:02
fuente
11

Haskell y sus derivados tienen clases de tipos que son similares a las interfaces. Aunque suena como si estuvieras preguntando cómo hacer la encapsulación, que es una pregunta con respecto a los sistemas tipográficos. El sistema de tipo Milner posterior es común en los lenguajes funcionales, y tiene tipos de datos que lo hacen de manera diferente en todos los idiomas.

    
respondido por el Jimmy Hoffa 03.03.2013 - 05:41
fuente
5

Hay algunas formas de permitir que una función se ocupe de varias 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 que está bien, pero no te da el envío 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, puedes expresar todo tipo de comportamientos complejos e interesantes en tus tipos.

    
respondido por el jozefg 03.03.2013 - 06:08
fuente
1

La regla básica es que en la programación de FP las funciones 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 decente de PF por ahí 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é usando, así que déjelo para este último. La otra forma de definir una interfaz es la forma estructural, viendo qué parámetros reciben y devuelven. Este es el tipo de interfaz que tiende a ver en lenguajes dinámicos tipográficos y se adapta muy bien a todos los FP: una interfaz son los tipos de parámetros de entrada de 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, evitas la fealdad de pasar las funciones por separado empaquetándolas 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 simples o registros de funciones ayudará en gran medida a 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 ofrecen funciones adicionales. Un ejemplo de las personas mencionadas son las clases de tipo Haskell. Las clases de tipos esencialmente asocian un tipo con uno de esos registros de funciones y le permiten 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 acerca de las clases de tipos es que los diccionarios están asociados con los tipos, y no con los valores (como lo que ocurre 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 los lleve a los blargables, las clases de tipos limitarán a que todo sea del mismo tipo, mientras que el enfoque del diccionario le permitirá tener blargables de diferentes orígenes (la 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.

    
respondido por el hugomg 04.03.2013 - 17:25
fuente
0

Creo que va a ser específico del idioma. Vengo de un fondo lispy En muchos casos, las interfaces con el 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)))
    
respondido por el ipaul 03.03.2013 - 17:06
fuente

Lea otras preguntas en las etiquetas