Functor, Applicative, dan Monad dengan Gambar
Ada sebuan nilai (value) dasar:
Dan kita tahu bagaimana cara mengaplikasikan suatu fungsi pada nilai tersebut:
Cukup mudah. Mari kita perdalam hal ini dengan menganggap bahwa suatu nilai dapat berada di dalam suatu konteks. Sekarang Anda cukup menganggap bahwa konteks itu sebagai kotak yang dapat Anda berikan suatu nilai di dalamnya:
Sekarang, ketika Anda mengaplikasikan suatu fungsi kepada nilai tersebut, Anda akan mendapatkan hasil yang berbeda bergantung dengan konteks yang ada. Ide ini merupakan awal mula munculnya Funtor, Applicative, Monad, Arrow, dan lainnya. Salah satunya tipe data Maybe yang memiliki dua konteks yang saling berhubungan:
data Maybe a = Nothing | Just a
Pada bagian kedua kita akan melihat bagaimana fungsi bisa memberikan sesuatu yang berbeda ketika diapliaksikan ke Just a
dan Nothing
. Pertama, mari kita diskusikan Functor!
Functor
Ketika suatu nilai terbungkus dalam suatu konteks, Anda tidak dapat mengaplikasikan fungsi pada biasanya:
Disaat inilah fmap
muncul. fmap
tahu bagaimana caranya mengaplikasikan fungsi ke suatu nilai yang terbungkus oleh suatu konteks. Contohnya, jika Anda ingin mengaplikasikan fungsi (+3)
ke Just 2
menggunakan fmap
:
> fmap (+3) (Just 2)
Just 5
Bam! fmap menunjukan kita hasilnya! Tapi bagaimana fmap
tahu cara mengaplikasikan fungsi tersebut?
Sebenarnya, apa sih Functor itu?
Functor merupakan suatu typeclass. Di bawah ini merupakan definisi dari Functor:
Functor adalah suatu tipe data yang mendefinisikan bagaimana fmap
mengaplikasikan tipe data tersebut. Ini bagaimana cara fmap
bekerja:
Jadi kita bisa melakukan ini:
> fmap (+3) (Just 2)
Just 5
Dan fmap
secara ajaib mengaplikasikan fungsi tersebut, karena Maybe
merupakan Functor. Functor mendefinisikan bagaimana fmap
dapat berlaku pada Just
dan Nothing
:
instance Functor Maybe where
fmap func (Just val) = Just (func val)
fmap func Nothing = Nothing
Sebenarnya, ini yang terjadi di balik layar ketika kita menulis fmap (+3) (Just 2)
:
Jadi seperti, "Oke fmap
, tolong dong aplikasikan (+3)
ke Nothing
"
> fmap (+3) Nothing
Nothing
Seperti Morpheus di film Matrix, fmap
tahu apa yang harus ia lakukan; pada awalnya anda memulai dengan Nothing
, dan anda berakhir dengan Nothing
! Sekarang semua menjadi masuk akal, mengapa ada tipe data Maybe
. Sebagai contoh, ini bagaimana Anda bekerja dengan database di bahasa pemrograman yang tidak memiliki sintaks Maybe
:
post = Post.find_by_id(1)
if post
return post.title
else
return nil
end
Tapi, di Haskell:
fmap (getPostTitle) (findPost 1)
Jika findPost
mengembalikan post
, kita akan mendapatkan title
-nya dengan getPostTitle
. Jika findPost
mengembalikan Nothing
, kita akan mengembalikan Nothing
! Cukup elegan kan? Fungsi <$>
merupakan fmap
versi infiks dari fmap
, jadi Anda akan lebih sering menemukannya seperti ini:
getPostTitle <$> (findPost 1)
Contoh lain: apa yang terjadi ketika Anda mengaplikasikan fungsi ke list?
List ternyata merupakan functor juga! Di bawah ini merupakan definisi Functor dari list
instance Functor [] where
fmap = map
Oke, oke, satu contoh terakhir: apa yang terjadi ketika Anda mengaplikasikan fungsi ke fungsi lain?
fmap (+3) (+1)
Ini adalah ilustrasi dari fungsi:
Ini adalah ilustrasi saat fungsi diaplikasikan ke fungsi lain:
Hasilnya merupakan fungsi lain!
> import Control.Applicative
> let foo = fmap (+3) (+2)
> foo 10
15
Jadi fungsi merupakan Functor juga!
instance Functor ((->) r) where
fmap f g = f . g
Ketika Anda menggunakan fmap
pada sebuah fungsi, itu sama saja dengan melakukan melakukan fungsi komposisi!
Applicative
Applicative membuat semuanya menjadi lebih menarik. Mirip seperti Functor, nilai yang dibuat yang terbungkus oleh konteks
Tetapi fungsinya juga terbungkus di dalam konteks juga!
Yah. Begitulah. Applicative benar-benar menarik. Fungsi <*>
didefinisikan di Control.Applicative
, yang mana fungsi tersebut dapat mengaplikasikan fungsi yang terbungkus di suatu konteks ke suatu nilai yang terbungkus di suatu konteks:
i.e:
Just (+3) <*> Just 2 == Just 5
<*>
dapat menjadi lebih menarik dalam situasi tertentu. Contohnya:
> [(*2), (+3)] <*> [1, 2, 3]
[2, 4, 6, 4, 5, 6]
Ada beberapa hal yang dapat Applicative lakukan dan tidak dapat dilakukan oleh Functor. Bagaimana caranya mengaplikasikan fungsi yang memiliki dua argumen ke dua nilai yang terbungkus
Here's something you can do with Applicatives that you can't do with Functors. How do you apply a function that takes two arguments to two wrapped values?
> (+) <$> (Just 5)
Just (+5)
> Just (+5) <$> (Just 4)
ERROR ??? MENGAPA ???
Applicative:
> (+) <$> (Just 5)
Just (+5)
> Just (+5) <*> (Just 3)
Just 8
Applicative mengalahkan Functor dalam hal ini. "Pria besar bisa menggunakan fungsi dengan berapapun jumlah argumennya", katanya Applicative. "Dengan <$>
dan <*>
, aku bisa menggunakan fungsi yang membutuhkan nilai yang terbungkus. Dan aku bisa mengaplikasikan semua nilainya, dan aku akan mendapatkan nilainya terbungkus kembali! HAHAHAHA!"
> (*) <$> Just 5 <*> Just 3
Just 15
Dan, hei! Sebenarnya ada fungsi liftA2
yang melakukan hal yang sama:
> liftA2 (*) (Just 5) (Just 3)
Just 15
Monad
Langkah-langkah memahami Monad:
- Punya gelar S3 di bidang ilmu komputer.
- Buang gelar itu, karena Anda tidak memerlukan itu untuk memahami Monad!
Monad membuat semuanya menjadi lebih menarik.
Functor mengaplikasikan fungsi ke nilai yang terbungkus:
Applicative mengaplikasikan fungsi yang terbungkus ke nilai yang terbungkus:
Monad mengaplikasikan fungsi yang mengembalikan nilai yang terbungkus ke nilai yang terbungkus. Monad memiliki fungsi >>=
(Baca: bind) untuk melakukan hal tersebut.
Mari kita lihat contohnya. Maybe
merupakan Monad:
Anggap half
merupakan fungsi yang hanya dapat bekerja dengan bilangan genap:
half x = if even x
then Just (x `div` 2)
else Nothing
Bagaimana jika kita mengaplikasikan nilai yang terbungkus pada fungsi half
?
Kita perlu menggunakan >>=
untuk "menyedot" nilai yang terbungkus dan akan diaplikasikan ke dalam fungsi half
. Di bawah ini merupakan foto dari >>=
:
Beginilah cara fungsi >>=
bekerja:
> Just 3 >>= half
Nothing
> Just 4 >>= half
Just 2
> Nothing >>= half
Nothing
Apa yang terjadi sebenarnya? Monad adalah typeclass lain. Di bawah ini merupakan sebagian definisi dari Monad:
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
Dimana >>=
adalah:
Jadi Maybe
adalah Monad:
instance Monad Maybe where
Nothing >>= func = Nothing
Just val >>= func = func val
Ini yang terjadi dengan Just 3
!
Dan jika Anda memasukan Nothing
ke dalam fungsi tersebut.
Anda juga bisa menggabungkan fungsi tersebut:
> Just 20 >>= half >>= half >>= half
Nothing
Keren! Sekarang kita tahu kalau Maybe
merupakan Functor, Applicative, dan juga Monad.
Sekarang mari kita membahas contoh lainnya: Monad IO
:
Kita akan membahas tiga fungsi yang umum digunakan. getLine
yang tidak memiliki argumen dan mengambil input dari pengguna:
getLine :: IO String
readFile
menerima string (berupa nama berkas) dan mengembalikan konten berkas:
readFile :: FilePath -> IO String
putStrLn
menerima string dan menampilkannya:
putStrLn :: String -> IO ()
Ketiga fungsi di atas menerima nilai biasa (atau tidak) dan mengembalikan nilai yang terbungkus. Kita dapat menggabungkan operasi ketiganya menggunakan >>=
!
getLine >>= readFile >>= putStrLn
Mantap! Ajegile Monad!
Haskell juga menyediakan kita dengan syntatic sugar untuk Monad, namanya notasi do
:
foo = do
filename <- getLine
contents <- readFile filename
putStrLn contents
Kesimpulan
- Functor merupakan tipe data yang mengimplementasikan typeclass Functor.
- Applicative merupakan tipe data yang mengimplementasikan typeclass Applicative.
- Monad merupakan tipe data yang mengimplementasikan typeclass Monad.
- Maybe mengimplementasikan ketiga-tiganya, jadi Maybe adalah Functor, Applicative, sekaligus Monad.
Apa perbedaan diantara ketiga-tiganya?
functors: anda mengaplikasikan fungsi ke data yang terbungkus menggunakan fmap
atau <$>
applicatives: anda mengaplikasikan fungsi yang terbungkus ke data yang terbungkus menggunakan <$>
atau liftA
monads: anda mengaplikasikan fungsi yang mengembalikan nilai yang terbungkus dengan nilai yang terbungkus menggunakan >>=
atau liftM
Entri ini merupakan terjemahan dari Functors, Applicatives, And Monads In Pictures oleh Aditya Bhargava