Risposte:
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 Foo
prendere tipi numerici ( Int
, Double
ecc.) 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
, liftFoo4
e 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 liftFoo1
scrivere
instance Functor Foo where
fmap f foo = ...
Se vuoi davvero la completa regolarità puoi dire
liftFoo1 = fmap
Se riesci a trasformarti Foo
in un funzione, forse puoi renderlo un funzione applicativa. Infatti, se riesci a scrivere, liftFoo2
l'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.Applicative
modulo include
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
e allo stesso modo ci sono liftA
e 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 <$> arg1
restituisce 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.
lift id == id
e lift (f . g) == (lift f) . (lift g)
.
id
e .
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, id
e si .
riferiscono alle funzioni di Haskell che conosci e ami).
instance Functor Foo
, no instance Foo Functor
, giusto? Mi modificarei ma non ne sono sicuro al 100%.
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 Functor
e l'operazione di sollevamento viene chiamata fmap
:
fmap :: Functor f => (a -> b) -> f a -> f b
Nota la somiglianza con liftFoo1
il tipo di. Infatti, se lo hai liftFoo1
, puoi creare Foo
un'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!
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"
liftA2
trasforma 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 lift
da 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.
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
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 a
e penso agli elementi come bacche sull'albero Tree<a>
. C'è una funzione fmap
, che accetta una funzione di conversione degli elementi, a->b
e un contenitore functor<a>
. Si applica a->b
a ogni elemento del contenitore convertendolo efficacemente in functor<b>
. Quando viene fornito solo il primo argomento a->b
, fmap
attende il functor<a>
. Cioè, la fornitura a->b
da 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->b
per la conversione mentre Monad richiede all'utente di definirlo a -> f<b>
.
r
ad un tipo (usiamo c
per varietà), sono Functors. Non "contengono" alcuno c
. In questo caso, fmap è la composizione di funzioni, che accetta una a -> b
funzione e una r -> a
per darti una nuova r -> b
funzione. Ancora niente contenitori. Inoltre, se potessi, lo segnerei di nuovo per l'ultima frase.
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 .
To wait
, to expect
, to anticipate
sono i sinonimi. Dicendo "funzione attende" intendevo "funzione anticipa".
b = 5 : a
ed f 0 = 55
f n = g n
entrambi 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.