Saya tidak mengerti apa itu "mengangkat". Haruskah saya terlebih dahulu memahami monads sebelum memahami apa itu "lift"? (Saya juga benar-benar tidak tahu tentang monad :) Atau dapatkah seseorang menjelaskannya kepada saya dengan kata-kata sederhana?
haskell
functional-programming
GabiMe
sumber
sumber
Jawaban:
Mengangkat lebih merupakan pola desain daripada konsep matematika (meskipun saya berharap seseorang di sekitar sini sekarang akan membantah saya dengan menunjukkan bagaimana lift adalah sebuah kategori atau sesuatu).
Biasanya Anda memiliki beberapa tipe data dengan parameter. Sesuatu seperti
data Foo a = Foo { ...stuff here ...}
Misalkan Anda menemukan banyak penggunaan
Foo
tipe numerik take (Int
,Double
dll) dan Anda tetap harus menulis kode yang membuka bungkus angka-angka ini, menambah atau mengalikannya, dan kemudian membungkusnya kembali. Anda dapat melakukan hubungan pendek ini dengan menulis kode unwrap-and-wrap sekali. Fungsi ini secara tradisional disebut "lift" karena terlihat seperti ini:liftFoo2 :: (a -> b -> c) -> Foo a -> Foo b -> Foo c
Dengan kata lain, Anda memiliki fungsi yang mengambil fungsi dua argumen (seperti
(+)
operator) dan mengubahnya menjadi fungsi yang setara untuk Foos.Jadi sekarang Anda bisa menulis
addFoo = liftFoo2 (+)
Edit: informasi lebih lanjut
Anda tentu saja bisa memiliki
liftFoo3
,liftFoo4
dan seterusnya. Namun hal ini seringkali tidak diperlukan.Mulailah dengan observasi
liftFoo1 :: (a -> b) -> Foo a -> Foo b
Tapi itu sama persis dengan
fmap
. Jadi daripadaliftFoo1
Anda akan menulisinstance Functor Foo where fmap f foo = ...
Jika Anda benar-benar ingin keteraturan lengkap Anda dapat mengatakan
liftFoo1 = fmap
Jika Anda bisa menjadikannya
Foo
functor, mungkin Anda bisa menjadikannya sebagai functor aplikatif. Faktanya, jika Anda dapat menulisliftFoo2
maka contoh aplikatif terlihat seperti ini:import Control.Applicative instance Applicative Foo where pure x = Foo $ ... -- Wrap 'x' inside a Foo. (<*>) = liftFoo2 ($)
The
(<*>)
Operator untuk Foo memiliki jenis(<*>) :: Foo (a -> b) -> Foo a -> Foo b
Ini menerapkan fungsi dibungkus ke nilai yang dibungkus. Jadi jika Anda dapat menerapkan
liftFoo2
maka Anda dapat menulis ini dalam istilah itu. Atau Anda bisa mengimplementasikannya secara langsung dan tidak repotliftFoo2
, karenaControl.Applicative
termasuk modulliftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
dan juga ada
liftA
danliftA3
. Tetapi Anda sebenarnya tidak terlalu sering menggunakannya karena ada operator lainIni memungkinkan Anda menulis:
result = myFunction <$> arg1 <*> arg2 <*> arg3 <*> arg4
Istilah
myFunction <$> arg1
mengembalikan fungsi baru yang dibungkus dengan Foo. Ini pada gilirannya dapat diterapkan ke argumen berikutnya menggunakan(<*>)
, dan seterusnya. Jadi, sekarang alih-alih memiliki fungsi lift untuk setiap wilayah, Anda hanya memiliki rangkaian aplikasi daisy chain.sumber
lift id == id
danlift (f . g) == (lift f) . (lift g)
.id
dan.
merupakan komposisi panah dan panah identitas dari beberapa kategori. Biasanya ketika berbicara tentang Haskell, kategori yang dimaksud adalah "Hask", yang panah yang Haskell fungsi (dengan kata lain,id
dan.
mengacu pada fungsi Haskell Anda kenal dan cinta).instance Functor Foo
, bukaninstance Foo Functor
? Saya akan mengedit diri saya sendiri tetapi saya tidak 100% yakin.Paul dan yairchu adalah penjelasan yang bagus.
Saya ingin menambahkan bahwa fungsi yang diangkat dapat memiliki sejumlah argumen yang berubah-ubah dan tidak harus memiliki tipe yang sama. Misalnya, Anda juga dapat menentukan liftFoo1:
liftFoo1 :: (a -> b) -> Foo a -> Foo b
Secara umum, pengangkatan fungsi yang mengambil 1 argumen ditangkap di kelas tipe
Functor
, dan operasi pengangkatan disebutfmap
:fmap :: Functor f => (a -> b) -> f a -> f b
Perhatikan kemiripannya dengan
liftFoo1
tipe. Bahkan, jika sudahliftFoo1
, Anda bisa membuatFoo
contohFunctor
:instance Functor Foo where fmap = liftFoo1
Selanjutnya, generalisasi pengangkatan ke sejumlah argumen yang berubah-ubah disebut gaya aplikatif . Jangan repot-repot mendalami ini sampai Anda memahami pengangkatan fungsi dengan sejumlah argumen tetap. Tetapi ketika Anda melakukannya, Learn you a Haskell memiliki bab yang bagus tentang ini. The Typeclassopedia adalah dokumen bagus lainnya yang menjelaskan Functor dan Applicative (serta kelas tipe lainnya; gulir ke bawah ke bab kanan dalam dokumen itu).
Semoga ini membantu!
sumber
Mari kita mulai dengan sebuah contoh (beberapa ruang putih ditambahkan untuk presentasi yang lebih jelas):
> 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
mengubah fungsi tipe biasa menjadi fungsi tipe yang sama yang dibungkus dalamApplicative
, seperti daftarIO
, dll.Lift umum lainnya adalah
lift
dariControl.Monad.Trans
. Ini mengubah aksi monad dari satu monad menjadi aksi monad yang ditransformasikan.Secara umum, "lift" mengangkat fungsi / tindakan menjadi tipe "terbungkus" (sehingga fungsi asli bekerja "di bawah pembungkus").
Cara terbaik untuk memahami ini, dan monad dll, dan untuk memahami mengapa mereka berguna, mungkin dengan membuat kode dan menggunakannya. Jika ada sesuatu yang Anda kodekan sebelumnya yang Anda curigai bisa mendapatkan keuntungan dari ini (yaitu ini akan membuat kode itu lebih pendek, dll.), Coba saja dan Anda akan dengan mudah memahami konsepnya.
sumber
Mengangkat adalah konsep yang memungkinkan Anda mengubah fungsi menjadi fungsi terkait dalam pengaturan lain (biasanya lebih umum)
lihat di http://haskell.org/haskellwiki/Lifting
sumber
Menurut tutorial mengkilap ini , sebuah functor adalah suatu wadah (seperti
Maybe<a>
,List<a>
atauTree<a>
yang dapat menyimpan elemen dari jenis lain,a
). Saya telah menggunakan notasi umum Java<a>
,, untuk tipe elemena
dan menganggap elemen sebagai beri di pohonTree<a>
. Ada fungsifmap
, yang mengambil fungsi konversi elemen,a->b
dan wadahfunctor<a>
. Ini berlakua->b
untuk setiap elemen wadah yang secara efektif mengubahnya menjadifunctor<b>
. Ketika hanya argumen pertama yang diberikana->b
,,fmap
tunggufunctor<a>
. Artinya, memasoka->b
saja mengubah fungsi level elemen ini menjadi fungsifunctor<a> -> functor<b>
yang beroperasi di atas container. Ini disebut pengangkatandari fungsi tersebut. Karena wadah juga disebut Functor , Functors daripada Monad merupakan prasyarat untuk pengangkatan. Monad adalah semacam "paralel" dengan pengangkatan. Keduanya mengandalkan pengertian Functor dan dof<a> -> f<b>
. Perbedaannya adalah bahwa pengangkatan digunakana->b
untuk konversi sedangkan Monad mengharuskan pengguna untuk menentukana -> f<b>
.sumber
r
ke tipe (mari kita gunakanc
untuk variasi), adalah Functor. Mereka tidak "mengandung" apapunc
. Dalam contoh ini, fmap adalah komposisi fungsi, mengambil satua -> b
fungsi danr -> a
satu, memberi Andar -> b
fungsi baru. Masih tidak ada wadah. Juga, jika saya bisa, saya akan menandainya lagi untuk kalimat terakhir.fmap
adalah sebuah fungsi, dan tidak "menunggu" untuk apapun; "Wadah" menjadi Functor adalah inti dari pengangkatan. Selain itu, Monad, jika ada, adalah gagasan ganda untuk mengangkat: Monad memungkinkan Anda menggunakan sesuatu yang telah diangkat beberapa kali, seolah-olah hanya diangkat sekali - ini lebih dikenal sebagai perataan .To wait
,to expect
,to anticipate
adalah sinonim. Dengan mengatakan "fungsi menunggu" yang saya maksud adalah "fungsi mengantisipasi".b = 5 : a
danf 0 = 55
f n = g n
, keduanya melibatkan mutasi semu "wadah". Juga fakta bahwa daftar biasanya disimpan sepenuhnya dalam memori sedangkan fungsi biasanya disimpan sebagai penghitungan. Tapi daftar memoizing / monorphic yang tidak disimpan di antara panggilan keduanya memecahkan omong kosong dari ide itu.