Cosa significa "sollevare" in Haskell?


Risposte:


179

Il sollevamento è più un modello di progettazione che un concetto matematico (anche se mi aspetto che qualcuno qui mi smentirà dimostrando come gli ascensori siano una categoria o qualcosa del genere).

In genere hai un tipo di dati con un parametro. Qualcosa di simile a

data Foo a = Foo { ...stuff here ...}

Supponiamo di scoprire che molti usi di Fooprendere tipi numerici ( Int, Doubleecc.) E di continuare a scrivere codice che scartano questi numeri, li aggiunge o li moltiplica, quindi li avvolge di nuovo. Puoi cortocircuitare questo comando scrivendo il codice involucro una volta. Questa funzione è tradizionalmente chiamata "ascensore" perché si presenta così:

liftFoo2 :: (a -> b -> c) -> Foo a -> Foo b -> Foo c

In altre parole, hai una funzione che accetta una funzione a due argomenti (come l' (+)operatore) e la trasforma nella funzione equivalente per Foos.

Quindi ora puoi scrivere

addFoo = liftFoo2 (+)

Modifica: maggiori informazioni

Certo che puoi liftFoo3, liftFoo4e così via. Tuttavia questo spesso non è necessario.

Inizia con l'osservazione

liftFoo1 :: (a -> b) -> Foo a -> Foo b

Ma è esattamente lo stesso di fmap. Quindi piuttosto che liftFoo1scrivere

instance Functor Foo where
   fmap f foo = ...

Se vuoi davvero la completa regolarità puoi dire

liftFoo1 = fmap

Se riesci a trasformarti Fooin un funzione, forse puoi renderlo un funzione applicativa. Infatti, se riesci a scrivere, liftFoo2l'istanza applicativa si presenta così:

import Control.Applicative

instance Applicative Foo where
   pure x = Foo $ ...   -- Wrap 'x' inside a Foo.
   (<*>) = liftFoo2 ($)

L' (<*>)operatore per Foo ha il tipo

(<*>) :: Foo (a -> b) -> Foo a -> Foo b

Applica la funzione di wrapping al valore di wrapping. Quindi, se puoi implementarlo liftFoo2, puoi scrivere questo in termini di esso. Oppure puoi implementarlo direttamente e non preoccuparti liftFoo2, perché il Control.Applicativemodulo include

liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c

e allo stesso modo ci sono liftAe liftA3. Ma in realtà non li usi molto spesso perché c'è un altro operatore

(<$>) = fmap

Questo ti permette di scrivere:

result = myFunction <$> arg1 <*> arg2 <*> arg3 <*> arg4

Il termine myFunction <$> arg1restituisce una nuova funzione racchiusa in Foo. Questo a sua volta può essere applicato all'argomento successivo usando (<*>), e così via. Quindi ora invece di avere una funzione di sollevamento per ogni arità, hai solo una catena di applicazioni.


26
Vale probabilmente la pena ricordare che gli ascensori dovrebbero rispettare le leggi standard lift id == ide lift (f . g) == (lift f) . (lift g).
Carlos Scheidegger,

13
Gli ascensori sono davvero "una categoria o qualcosa del genere". Carlos ha appena elencato le leggi di Functor, dove ide .sono rispettivamente la freccia dell'identità e la composizione della freccia di una categoria. Di solito quando si parla di Haskell, la categoria in questione è "Hask", le cui frecce sono funzioni di Haskell (in altre parole, ide si .riferiscono alle funzioni di Haskell che conosci e ami).
Dan Burton,

3
Questo dovrebbe leggere instance Functor Foo, no instance Foo Functor, giusto? Mi modificarei ma non ne sono sicuro al 100%.
amalloy,

2
Il sollevamento senza applicativo è = Functor. Voglio dire, hai 2 scelte: Functor o Functor Applicativo. Il primo solleva le funzioni a parametro singolo mentre le seconde funzioni a più parametri. Praticamente tutto qui. Destra? Non è scienza missilistica :) sembra proprio così. Grazie per l'ottima risposta a proposito!
jhegedus,

2
@atc: questa è un'applicazione parziale. Vedi wiki.haskell.org/Partial_application
Paul Johnson,

41

Paul e yairchu sono entrambe buone spiegazioni.

Vorrei aggiungere che la funzione sollevata può avere un numero arbitrario di argomenti e che non devono essere dello stesso tipo. Ad esempio, potresti anche definire un liftFoo1:

liftFoo1 :: (a -> b) -> Foo a -> Foo b

In generale, il sollevamento di funzioni che accettano 1 argomento viene acquisito nella classe di tipo Functore l'operazione di sollevamento viene chiamata fmap:

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

Nota la somiglianza con liftFoo1il tipo di. Infatti, se lo hai liftFoo1, puoi creare Fooun'istanza di Functor:

instance Functor Foo where
  fmap = liftFoo1

Inoltre, la generalizzazione del passaggio a un numero arbitrario di argomenti si chiama stile applicativo . Non preoccuparti di immergerti in questo fino a quando non capisci il sollevamento di funzioni con un numero fisso di argomenti. Ma quando lo fai, Learn you a Haskell ha un buon capitolo su questo. Il Typeclassopedia è un altro buon documento che descrive Functor e applicativa (così come altre classi tipo; scorrere verso il basso il capitolo proprio in quel documento).

Spero che questo ti aiuti!


25

Cominciamo con un esempio (viene aggiunto uno spazio bianco per una presentazione più chiara):

> import Control.Applicative
> replicate 3 'a'
"aaa"
> :t replicate
replicate        ::         Int -> b -> [b]
> :t liftA2
liftA2 :: (Applicative f) => (a -> b -> c) -> (f a -> f b -> f c)
> :t liftA2 replicate
liftA2 replicate :: (Applicative f) =>       f Int -> f b -> f [b]
> (liftA2 replicate) [1,2,3] ['a','b','c']
["a","b","c","aa","bb","cc","aaa","bbb","ccc"]
> ['a','b','c']
"abc"

liftA2trasforma una funzione di tipi semplici in una funzione degli stessi tipi racchiusa in un tipoApplicative , ad esempio liste IO, ecc.

Un altro ascensore comune viene liftda Control.Monad.Trans. Trasforma un'azione monadica di una monade in un'azione di una monade trasformata.

In generale, "ascensore" solleva una funzione / azione in un tipo "avvolto" (in modo che la funzione originale funzioni "sotto gli involucri").

Il modo migliore per capire questo, le monadi ecc. E capire perché sono utili è probabilmente codificarlo e usarlo. Se c'è qualcosa che hai precedentemente codificato di cui sospetti possa beneficiare (ad esempio, questo accorcia il codice, ecc.), Provalo e afferrerai facilmente il concetto.


13

Il sollevamento è un concetto che consente di trasformare una funzione in una funzione corrispondente all'interno di un'altra impostazione (generalmente più generale)

dai un'occhiata a http://haskell.org/haskellwiki/Lifting


40
Sì, ma quella pagina inizia "Di solito iniziamo con un funzione (covariante) ...". Non esattamente adatto ai principianti.
Paul Johnson

3
Ma "functor" è collegato, quindi il principiante può semplicemente fare clic su di esso per vedere cos'è un Functor. Certo, la pagina collegata non è così buona. Devo ottenere un account e risolverlo.
jrockway

10
È un problema che ho visto su altri siti di programmazione funzionale; ogni concetto è spiegato in termini di altri concetti (non familiari) fino a quando il neofita fa il giro completo (e attorno alla curva). Deve avere a che fare con la simpatia della ricorsione.
DNA,

2
Vota questo link. Lift crea connessioni tra un mondo e un altro mondo.
eccstartup,

3
Le risposte come questa sono buone solo quando hai già capito l'argomento.
doubleOrt

-2

Secondo questo brillante tutorial , un functor è un contenitore (come Maybe<a>, List<a>o Tree<a>che può contenere elementi di un altro tipo a). Ho usato la notazione generica di Java <a>, per tipo di elemento ae penso agli elementi come bacche sull'albero Tree<a>. C'è una funzione fmap, che accetta una funzione di conversione degli elementi, a->be un contenitore functor<a>. Si applica a->ba ogni elemento del contenitore convertendolo efficacemente in functor<b>. Quando viene fornito solo il primo argomento a->b, fmapattende il functor<a>. Cioè, la fornitura a->bda sola trasforma questa funzione a livello di elemento nella funzione functor<a> -> functor<b>che opera su container. Questo si chiama sollevamentodella funzione. Poiché il contenitore è anche chiamato un funtore , i Funtori anziché Monadi sono un prerequisito per il sollevamento. Le monadi sono una specie di "parallelo" al sollevamento. Entrambi si basano sull'idea di Functor e lo fanno f<a> -> f<b>. La differenza è che il sollevamento utilizza a->bper la conversione mentre Monad richiede all'utente di definirlo a -> f<b>.


5
Ti ho dato un voto in basso, perché "un funzione è un contenitore" è un'esca di fiamma al gusto di troll. Esempio: funzioni da alcuni rad un tipo (usiamo cper varietà), sono Functors. Non "contengono" alcuno c. In questo caso, fmap è la composizione di funzioni, che accetta una a -> bfunzione e una r -> aper darti una nuova r -> bfunzione. Ancora niente contenitori. Inoltre, se potessi, lo segnerei di nuovo per l'ultima frase.
BMeph,

1
Inoltre, fmapè una funzione e non "aspetta" nulla; Il "contenitore" essendo un Functor è il punto di sollevamento. Inoltre, le Monadi sono, semmai, una duplice idea da sollevare: una Monade ti consente di usare qualcosa che è stato sollevato un numero positivo di volte, come se fosse stato sollevato solo una volta - questo è meglio noto come appiattimento .
BMeph,

1
@BMeph To wait, to expect, to anticipatesono i sinonimi. Dicendo "funzione attende" intendevo "funzione anticipa".
Val

@BMeph Direi che invece di pensare a una funzione come controesempio all'idea che i funzione sono contenitori, dovresti pensare all'istanza sana di funzione del funzione come controesempio all'idea che le funzioni non sono contenitori. Una funzione è una mappatura da un dominio a un codice, il dominio è il prodotto incrociato di tutti i parametri, mentre il codice è il tipo di output della funzione. Allo stesso modo un elenco è una mappatura dai Naturali al tipo interno dell'elenco (dominio -> codomain). Diventano ancora più simili se si memorizza la funzione o non si mantiene l'elenco.
punto

@BMeph si ritiene che uno dei soli elenchi dei motivi sia più simile a un contenitore è che in molte lingue possono essere mutati, mentre tradizionalmente le funzioni non possono. Ma in Haskell anche questa non è un'affermazione equa in quanto nessuno dei due può essere mutato, ed entrambi possono essere mutati in copia: b = 5 : aed f 0 = 55 f n = g nentrambi implicano la pseudo-mutazione del "contenitore". Inoltre, il fatto che gli elenchi vengano in genere archiviati completamente nella memoria, mentre le funzioni vengono in genere archiviate come calcolo. Ma gli elenchi memoizing / monorfi che non sono memorizzati tra le chiamate interrompono entrambi l'idea di merda.
punto
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.