Nella programmazione funzionale, cos'è un funzione?


224

Mi sono imbattuto più volte nel termine "Functor" durante la lettura di vari articoli sulla programmazione funzionale, ma in genere gli autori presumono che il lettore comprenda già il termine. Guardarsi in giro sul web ha fornito descrizioni eccessivamente tecniche (vedi l' articolo di Wikipedia ) o descrizioni incredibilmente vaghe (vedi la sezione su Functors in questo sito Web ocaml -tutorial ).

Qualcuno può gentilmente definire il termine, spiegarne l'uso e forse fornire un esempio di come vengono creati e utilizzati i Functor?

Modifica : mentre sono interessato alla teoria alla base del termine, sono meno interessato alla teoria di quanto lo sia nell'attuazione e nell'uso pratico del concetto.

Modifica 2 : Sembra che stia succedendo una terminologia incrociata: mi riferisco in particolare ai Functor della programmazione funzionale, non agli oggetti funzione di C ++.



Abbastanza buona risposta anche: stackoverflow.com/a/45149475/1498178
toraritte

Se sei più interessato all'implementazione pratica e all'utilizzo che alla terminologia stratosferica e alla teoria alla base del concetto, hai solo bisogno di una riga: un funzione espone una funzione "mappa".
Richard Gomes,

@RichardGomes IMHO Penso che riduca il ruolo di un funzione a una semplice interfaccia simile a Java, che non lo è. Un functor trasforma cose, costruisce nuovi tipi da quelli esistenti (in Haskell), il che significa che anche i tipi sono mappati. fmapmappa le funzioni. Vi sono due tipi di mappature coinvolte. Questo modo di vedere le cose aiuterà a comprendere la teoria delle categorie (che è più generale). Voglio dire, è interessante capire la teoria delle categorie di base per aiutarci con tutte le cose della teoria delle categorie in Haskell (functor, monadi, ...).
Ludovic Kuty

@VladtheImpala Il post sul blog è fantastico ma, anche se aiuta molto, mi piace tenere presente che un funzione crea (si mappa) un altro tipo. Mi piace particolarmente la frase "Una funzione F prende ogni tipo T e lo mappa su un nuovo tipo FT" in Monadi sono come i burritos . IMHO non è solo un contesto (un riquadro) attorno a un valore, anche se ciò risulta pratico vedere cose del genere (Haskell PoV vs teoria delle categorie PoV?)
Ludovic Kuty,

Risposte:


273

La parola "funzione" deriva dalla teoria delle categorie, che è una branca della matematica molto generale e molto astratta. È stato preso in prestito dai progettisti di linguaggi funzionali in almeno due modi diversi.

  • Nella famiglia di lingue ML, un functor è un modulo che accetta uno o più altri moduli come parametro. È considerata una funzionalità avanzata e la maggior parte dei programmatori principianti ha difficoltà con essa.

    Come esempio di implementazione e uso pratico, potresti definire la tua forma preferita di albero di ricerca binaria bilanciata una volta per tutte come un funzione, e prenderebbe come parametro un modulo che fornisce:

    • Il tipo di chiave da utilizzare nella struttura binaria

    • Una funzione di ordinamento totale sui tasti

    Una volta fatto questo, puoi usare per sempre la stessa implementazione bilanciata dell'albero binario. (Il tipo di valore memorizzato nell'albero viene solitamente lasciato polimorfico: l'albero non ha bisogno di guardare valori diversi da copiarli, mentre l'albero deve sicuramente essere in grado di confrontare le chiavi e ottiene la funzione di confronto da il parametro del funzione.)

    Un'altra applicazione dei funzioni ML sono i protocolli di rete a più livelli . Il collegamento è a un articolo davvero eccezionale del gruppo Fox della CMU; mostra come utilizzare i funzioni per costruire livelli di protocollo più complessi (come TCP) su tipi di livelli più semplici (come IP o anche direttamente su Ethernet). Ogni livello è implementato come un funzione che accetta come parametro il livello sottostante. La struttura del software riflette in realtà il modo in cui le persone pensano al problema, al contrario dei livelli esistenti solo nella mente del programmatore. Nel 1994, quando questo lavoro fu pubblicato, fu un grosso problema.

    Per un esempio selvaggio di funzioni ML in azione, potresti vedere l'articolo ML Module Mania , che contiene un esempio pubblicabile (ovvero spaventoso) di funzioni al lavoro. Per una spiegazione brillante, chiara e pellucida del sistema di moduli ML (con confronti con altri tipi di moduli), leggi le prime pagine del brillante documento POPL 1994 di Xavier Leroy sui tipi di manifest, i moduli e la compilazione separata .

  • In Haskell, e in alcuni linguaggi funzionali pure correlati, Functorc'è una classe di tipo . Un tipo appartiene a una classe di tipo (o più tecnicamente, il tipo "è un'istanza di" la classe di tipo) quando il tipo fornisce determinate operazioni con determinati comportamenti previsti. Un tipo Tpuò appartenere alla classe Functorse ha un certo comportamento simile a una raccolta:

    • Il tipo Tviene parametrizzato su un altro tipo, che dovresti considerare come il tipo di elemento della raccolta. Il tipo di collezione è quindi qualcosa di simile T Int, T String, T Bool, se si sta contenenti rispettivamente numeri interi, stringhe o booleani. Se il tipo di elemento è sconosciuto, viene scritto come parametro di tipo a , come in T a.

      Gli esempi includono elenchi (zero o più elementi di tipo a), il Maybetipo (zero o un elemento di tipo a), insiemi di elementi di tipo a, matrici di elementi di tipo a, tutti i tipi di alberi di ricerca contenenti valori di tipo ae molti altri posso pensare.

    • L'altra proprietà che Tdeve soddisfare è che se hai una funzione di tipo a -> b(una funzione sugli elementi), devi essere in grado di prendere quella funzione e produrre una funzione correlata sulle collezioni. Puoi farlo con l'operatore fmap, che è condiviso da ogni tipo nella Functorclasse type. L'operatore è effettivamente sovraccarico, quindi se hai una funzione evencon il tipo Int -> Bool, allora

      fmap even

      è una funzione sovraccarica che può fare molte cose meravigliose:

      • Converti un elenco di numeri interi in un elenco di valori booleani

      • Converti un albero di numeri interi in un albero di valori booleani

      • Converti Nothingin Nothinge Just 7inJust False

      In Haskell, questa proprietà è espressa dando il tipo di fmap:

      fmap :: (Functor t) => (a -> b) -> t a -> t b

      dove ora abbiamo un piccolo t, che significa "qualsiasi tipo nella Functorclasse".

    Per farla breve, in Haskell un funzione è una specie di collezione per la quale se ti viene data una funzione sugli elementi, fmapti restituirà una funzione sulle collezioni . Come puoi immaginare, questa è un'idea che può essere ampiamente riutilizzata, motivo per cui è benedetta come parte della biblioteca standard di Haskell.

Come al solito, le persone continuano a inventare astrazioni nuove e utili e potresti voler esaminare i funzionari applicativi , per i quali il miglior riferimento potrebbe essere un documento chiamato Programmazione applicativa con effetti di Conor McBride e Ross Paterson.


7
Capisco sia i funzione ML che i funzioni Haskell, ma non ho la visione di metterli insieme. Qual è la relazione tra questi due, in senso teorico di categoria?
Wei Hu,

6
@Wei Hu: la teoria delle categorie non ha mai avuto senso per me. Il meglio che posso dire è che tutte e tre le nozioni riguardano la mappatura.
Norman Ramsey,

16
Secondo questo wiki di Haskell: en.wikibooks.org/wiki/Haskell/Category_theory , è così: una categoria è una raccolta di oggetti e morfismi (funzioni), in cui i morfismi sono da oggetti in una categoria ad altri oggetti in quella categoria . Un funzione è una funzione che mappa oggetti e morfismi da una categoria a oggetti e morfismi in un'altra. Almeno è così che lo capisco. Che cosa significhi esattamente per la programmazione devo ancora capire.
paul

5
@ Norman-Ramsey, hai mai guardato la matematica concettuale di Lawvere e Schanuel? Sono un principiante assoluto della zona, ma il libro è estremamente leggibile e - oserei dire - divertente. (Mi è piaciuta molto la tua spiegazione.)
Ram Rajamony,

2
then you have to be able to take that function and product a related function on collectionsIntendevi produceinvece di product?
problemofficer,

64

Altre risposte qui sono complete, ma proverò un'altra spiegazione dell'uso di FP di functor . Prendi questo come analogia:

Un funzione è un contenitore di tipo a che, quando sottoposto a una funzione che mappa da ab , produce un contenitore di tipo b .

Diversamente dall'uso del puntatore a funzione astratta in C ++, qui il funzione non è la funzione; piuttosto, è qualcosa che si comporta in modo coerente quando sottoposto a una funzione .


3
Un contenitore di tipo b significa "lo stesso tipo di contenitore del contenitore di input, ma ora riempito con b". Quindi, se abbiamo un elenco di banane e mappiamo una funzione che prende una banana e produce un'insalata di frutta, ora abbiamo un elenco di macedonie. Allo stesso modo, se avessimo un albero di banane e mappassimo la stessa funzione, ora avremmo un albero di mele. L' albero e l' elenco ecc. Sono due portatori qui.
Qqwy,

3
"Un functor è un contenitore di tipo a che, quando sottoposto a una funzione" - in realtà è il contrario - la funzione (morfismo) è soggetta a un funzione, per essere mappata in un altro morfismo
Dmitri Zaitsev

38

Esistono tre significati diversi, non molto correlati!

  • In Ocaml è un modulo parametrizzato. Vedi il manuale . Penso che il modo migliore per ingannarli sia con l'esempio: (scritto rapidamente, potrebbe essere difettoso)

    module type Order = sig
        type t
        val compare: t -> t -> bool
    end;;
    
    
    module Integers = struct
        type t = int
        let compare x y = x > y
    end;;
    
    module ReverseOrder = functor (X: Order) -> struct
        type t = X.t
        let compare x y = X.compare y x
    end;;
    
    (* We can order reversely *)
    module K = ReverseOrder (Integers);;
    Integers.compare 3 4;;   (* this is false *)
    K.compare 3 4;;          (* this is true *)
    
    module LexicographicOrder = functor (X: Order) -> 
      functor (Y: Order) -> struct
        type t = X.t * Y.t
        let compare (a,b) (c,d) = if X.compare a c then true
                             else if X.compare c a then false
                             else Y.compare b d
    end;;
    
    (* compare lexicographically *)
    module X = LexicographicOrder (Integers) (Integers);;
    X.compare (2,3) (4,5);;
    
    module LinearSearch = functor (X: Order) -> struct
        type t = X.t array
        let find x k = 0 (* some boring code *)
    end;;
    
    module BinarySearch = functor (X: Order) -> struct
        type t = X.t array
        let find x k = 0 (* some boring code *)
    end;;
    
    (* linear search over arrays of integers *)
    module LS = LinearSearch (Integers);;
    LS.find [|1;2;3] 2;;
    (* binary search over arrays of pairs of integers, 
       sorted lexicographically *)
    module BS = BinarySearch (LexicographicOrder (Integers) (Integers));;
    BS.find [|(2,3);(4,5)|] (2,3);;
    

Ora puoi aggiungere rapidamente molti possibili ordini, modi per formare nuovi ordini, fare una ricerca binaria o lineare facilmente su di essi. Programmazione generica FTW.

  • In linguaggi di programmazione funzionale come Haskell, significa alcuni costruttori di tipi (tipi parametrizzati come elenchi, set) che possono essere "mappati". Per essere precisi, fè dotato di un funzione (a -> b) -> (f a -> f b). Questo ha origini nella teoria delle categorie. L'articolo di Wikipedia a cui ti sei collegato è questo utilizzo.

    class Functor f where
        fmap :: (a -> b) -> (f a -> f b)
    
    instance Functor [] where      -- lists are a functor
        fmap = map
    
    instance Functor Maybe where   -- Maybe is option in Haskell
        fmap f (Just x) = Just (f x)
        fmap f Nothing = Nothing
    
    fmap (+1) [2,3,4]   -- this is [3,4,5]
    fmap (+1) (Just 5)  -- this is Just 6
    fmap (+1) Nothing   -- this is Nothing
    

Quindi, questo è un tipo speciale di costruttori di tipo e ha poco a che fare con i funzioni di Ocaml!

  • Nei linguaggi imperativi, è un puntatore a funzionare.

<q> mappa </q> nelle ultime 3 righe di questo commento non dovrebbe in effetti essere <q> fmap </q>?
imz - Ivan Zakharyaschev,

1
Ho sempre letto che i funzione sono contenitori - ma questa è solo una scarsa semplificazione. La tua risposta alla fine ha fornito il link mancante: i Functor sono una classe di tipo (vincolo di tipo) per tipi parametrizzati (costruttori di tipo). È così semplice!

16

In OCaml, è un modulo con parametri.

Se conosci C ++, pensa a un funzione OCaml come modello. C ++ ha solo modelli di classe e i funzioni funzionano a livello di modulo.

Un esempio di functor è Map.Make; module StringMap = Map.Make (String);;crea un modulo mappa che funziona con le mappe con chiave String.

Non è possibile ottenere qualcosa come StringMap con il solo polimorfismo; devi fare alcune ipotesi sui tasti. Il modulo String contiene le operazioni (confronto, ecc.) Su un tipo di stringa totalmente ordinato e il funzione si collegherà alle operazioni contenute nel modulo String. Potresti fare qualcosa di simile con la programmazione orientata agli oggetti, ma avresti un overhead di metodo indiretto.


L'ho preso dal sito Web di ocaml, ma non capisco quale sarebbe l'uso di un modulo con parametri.
Erik Forbes,

4
@Kornel Sì, quello che ho descritto è un concetto OCaml. L'altro concetto è solo "valore funzionale", che non è nulla di particolare in FP. @Erik Mi sono leggermente espanso, ma i documenti di riferimento sono lenti da caricare.
Tobu,

13

Hai abbastanza buone risposte. Inserirò:

Un funzione, in senso matematico, è un tipo speciale di funzione su un'algebra. È una funzione minima che associa un'algebra a un'altra algebra. La "minimalità" è espressa dalle leggi dei funzioni.

Esistono due modi per esaminarlo. Ad esempio, gli elenchi sono funzioni su un certo tipo. Cioè, data un'algebra su un tipo "a", è possibile generare un'algebra compatibile di elenchi contenenti elementi di tipo "a". (Ad esempio: la mappa che porta un elemento in un elenco singleton che lo contiene: f (a) = [a]) Ancora una volta, la nozione di compatibilità è espressa dalle leggi dei funzioni.

D'altra parte, dato un funzione f "sopra" un tipo a, (cioè fa è il risultato dell'applicazione del funzione f all'algebra di tipo a) e funzione da g: a -> b, possiamo calcolare un nuovo funzione F = (fmap g) che mappa da fa a f b. In breve, fmap è la parte di F che mappa "parti di funzione" a "parti di funzione" e g è la parte della funzione che mappa "parti di algebra" a "parti di algebra". Prende una funzione, un funzione e, una volta completata, è anche una funzione.

Potrebbe sembrare che lingue diverse stiano usando nozioni diverse di funzione, ma non lo sono. Stanno semplicemente usando i funzioni su diverse algebre. OCamls ha una algebra di moduli e i funzioni di tale algebra consentono di allegare nuove dichiarazioni a un modulo in modo "compatibile".

Un funzione Haskell NON è una classe di tipo. È un tipo di dati con una variabile libera che soddisfa la classe di tipo. Se sei disposto a scavare nelle viscere di un tipo di dati (senza variabili libere), puoi reinterpretare un tipo di dati come un funzione su un'algebra sottostante. Per esempio:

dati F = F Int

è isomorfo alla classe di Ints. Quindi F, come costruttore di valori, è una funzione che associa Int a F Int, un'algebra equivalente. È un funzione. D'altra parte, non ottieni fmap gratis qui. Ecco a cosa serve la corrispondenza dei modelli.

I portatori sono buoni per "attaccare" le cose agli elementi di algebre, in modo algebricamente compatibile.


8

La migliore risposta a questa domanda si trova in "Typeclassopedia" di Brent Yorgey.

Questo numero di Monad Reader contiene una definizione precisa di cosa sia un funzione, nonché molte definizioni di altri concetti e un diagramma. (Monoid, Applicative, Monad e altri concetti sono spiegati e visti in relazione a un funzione).

http://haskell.org/sitewiki/images/8/85/TMR-Issue13.pdf

estratto da Typeclassopedia per Functor: "Una semplice intuizione è che un Functor rappresenta un" contenitore "di qualche tipo, insieme alla capacità di applicare una funzione uniformemente a ogni elemento nel contenitore"

Ma in realtà l'intera tipeclassopedia è una lettura altamente raccomandata che è sorprendentemente facile. In un certo senso puoi vedere la tabella dei tipi presentata lì come un parallelo al modello di progettazione nell'oggetto, nel senso che ti danno un vocabolario per un determinato comportamento o capacità.

Saluti


7

C'è un esempio piuttosto buono nel libro O'Reilly OCaml che si trova sul sito web di Inria (che al momento della stesura di questo è purtroppo negativo). Ho trovato un esempio molto simile in questo libro usato da caltech: Introduzione a OCaml (link pdf) . La sezione pertinente è il capitolo sui funzioni (Pagina 139 nel libro, pagina 149 nel PDF).

Nel libro hanno un funzione chiamato MakeSet che crea una struttura di dati che consiste in un elenco e funzioni per aggiungere un elemento, determinare se un elemento è nell'elenco e trovare l'elemento. La funzione di confronto che viene utilizzata per determinare se è inserita / no nell'insieme è stata parametrizzata (che è ciò che rende MakeSet un functor anziché un modulo).

Hanno anche un modulo che implementa la funzione di confronto in modo che faccia un confronto senza distinzione tra maiuscole e minuscole.

Utilizzando il functor e il modulo che implementa il confronto, possono creare un nuovo modulo in una riga:

module SSet = MakeSet(StringCaseEqual);;

che crea un modulo per una struttura di dati impostata che utilizza confronti senza distinzione tra maiuscole e minuscole. Se si desidera creare un set che utilizza confronti con distinzione tra maiuscole e minuscole, è sufficiente implementare un nuovo modulo di confronto anziché un nuovo modulo di struttura dati.

Tobu ha confrontato i funzioni con i modelli in C ++ che ritengo abbastanza adatti.


6

Date le altre risposte e ciò che sto per pubblicare ora, direi che è una parola piuttosto pesantemente sovraccarica, ma comunque ...

Per un suggerimento sul significato della parola "functor" in Haskell, chiedi a GHCi:

Prelude> :info Functor
class Functor f where
  fmap :: forall a b. (a -> b) -> f a -> f b
  (GHC.Base.<$) :: forall a b. a -> f b -> f a
        -- Defined in GHC.Base
instance Functor Maybe -- Defined in Data.Maybe
instance Functor [] -- Defined in GHC.Base
instance Functor IO -- Defined in GHC.Base

Quindi, fondamentalmente, un funzione in Haskell è qualcosa che può essere mappato. Un altro modo di dire è che un funzione è qualcosa che può essere considerato come un contenitore a cui può essere chiesto di usare una determinata funzione per trasformare il valore che contiene; quindi, per gli elenchi, fmapcoincide con map, per Maybe, fmap f (Just x) = Just (f x), fmap f Nothing = Nothingetc.

La sottosezione della tabella dei tipi di Functor e la sezione su Functors, Functors applicativi e Monoids of Learn You a Haskell for Great Good forniscono alcuni esempi di dove questo particolare concetto è utile. (Un riassunto: molti posti! :-))

Si noti che qualsiasi monade può essere trattata come un funzione e, infatti, come sottolinea Craig Stuntz, i funzioni più utilizzate tendono ad essere monadi ... OTOH, a volte è conveniente fare di un tipo un'istanza della tabella dei tipi di Functor senza preoccuparsi di renderla una Monade. (Ad esempio nel caso di ZipListda Control.Applicative, menzionato in una delle pagine di cui sopra .)


5

Ecco un articolo sui funzioni di un POV di programmazione , seguito da più specificamente come emergono nei linguaggi di programmazione .

L'uso pratico di un funzione è in una monade, e se lo cerchi puoi trovare molti tutorial sulle monadi.


1
"L'uso pratico di un funzione è in una monade" - non solo. Tutte le monadi sono funzioni, ma ci sono molti usi per i non-monade.
amindfv,

1
Direi che studiare le monadi per usare i funzioni è come risparmiare per un Rolls per andare a fare la spesa.
Marco Faustinelli,

5

In un commento alla risposta più votata , l'utente Wei Hu chiede:

Capisco sia i funzione ML che i funzioni Haskell, ma non ho la visione di metterli insieme. Qual è la relazione tra questi due, in senso teorico di categoria?

Nota : non conosco ML, quindi ti prego di perdonare e correggere eventuali errori correlati.

Supponiamo inizialmente che conosciamo tutti le definizioni di "categoria" e "funzione".

Una risposta compatta sarebbe che i "funzioni di Haskell" sono (endo) funzioni F : Hask -> Haskmentre i "funzioni di ML" sono funzioni G : ML -> ML'.

Qui, Haskc'è la categoria formata da tipi e funzioni di Haskell tra loro, e allo stesso modo MLe ML'sono categorie definite da strutture ML.

Nota : ci sono alcuni problemi tecnici con la creazione di Haskuna categoria, ma ci sono modi per aggirarli.

Da una prospettiva teorica di categoria, ciò significa che un Hask-functor è una mappa Fdei tipi di Haskell:

data F a = ...

insieme a una mappa fmapdelle funzioni di Haskell:

instance Functor F where
    fmap f = ...

ML è praticamente la stessa, sebbene non ci sia fmapastrazione canonica di cui io sia a conoscenza, quindi definiamone una:

signature FUNCTOR = sig
  type 'a f
  val fmap: 'a -> 'b -> 'a f -> 'b f
end

Queste sono fmappe ML-tipi e fmapmappe- MLfunzioni, quindi

functor StructB (StructA : SigA) :> FUNCTOR =
struct
  fmap g = ...
  ...
end

è un funzione F: StructA -> StructB.


5

"Functor è la mappatura di oggetti e morfismi che preserva la composizione e l'identità di una categoria."

Consente di definire cos'è una categoria?

È un mucchio di oggetti!

Disegna alcuni punti (per ora 2 punti, uno è 'a' un altro è 'b') all'interno di un cerchio e chiama quel cerchio A (Categoria) per ora.

Cosa contiene la categoria?

Composizione tra oggetti e funzione Identità per ogni oggetto.

Quindi, dobbiamo mappare gli oggetti e preservare la composizione dopo aver applicato il nostro Functor.

Immaginiamo che 'A' sia la nostra categoria che ha oggetti ['a', 'b'] e che esiste un morfismo a -> b

Ora, dobbiamo definire un funzione che può mappare questi oggetti e morfismi in un'altra categoria 'B'.

Diciamo che il functor si chiama 'Forse'

data Maybe a = Nothing | Just a

Quindi, la categoria 'B' è simile a questa.

Disegna un altro cerchio, ma questa volta con 'Forse a' e 'Forse b' invece di 'a' e 'b'.

Tutto sembra a posto e tutti gli oggetti sono mappati

'a' è diventato 'Forse a' e 'b' è diventato 'Forse b'.

Ma il problema è che dobbiamo mappare il morfismo anche da "a" a "b".

Ciò significa che il morfismo a -> b in 'A' dovrebbe essere mappato al morfismo 'Forse a' -> 'Forse b'

il morfismo da a -> b si chiama f, quindi il morfismo da 'Forse a' -> 'Forse b' si chiama 'fmap f'

Ora vediamo quale funzione 'f' stava facendo in 'A' e vediamo se possiamo replicarla in 'B'

definizione della funzione di 'f' in 'A':

f :: a -> b

f prende a e ritorna b

definizione della funzione di 'f' in 'B':

f :: Maybe a -> Maybe b

f prende Forse a e ritorna Forse b

vediamo come usare fmap per mappare la funzione 'f' da 'A' per funzione 'fmap f' in 'B'

definizione di fmap

fmap :: (a -> b) -> (Maybe a -> Maybe b)
fmap f Nothing = Nothing
fmap f (Just x) = Just(f x)

Allora, che ci facciamo qui?

Stiamo applicando la funzione 'f' a 'x' che è di tipo 'a'. La definizione speciale di "Niente" deriva dalla definizione di Functor Maybe.

Quindi, abbiamo mappato i nostri oggetti [a, b] e morfismi [f] dalla categoria 'A' alla categoria 'B'.

Quello è Functor!

inserisci qui la descrizione dell'immagine


Risposta interessante. Vorrei completarlo con le Monadi sono come i burritos (risposta divertente all'astrazione, all'intuizione e al "fallacia del monade tutorial" ) e la sua frase "Una funzione F prende ogni tipo T e lo mappa su un nuovo tipo FT" aka un costruttore di tipo . Programmazione funzionale e teoria delle categorie - Anche le categorie e i fattori sono stati utili.
Ludovic Kuty

3

Panoramica generale

Nella programmazione funzionale, un funtore è essenzialmente una costruzione di sollevamento ordinarie unari funzioni (cioè quelli con un argomento) per funzioni tra variabili dei nuovi tipi. È molto più facile scrivere e mantenere semplici funzioni tra oggetti semplici e utilizzare i funzioni per sollevarli, quindi scrivere manualmente funzioni tra oggetti container complicati. Un ulteriore vantaggio è quello di scrivere funzioni semplici una sola volta e poi riutilizzarle tramite diversi funzioni.

Esempi di funzioni includono array, funzioni "forse" e "entrambi", futures (vedere ad esempio https://github.com/Avaq/Fluture ) e molti altri.

Illustrazione

Considera la funzione che costruisce il nome completo della persona dal nome e dal cognome. Potremmo definirlo fullName(firstName, lastName)come una funzione di due argomenti, che tuttavia non sarebbe adatto a funzioni che trattano solo le funzioni di un argomento. Per rimediare, raccogliamo tutti gli argomenti in un singolo oggetto name, che ora diventa il singolo argomento della funzione:

// In JavaScript notation
fullName = name => name.firstName + ' ' + name.lastName

E se avessimo molte persone in un array? Invece di consultare manualmente l'elenco, possiamo semplicemente riutilizzare la nostra funzione fullNametramite il mapmetodo fornito per gli array con una breve riga di codice:

fullNameList = nameList => nameList.map(fullName)

e usalo come

nameList = [
    {firstName: 'Steve', lastName: 'Jobs'},
    {firstName: 'Bill', lastName: 'Gates'}
]

fullNames = fullNameList(nameList) 
// => ['Steve Jobs', 'Bill Gates']

Funzionerà, ogni volta che ogni voce nel nostro nameListè un oggetto che fornisce sia firstNamee lastNameproprietà. Ma cosa succede se alcuni oggetti non lo fanno (o addirittura non lo sono affatto)? Per evitare errori e rendere il codice più sicuro, possiamo inserire i nostri oggetti nel Maybetipo (ad es. Https://sanctuary.js.org/#maybe-type ):

// function to test name for validity
isValidName = name => 
    (typeof name === 'object') 
    && (typeof name.firstName === 'string')
    && (typeof name.lastName === 'string')

// wrap into the Maybe type
maybeName = name => 
    isValidName(name) ? Just(name) : Nothing()

dove Just(name)è un contenitore che porta solo nomi validi ed Nothing()è il valore speciale utilizzato per tutto il resto. Ora invece di interrompere (o dimenticare) per verificare la validità dei nostri argomenti, possiamo semplicemente riutilizzare (sollevare) la nostra fullNamefunzione originale con un'altra singola riga di codice, basata di nuovo sul mapmetodo, questa volta fornito per il tipo Maybe:

// Maybe Object -> Maybe String
maybeFullName = maybeName => maybeName.map(fullName)

e usalo come

justSteve = maybeName(
    {firstName: 'Steve', lastName: 'Jobs'}
) // => Just({firstName: 'Steve', lastName: 'Jobs'})

notSteve = maybeName(
    {lastName: 'SomeJobs'}
) // => Nothing()

steveFN = maybeFullName(justSteve)
// => Just('Steve Jobs')

notSteveFN = maybeFullName(notSteve)
// => Nothing()

Teoria di categoria

Una teoria della teoria delle categorie è una mappa tra due categorie che rispetta la composizione dei loro morfismi. In un linguaggio informatico , la principale categoria di interesse è quella i cui oggetti sono tipi (determinati insiemi di valori) e i cui morfismi sono funzioni f:a->bda un tipo aa un altro b.

Ad esempio, prendi aper essere il Stringtipo, bil tipo Numero, ed fè la funzione che mappa una stringa nella sua lunghezza:

// f :: String -> Number
f = str => str.length

Qui a = Stringrappresenta l'insieme di tutte le stringhe e b = Numberl'insieme di tutti i numeri. In tal senso, entrambi ae brappresentano oggetti nella categoria Set (che è strettamente correlata alla categoria dei tipi, con la differenza che qui è inessenziale). Nella categoria Set, i morfismi tra due set sono precisamente tutte le funzioni dal primo al secondo. Quindi la nostra funzione di lunghezza fqui è un morfismo dall'insieme delle stringhe nell'insieme dei numeri.

Considerando solo la categoria impostata, i fattori rilevanti da essa stessi in sé sono mappe che inviano oggetti a oggetti e morfismi a morfismi, che soddisfano determinate leggi algebriche.

Esempio: Array

Arraypuò significare molte cose, ma solo una cosa è un Functor: il costrutto type, che associa un tipo aal tipo [a]di tutte le matrici di tipo a. Ad esempio, il funzione Arrayassocia il tipo String al tipo [String](l'insieme di tutte le matrici di stringhe di lunghezza arbitraria) e imposta il tipo Numbernel tipo corrispondente [Number](l'insieme di tutte le matrici di numeri).

È importante non confondere la mappa di Functor

Array :: a => [a]

con un morfismo a -> [a]. Il funzione semplicemente mappa (associa) il tipo anel tipo [a]come una cosa all'altra. Che ogni tipo sia in realtà un insieme di elementi, non ha rilevanza qui. Al contrario, un morfismo è una funzione reale tra questi insiemi. Ad esempio, esiste un morfismo naturale (funzione)

pure :: a -> [a]
pure = x => [x]

che invia un valore nell'array a 1 elemento con quel valore come singola voce. Quella funzione non fa parte del ArrayFunctor! Dal punto di vista di questo funzione, pureè solo una funzione come qualsiasi altra, niente di speciale.

D'altra parte, il ArrayFunctor ha la sua seconda parte - la parte del morfismo. Che mappa un morfismo f :: a -> bin un morfismo [f] :: [a] -> [b]:

// a -> [a]
Array.map(f) = arr => arr.map(f)

Ecco arruna matrice di lunghezza arbitraria con valori di tipo a, ed arr.map(f)è una matrice della stessa lunghezza con valori di tipo b, le cui voci sono i risultati dell'applicazione fdelle voci di arr. Per renderlo un funzione, le leggi matematiche di mappare identità su identità e composizioni su composizioni devono valere, che sono facili da verificare in questo Arrayesempio.


2

Per non contraddire le precedenti risposte teoriche o matematiche, ma un Functor è anche un Oggetto (in un linguaggio di programmazione orientato agli oggetti) che ha un solo metodo ed è effettivamente utilizzato come funzione.

Un esempio è l'interfaccia Runnable in Java, che ha un solo metodo: run.

Considera questo esempio, prima in Javascript, che ha funzioni di prima classe:

[1, 2, 5, 10].map(function(x) { return x*x; });

Uscita: [1, 4, 25, 100]

Il metodo map accetta una funzione e restituisce un nuovo array con ogni elemento risultante dall'applicazione di quella funzione al valore nella stessa posizione dell'array originale.

Per fare la stessa cosa è Java, usando un Functor, devi prima definire un'interfaccia, ad esempio:

public interface IntMapFunction {
  public int f(int x);
}

Quindi, se aggiungi una classe di raccolta con una funzione mappa, puoi fare:

myCollection.map(new IntMapFunction() { public int f(int x) { return x * x; } });

Questo utilizza una sottoclasse in linea di IntMapFunction per creare un Functor, che è l'equivalente OO della funzione dell'esempio JavaScript precedente.

L'uso di Functors consente di applicare tecniche funzionali in un linguaggio OO. Naturalmente, alcune lingue OO hanno anche il supporto diretto per le funzioni, quindi non è necessario.

Riferimento: http://en.wikipedia.org/wiki/Function_object


In realtà "oggetto funzione" non è una descrizione corretta di un funzione. Ad esempio Arrayè un functor, ma Array(value)fornisce solo array a 1 elemento.
Dmitri Zaitsev,

0

KISS: Un functor è un oggetto che ha un metodo map.

Le matrici in JavaScript implementano la mappa e sono quindi funzioni. Promesse, stream e alberi implementano spesso la mappa in linguaggi funzionali e quando lo fanno, sono considerati funzioni. Il metodo map del funzione richiede i propri contenuti e li trasforma ciascuno usando il callback di trasformazione passato alla mappa e restituisce un nuovo funzione, che contiene la struttura come primo funzione, ma con i valori trasformati.

src: https://www.youtube.com/watch?v=DisD9ftUyCk&feature=youtu.be&t=76


1
Con la nota a margine che "oggetto" dovrebbe essere preso in senso lato e significa semplicemente "qualcosa". Per i linguaggi OOP, ad esempio, sostituire l' oggetto per la classe . Si potrebbe dire "un functor è una classe che implementa l'interfaccia di Functor" (Naturalmente, questa interfaccia potrebbe non essere fisicamente lì, ma è possibile sollevare la logica "mappa" su quella interfaccia e avere tutte le classi mappabili condividerla - fintanto che il tuo sistema di tipi consente di scrivere cose così generali, cioè).
Qqwy,

1
Trovo le classi super confuse per essere oneste, da un lato sono solo un modello per qualcosa di concreto / ma possono anche avere metodi (roba statica) e possono comportarsi come oggetti. La classe implementa l'interfaccia o l'istanza che crea?
soundyogi,

1
Sì, possono essere fonte di confusione. Ma: le classi implementano interfacce ("riempiono" gli spazi vuoti che sono stati dati nei metodi delle interfacce. In altre parole: trasformano le linee guida astratte dell'interfaccia in una linea guida concreta che può essere istantaneamente (perdona il gioco di parole) istanziato). Per quanto riguarda le "classi si comportano come oggetti": in linguaggi veramente OOP come Ruby, le classi sono istanze della classe "Class". Sono le tartarughe fino in fondo.
Qqwy,

ArrayIl tipo costrutto definisce un singolo funzione. Le sue istanze sono anche chiamate "matrici" ma non sono funzioni. La descrizione qui dovrebbe essere resa più precisa.
Dmitri Zaitsev,

@DmitriZaitsev Potresti elaborare? Quindi quello che stai dicendo è che le istanze non sono funzioni? Non vedo il senso da quando ottieni un nuovo funzione mappandone uno.
soundyogi,

-4

In pratica, functor indica un oggetto che implementa l'operatore di chiamata in C ++. In ocaml penso che functor si riferisca a qualcosa che accetta un modulo come input e output un altro modulo.


-6

In parole povere, un funzione, o un oggetto funzione, è un oggetto di classe che può essere chiamato proprio come una funzione.

In C ++:

Ecco come si scrive una funzione

void foo()
{
    cout << "Hello, world! I'm a function!";
}

Ecco come si scrive un funzione

class FunctorClass
{
    public:
    void operator ()
    {
        cout << "Hello, world! I'm a functor!";
    }
};

Ora puoi farlo:

foo(); //result: Hello, World! I'm a function!

FunctorClass bar;
bar(); //result: Hello, World! I'm a functor!

Ciò che li rende così fantastici è che puoi mantenere lo stato in classe: immagina se vuoi chiedere una funzione quante volte è stato chiamato. Non c'è modo di farlo in un modo pulito e incapsulato. Con un oggetto funzione, è proprio come qualsiasi altra classe: avresti qualche variabile di istanza in cui incrementare operator ()e qualche metodo per ispezionare quella variabile, e tutto è pulito come ti pare.


12
No, quei funzioni non sono la teoria dei tipi utilizzata dai linguaggi FP.
Tobu,

1
Posso in qualche modo vedere come si possa dimostrare che FunctorClasssoddisfa la prima Legge di Functor, ma potresti delineare una prova per la seconda Legge? Non lo vedo del tutto.
Jörg W Mittag,

3
Bah, ragazzi avete ragione. Ho preso una decisione per risolvere il "web ha fornito descrizioni estremamente tecniche" e ho esagerato, cercando di evitare "Nella famiglia di linguaggi ML, un funzione è un modulo che prende uno o più altri moduli come parametro". Questa risposta, tuttavia, è, bene, cattiva. Semplificato e poco specificato. Sono tentato di cancellarlo, ma lascerò che le generazioni future scuotano la testa a :)
Matt,

Sono contento che tu abbia lasciato la risposta e i commenti, perché aiuta a inquadrare il problema. Grazie! Ho problemi in quanto la maggior parte delle risposte sono scritte in termini di Haskell o OCaml, e per me è un po 'come spiegare gli alligatori in termini di coccodrilli.
Rob,

-10

Functor non è specificamente correlato alla programmazione funzionale. È solo un "puntatore" a una funzione o ad un tipo di oggetto, che può essere chiamato come sarebbe una funzione.


8
Esiste un concetto FP specifico di funzione (dalla teoria delle categorie), ma hai ragione nel dire che la stessa parola è usata anche per altre cose in linguaggi non FP.
Craig Stuntz,

Sei sicuro che i puntatori a funzione siano Functors? Non vedo come gli indicatori di funzione soddisfino le due leggi di Functor, in particolare la seconda legge di Functor (conservazione della composizione del morfismo). Ne hai una prova? (Solo uno schizzo approssimativo.)
Jörg W Mittag
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.