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


15

Se voglio programmare in uno stile "funzionale", con cosa dovrei 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 che siano disponibili determinate proprietà / metodi.


Modifica: qualche dettaglio in più su ciò che sto pensando / tentando.

Dì, 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 Barposso utilizzare 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 'doloroso come devo menzionare bartre volte in una sola chiamata. Inoltre, non intendo davvero che i chiamanti forniscano funzioni di istanze diverse

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

In C #, interfaceè un modo per gestirlo; ma sembra un approccio molto orientato agli oggetti. Mi chiedo se esiste una soluzione più funzionale: 1) passare il gruppo di funzioni insieme e 2) assicurarsi che le funzioni siano correttamente correlate tra loro.


Non lo faresti. Le interfacce per i tipi di dati sono perfette (anche se preferiresti oggetti immutabili).
Telastyn,

1
Il capitolo 2 della SICP parla praticamente di questo.
user16764

7
Dopo aver riletto la tua domanda, sono curioso di sapere quale funzionalità specifica stai cercando di realizzare? Quello che sembra chiedere è come eseguire un programma minime efficace contro un'istanza in uno stile funzionale, il che non ha senso ..
Jimmy Hoffa

La risposta dipende dalla lingua. In Clojure puoi usare clojure.org/protocols , dove l'unica area morbida è il tipo di parametri su cui le funzioni devono operare - sono un oggetto - questo è tutto ciò che sai.
Giobbe

1
Renderlo semplice: una struttura contenente quei puntatori di metodo, oltre a una funzione per inizializzarlo da un'istanza di oggetto. Perché Haskell? ;)
mlvljr

Risposte:


6

Non trattare la programmazione funzionale come una faccetta sottile rispetto alla programmazione imperativa; c'è molto di più di una semplice differenza sintattica.

In questo caso, hai un GetIDmetodo, che implica 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.


3
"Devi pensare in russo."
Ð

Giusto; il mio vero problema non è particolarmente interessante. Ho già funzionato perfettamente in C # ( github.com/JDanielSmith/Projects/tree/master/PictureOfTheDay se vuoi davvero guardare il codice), ma sarebbe divertente farlo in uno stile più funzionale mentre usando ancora C #.
Ð

1
@ Ðаn È davvero una citazione di Firefox (film) (perché è fantastico se lo è)? O è usato da qualche altra parte?
icc97,

2
Laddove sono d'accordo, è un passaggio di paradigma completo dalla programmazione imperiale alla programmazione funzionale, ci sono molti ordini di grandezza più righe di codice scritte in modo imperativo. Quindi molti più casi angolari scritti per sistemi enormi saranno stati trovati con una programmazione imperativa. Ci sono molte buone pratiche nella programmazione imperativa e sapere se tali competenze possono essere tradotte o se non rappresentano un problema in FP è una domanda meritevole. È possibile scrivere codice orribile in FP, quindi questo tipo di domande dovrebbe evidenziare anche le parti positive di FP.
icc97,

11

Haskell e i suoi derivati ​​hanno caratteri tipografici simili alle interfacce. Anche se sembra che ti stia chiedendo come fare l'incapsulamento, che è una domanda riguardante i sistemi di tipo. Il sistema di tipo Hindner Milner è comune nei linguaggi funzionali e ha tipi di dati che lo fanno per te in vari modi attraverso le lingue.


5
+1 per le typeclass: la principale differenza tra una typeclass Haskell e un'interfaccia Java è che la typeclass è associata al tipo dopo che entrambi sono stati dichiarati separatamente. È possibile utilizzare un vecchio tipo attraverso una nuova "interfaccia" con la stessa facilità con cui è possibile utilizzare una vecchia "interfaccia" per accedere a un nuovo tipo. Per nascondere i dati, si nasconde l'implementazione del tipo in un modulo. Almeno secondo Bertrand Meyer della fama di Eiffel , una classe OOP è una sorta di modulo.
Steve314

5

Esistono 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

Il che è bello, ma non ti dà l'invio dinamico che hanno le interfacce OO. Per questo Haskell ha le macchine da scrivere, Scala ha implicazioni, 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.


1
È possibile aggiungere suggerimenti per evidenziare la sintassi quando la lingua nella risposta non corrisponde alla lingua della domanda. Vedi la mia modifica suggerita per esempio.
hugomg

1

La regola empirica di base è che nelle funzioni di programmazione FP svolgono 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 consente di avere "variabili di istanza" nella tua funzione con chiusure / ambito 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 domanda successiva è cosa intendi con un'interfaccia? Un approccio sta usando le interfacce nominali (è conforme all'interfaccia se dice che lo fa) - questo di solito dipende molto dal linguaggio che stai usando, quindi lascialo per quest'ultimo. L'altro modo di definire un'interfaccia è il modo strutturale, vedendo quali parametri la cosa riceve e ritorna. Questo è il tipo di interfaccia che tendi a vedere in linguaggi dinamici, tipizzati da papera e si adatta molto bene a tutti i FP: un'interfaccia è solo i tipi di parametri di input per le nostre funzioni e i tipi che restituiscono, quindi Tutte le funzioni corrispondenti al tipi corretti si adattano all'interfaccia!

Pertanto, il modo più semplice di rappresentare un oggetto corrispondente a un'interfaccia è semplicemente avere un gruppo di funzioni. Di solito si aggira la bruttezza di passare le funzioni separatamente impacchettandole in una sorta di record:

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

do_something(my_blarfable)

L'uso di funzioni nude o di registrazioni di funzioni farà molto per risolvere la maggior parte dei problemi comuni in modo "privo di grassi" senza tonnellate di scaldabagno. Se hai bisogno di qualcosa di più avanzato di quello, a volte le lingue ti offrono funzionalità extra. Un esempio di persone citate sono le classi di tipo Haskell. Le classi di tipi essenzialmente associano un tipo a uno di quei record di funzioni e ti permettono di scrivere cose in modo che i dizionari siano impliciti e vengano automaticamente passati 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 sulle calotte è che i dizionari sono associati ai tipi e non ai valori (come ciò che accade nel dizionario e nelle versioni OO). Ciò significa che il sistema di tipi non consente di mescolare "tipi" [1]. Se vuoi un elenco di "argomenti" o una funzione binaria che porti a argomenti, allora le typeclass vinceranno tutto allo stesso tipo mentre l'approccio del dizionario ti permetterà di avere obiettivi di origini diverse (quale versione è meglio dipende molto da ciò che sei facendo)

[1] Ci sono modi avanzati per fare "tipi esistenziali" ma di solito non vale la pena.


0

Penso che sarà specifico della lingua. Vengo da uno sfondo sfarzoso. In molti casi le interfacce con lo stato interrompono il modello funzionale in una certa misura. Quindi CLOS, ad esempio, è dove LISP è meno funzionale e più vicino a un linguaggio imperativo. Generalmente, 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)))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.