Domanda Lottando con l'utilizzo di pura programmazione funzionale per risolvere un problema quotidiano


vidi questo post in notizie sugli hacker oggi. Sto lottando con gli stessi problemi di comprensione di come la pura programmazione funzionale mi aiuterà a risolvere un problema del mondo reale. Ho fatto il passaggio dall'imperativo alla programmazione OO 7 anni fa. Sento di averlo padroneggiato e mi è servito bene. Negli ultimi due anni ho imparato alcuni trucchi e concetti nella programmazione funzionale come la mappa e la riduzione, e mi piacciono anche loro. Li ho usati nel mio codice OO e ne sono stato contento, ma quando estrai una serie di istruzioni, posso solo pensare alle astrazioni OO per rendere il codice più carino.

Recentemente ho lavorato su un problema in python, e ho cercato di evitare l'uso di OO per risolverlo. Per la maggior parte la mia soluzione sembra imperativa, e so che potrei renderlo bello e pulito se usassi OO. Ho pensato di postare il problema e forse gli esperti funzionali possono proporre una soluzione bella e funzionale. Posso pubblicare il mio brutto codice se devo, ma preferirei non farlo. :) Ecco il problema:

L'utente può richiedere un'immagine o una miniatura dell'immagine. Se l'utente richiede la miniatura dell'immagine e non esiste ancora, crearla usando il modulo PIL di python. Creare anche un collegamento simbolico all'originale o miniatura con un percorso leggibile dall'uomo, poiché il nome dell'immagine originale è un codice hash e non descrittivo del suo contenuto. Infine, reindirizza al collegamento simbolico di quell'immagine.

In OO creo probabilmente una classe base SymlinkImage, una sottoclasse ThumbnailSymlinkImage e una sottoclasse OriginalSymlinkImage. I dati condivisi (nella classe SymlinkImage) saranno cose come il percorso dell'originale. Il comportamento condiviso creerà il collegamento simbolico. Le sottoclassi implementeranno un metodo chiamato qualcosa come "generare" che sarà responsabile della creazione della miniatura, se applicabile, e che effettuerà la chiamata alla loro superclasse per creare il nuovo collegamento simbolico.


26
2018-05-31 16:39


origine


risposte:


Sì, lo faresti davvero in modo molto diverso usando un approccio funzionale.

Ecco uno schizzo che utilizza il linguaggio di programmazione semplice, predefinito e tipizzato per impostazione predefinita Haskell. Creiamo nuovi tipi per i concetti chiave del tuo problema e suddividiamo il lavoro in funzioni discrete che eseguono un compito alla volta. L'IO e altri effetti collaterali (come la creazione di un collegamento simbolico) sono limitati a determinate funzioni e indicati con un tipo. Per distinguere le due modalità di funzionamento, usiamo un tipo di somma.

--
-- User can request an image or a thumbnail of the image.
-- If the user requests the thumbnail of the image, and it doesn't yet exist, create it using
-- python's PIL module. Also create a symbolic link to the original or
-- thumbnail with a human readable path, because the original image name is a
-- hashcode, and not descriptive of it's contents. Finally, redirect to the
-- symbolic link of that image.
--

module ImageEvent where

import System.FilePath
import System.Posix.Files

-- Request types
data ImgRequest = Thumb ImgName | Full ImgName

-- Hash of image 
type ImgName = String

-- Type of redirects
data Redirect

request :: ImgRequest -> IO Redirect
request (Thumb img) = do
    f <- createThumbnail img
    let f' = normalizePath f
    createSymbolicLink f f'
    return (urlOf f)

request (Full img)  = do
    createSymbolicLink f f'
    return (urlOf f)
    where
        f  = lookupPath img
        f' = normalizePath f

Insieme ad alcuni aiutanti, che lascerò a voi la definizione.

-- Creates a thumbnail for a given image at a path, returns new filepath
createThumbnail :: ImgName -> IO FilePath
createThumbnail f = undefined
    where
        p = lookupPath f

-- Create absolute path from image hash
lookupPath :: ImgName -> FilePath
lookupPath f = "/path/to/img" </> f <.> "png"

-- Given an image, construct a redirect to that image url
urlOf :: FilePath -> Redirect
urlOf = undefined

-- Compute human-readable path from has
normalizePath :: FilePath -> FilePath
normalizePath = undefined

Una soluzione veramente bella estrae il modello richiesta / risposta con una struttura dati per rappresentare la sequenza di comandi da eseguire. Una richiesta arriva, costruisce una struttura puramente per rappresentare il lavoro di cui ha bisogno, e che viene consegnata al motore di esecuzione con cose come la creazione di file e così via. Quindi la logica di base sarà interamente pura funzioni (non che ci sia molta logica di base in questo problema). Per un esempio di questo stile di programmazione veramente puramente funzionale con effetti, raccomando la carta di Wouter Swiestra, "Beauty in the Beast: una semantica funzionale per l'Awkward Squad"


20
2018-05-31 17:07



L'unico modo per cambiare il tuo modo di pensare è cambiare il tuo modo di pensare. Posso dirti cosa ha funzionato per me:

Volevo lavorare su un progetto personale che richiedesse concorrenza. Mi sono guardato intorno e ho trovato l'erlang. L'ho scelto perché pensavo che avesse il miglior supporto per la concorrenza, non per nessun altro motivo. Non avevo mai lavorato con un linguaggio funzionale prima (e solo per confronto, ho iniziato a fare programmazione orientata agli oggetti nei primi anni '90).

Ho letto il libro di Erlang di Armstrong. È stata dura. Ho avuto un piccolo progetto su cui lavorare, ma ho continuato a martellarlo.

Il progetto è stato un fallimento, ma dopo un paio di mesi avevo mappato tutto ciò che avevo in testa a sufficienza da non pensare più agli oggetti nel modo in cui ero abituato.

Ho attraversato una fase in cui mappavo gli oggetti per eliminare i processi, ma non era troppo lungo e ne sono uscito.

Ora cambiare paradigmi è come cambiare lingua o passare da un'auto all'altra. Guidare la macchina di mio padre è diverso dal mio camion, ma non ci vuole molto per abituarsi di nuovo.

Penso che lavorare in Python potrebbe trattenerti, e ti consiglio vivamente di dare un'occhiata a erlang. La sua sintassi è molto strana, ma va anche bene, poiché una sintassi più tradizionale avrebbe (almeno per me) il tentativo di programmarlo nei vecchi modi e di essere frustrato.


5
2018-05-31 17:10



Personalmente, penso che il problema è che si sta tentando di utilizzare la programmazione funzionale per risolvere problemi progettati / dichiarati per la programmazione imperativa. I 3 paradigmi popolari (funzionale, imperativo, orientato agli oggetti) hanno diversi punti di forza:

  • La programmazione funzionale enfatizza la descrizione di che cosa deve essere fatto, di solito in termini di input / risultato.
  • La Programmazione Imperativa enfatizza su COME fare qualcosa, di solito in termini di lista e ordine di passi da compiere, e afferma di modificare.
  • La programmazione orientata agli oggetti enfatizza le RELAZIONI tra entità in un sistema

Quindi, quando ti avvicini a un problema, il primo ordine del giorno è quello di riformularlo in modo tale che il paradigma desiderato possa risolverlo correttamente. A proposito, come nodo laterale, per quanto ne so non esiste un "OOP puro". Il codice nei metodi delle tue classi OOP (sia esso Java, C #, C ++, Python o Objective C) sono tutti d'importanza.

Tornando all'esempio: il modo in cui si afferma il problema (prima, poi, anche, infine) è di natura imperativa. In quanto tale, la costruzione di una soluzione funzionale è quasi impossibile (senza fare trucchi come effetti collaterali o monadi, cioè). Allo stesso modo, anche se crei un sacco di classi, quelle classi sono inutili di per sé. Per usarli, devi scrivere il codice imperativo (anche se questi codici sono incorporati nelle classi) che risolvono il problema passo dopo passo.

Per rideterminare il problema:

  • Input: tipo di immagine (completa o miniatura), nome dell'immagine, file system
  • Uscita: l'immagine richiesta, il file system con l'immagine richiesta

Dalla nuova affermazione del problema, puoi risolverlo in questo modo:

def requestImage(type, name, fs) : 
    if type == "full" :
        return lookupImage(name, fs), fs
    else:
        thumb = lookupThumb(name, fs)
        if(thumb) :
            return thumb, fs
        else:
            thumb = createThumbnail(lookupImage(name, fs))
            return thumb, addThumbnailToFs(fs, name, thumb)

Ovviamente, questo è incompleto, ma possiamo sempre ricorsivamente risolvere lookupImage, lookupThumb, createThumbnail e aggiungereThumbnailToFs all'incirca nello stesso modo.

Nota importante: creare un nuovo filesystem sembra grande, ma non dovrebbe esserlo. Ad esempio, se questo è un modulo in un server web più grande, il "nuovo filesystem" può essere semplice quanto l'istruzione su dove dovrebbe essere la nuova miniatura. Oppure, nel peggiore dei casi, può essere una monade IO per mettere la miniatura nella posizione appropriata.


4
2018-05-31 17:37