Qual è l'alternativa di programmazione funzionale a un'interfaccia?

14

Se voglio programmare in uno stile "funzionale", con cosa sostituire un'interfaccia?

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

Forse un Tuple<> ?

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

L'unica ragione per cui sto usando un'interfaccia in primo luogo è perché voglio sempre alcune proprietà / metodi disponibili.

Modifica: altri dettagli su ciò che sto pensando / tentando.

Di ', ho un metodo che accetta tre funzioni:

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

Con un'istanza di Bar posso usare questo metodo:

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);

Ma è un po 'un problema perché devo menzionare bar tre volte in una singola chiamata. Inoltre, non intendo davvero che i chiamanti forniscano funzioni da istanze diverse

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

In C #, un interface è un modo per affrontarlo; ma sembra un approccio molto orientato agli oggetti. Mi chiedo se ci sia una soluzione più funzionale: 1) passare insieme il gruppo di funzioni e 2) assicurarsi che le funzioni siano correttamente correlate l'una con l'altra.

    
posta Ðаn 03.03.2013 - 05:17
fonte

5 risposte

6

Non trattare la programmazione funzionale come un rivestimento sottile sulla programmazione imperativa; c'è molto più di una semplice differenza sintattica.

In questo caso, hai un metodo GetID , che implica l'unicità degli oggetti. Questo non è un buon approccio alla scrittura di programmi funzionali. Forse potresti dirci il problema che stai cercando di risolvere e potremmo darti consigli più significativi.

    
risposta data 04.03.2013 - 08:02
fonte
11

Haskell e le sue derivate hanno tipograve che sono simili alle interfacce. Anche se sembra che tu stia chiedendo come fare l'incapsulamento, che è una domanda riguardante i sistemi di tipi. Il sistema di tipo Milner di hindley è comune nei linguaggi funzionali e ha tipi di dati che fanno questo per te in vari modi attraverso le lingue.

    
risposta data 03.03.2013 - 05:41
fonte
5

Ci sono alcuni modi per consentire a una funzione di gestire più input.

Primo e più comune: Polimorfismo parametrico.

Ciò consente a una funzione di agire su tipi arbitrari:

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

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

Che è bello, ma non ti dà il dispatch dinamico che hanno le interfacce OO. Per questo Haskell ha typeclass, Scala ha impliciti, ecc.

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 <+>

Tra questi due meccanismi, puoi esprimere tutti i tipi di comportamenti complessi e interessanti sui tuoi tipi.

    
risposta data 03.03.2013 - 06:08
fonte
1

La regola base di base è che nelle funzioni di programmazione FP fa lo stesso lavoro degli oggetti nella programmazione OO. Puoi chiamare i loro metodi (beh, comunque, il metodo "call") e rispondono secondo alcune regole interne incapsulate. In particolare, ogni linguaggio FP decente là fuori ti permette di avere "variabili d'istanza" nella tua funzione con chiusure / scoping lessicale.

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;
    }
};

Ora la prossima domanda è cosa intendi per interfaccia? Un approccio usa le interfacce nominali (è conforme all'interfaccia se dice che lo fa) - questo di solito dipende molto dalla lingua che stai usando, quindi lasciamolo per quest'ultimo. L'altro modo per definire un'interfaccia è il modo strutturale, vedere quali parametri ricevono e restituiscono. Questo è il tipo di interfaccia che si tende a vedere in linguaggi dinamici a dattilografia e si adatta molto bene a tutti i FP: un'interfaccia è solo il tipo di parametri di input per le nostre funzioni e i tipi restituiti in modo che tutte le funzioni corrispondano al i tipi corretti si adattano all'interfaccia!

Pertanto, il modo più semplice per rappresentare un oggetto che corrisponde a un'interfaccia è semplicemente avere un gruppo di funzioni. Di solito aggiri il brutto passaggio delle funzioni separatamente comprimendole in una sorta di record:

var my_blarfable = {
 get_name: function(){ ... },
 set_name: function(){ ... },
 get_id:   function(){ ... }
}

do_something(my_blarfable)

L'utilizzo di funzioni nude o record di funzioni farà molto per risolvere la maggior parte dei tuoi problemi comuni in modo "senza grassi" senza tonnellate di piastre. Se hai bisogno di qualcosa di più avanzato, a volte le lingue ti offrono funzionalità extra. Un esempio menzionato dalle persone è il tipo di classi Haskell. Le classi di tipi associano essenzialmente un tipo a uno di quei record di funzioni e ti consentono di scrivere le cose in modo che i dizionari siano impliciti e passano automaticamente alle funzioni interne come appropriato.

-- 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)

Una cosa importante da notare sui typeclass è che i dizionari sono associati ai tipi e non ai valori (come succede nel dizionario e nelle versioni OO). Ciò significa che il sistema di tipi non ti consente di mixare "tipi" [1]. Se vuoi una lista di "blargables" o di una funzione binaria che va a blargables allora le typeclass vincoleranno tutto per essere lo stesso tipo mentre l'approccio del dizionario ti permetterà di avere blargables di origini diverse (la versione migliore dipende molto da ciò che sei facendo)

[1] Esistono modi avanzati di fare "tipi esistenziali", ma di solito non ne vale la pena.

    
risposta data 04.03.2013 - 17:25
fonte
0

Penso che sarà specifico per la lingua. Vengo da uno sfondo lispy. In molti casi le interfacce con lo stato rompono il modello funzionale fino a un certo punto. Quindi, per esempio, CLOS è dove LISP è meno funzionale e più vicino a un linguaggio imperativo. In genere, i parametri di funzione richiesti combinati con metodi di livello superiore sono probabilmente ciò che stai cercando.

;; 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)))
    
risposta data 03.03.2013 - 17:06
fonte

Leggi altre domande sui tag