Simulazione di oggetti nella programmazione funzionale

3

In FP vs. OO, dalle trincee , Dice Michael Fogus:

  • Whenever I write some code to deal with data about people then functional programming seems to work best.

  • Whenever I write some code to simulate people then object-oriented programming seems to work best.

La mia domanda è: in un linguaggio FP puro, come si "simulano gli oggetti"? Alcune lingue definiamo un Modulo , ma quali sono le altre opzioni? Esistono modelli di progettazione specifici per affrontare questo specifico

    
posta 53777A 28.07.2016 - 11:52
fonte

2 risposte

8

Gli oggetti riguardano l'astrazione funzionale e dispatch dinamico . L'astrazione funzionale è ovviamente banale nei linguaggi funzionali. La spedizione dinamica può essere implementata utilizzando le funzioni di prima classe, di nuovo quelle dovrebbero essere disponibili in un linguaggio funzionale.

Fondamentalmente, un oggetto è una funzione di invio che restituisce funzioni (metodi) che sono annidate in una chiusura comune (stato condiviso).

function makeNewObject(sharedState) {
  const add = function (amount) { return makeNewObject(sharedState + amount);};
  const sub = function (amount) { return makeNewObject(sharedState - amount);};

  const toString = function () { return "I am an object with value: " + sharedState; }

  return function (method) {
    if (method === 'add')      return add;
    if (method === 'sub')      return sub;
    if (method === 'toString') return toString;
  }
}

const iAmAnObject = makeNewObject(42);
const iAmAlsoAnObject = iAmAnObject("add")(23);

console.log(iAmAlsoAnObject("toString")())
// I am an object with value: 65

Si può notare che questo è essenzialmente il modo in cui gli oggetti vengono implementati in ECMAScript, eccetto il fatto che il if cascade viene sostituito con un hashtable (chiamato in modo confuso "object" in ECMAScript) e c'è un po 'di zucchero sintattico attorno alle funzioni del costruttore con la parola chiave new . Ciò non è terribilmente sorprendente, dal momento che ECMAScript è basato su Scheme e quanto sopra è fondamentalmente il modo in cui si implementano gli oggetti in λ-calculus e Scheme.

    
risposta data 28.07.2016 - 17:05
fonte
3

@ La risposta di JorgWMittag fornisce un'ottima base teorica su un concetto teorico molto importante: un oggetto può essere simulato utilizzando una chiusura che seleziona il comportamento in base a un parametro. Sfortunatamente, questo approccio ha alcuni problemi:

  • Devi scrivere un codice per interpretare i singoli messaggi ricevuti da un oggetto, che è qualcosa che di solito vorresti che facesse la tua lingua per te
  • Nei linguaggi tipizzati staticamente avrai problemi con diversi metodi che gestiscono diversi tipi di argomenti.

Una possibile soluzione per i due problemi di cui sopra è quella di codificare il protocollo usando un tipo di dati algebrico. Ad esempio, in Haskell, il codice di Jorg potrebbe essere simile a questo:

data MyObjectProtocol = MyObjectProtocol_Add Int |
                        MyObjectProtocol_Sub Int | 
                        MyObjectProtocol_ToString
type MyObject = MyObjectProtocol -> Either MyObject String

makeNewObject :: Int -> MyObject
makeNewObject state = handleRequests
    where
       handleRequests (MyObjectProtocol_Add amount) = Left $ makeNewObject $ state + amount
       handleRequests (MyObjectProtocol_Sub amount) = Left $ makeNewObject $ state - amount
       handleRequests (MyObjectProtocol_ToString) = Right $ "I am an object with value: " ++ show state

Questo lascia ancora un paio di problemi:

  • I vari tipi di restituzione delle funzioni devono essere codificati in un altro tipo, al quale dovrai associare il pattern quando usi l'oggetto, che è molto lontano dal comodo.
  • Il modo in cui l'ho fatto non può gestire un metodo che altera entrambi l'oggetto e restituisce un valore in un modo semplice. Probabilmente il modo migliore per risolverlo è trasformare l'oggetto in una monade, ma ciò rende l'implementazione piuttosto complicata.

Sarebbe meglio, quindi, utilizzare più funzioni per codificare i metodi dell'oggetto e implementare qualcosa di simile a un dispatch virtuale usato dalla maggior parte dei linguaggi OO oggi, ovvero memorizzando una tabella di puntatori del metodo virtuale (cioè funzioni ) nell'oggetto. È quindi possibile avere una serie di funzioni standard che recuperano il metodo virtuale dall'oggetto ed eseguirlo. Questo potrebbe apparire in questo modo:

data MyObject = MyObject (Int -> MyObject) 
                         (Int -> MyObject)
                         (String)
myObject_add :: MyObject -> Int -> MyObject
myObject_add (MyObject f _ _) amount = f amount
myObject_sub :: MyObject -> Int -> MyObject
myObject_sub (MyObject _ f _) amount = f amount
myObject_toString :: MyObject -> String
myObject_toString (MyObject _ _ f) = f

makeNewObject :: Int -> MyObject
makeNewObject state = MyObject add sub toString where
    add amount = makeNewObject $ state + amount
    sub amount = makeNewObject $ state - amount
    toString = "I am an object with value: " ++ show state

Il risultato è un po 'più piatto nella definizione del tipo di oggetto, ma lo rende molto più facile per usare l'oggetto. E se hai diverse implementazioni del tipo di oggetto, la quantità di boilerplate per ogni implementazione aggiuntiva è inferiore, quindi questo è sicuramente un modo migliore di lavorare per interfacce che potrebbero avere molte implementazioni.

    
risposta data 29.07.2016 - 00:14
fonte

Leggi altre domande sui tag