24 Comonads (Sketch)
ในตอนนี้เราได้ครอบคลุมmonadsหมดแล้ว เราสามารถใช้ผลประโยชน์ของdualityและได้comonadsมาแบบฟรีๆอย่างง่านๆโดนการย้อนทางลูกศรและทำงานในcategoryตรงข้าม
จำได้ว่าในระดับที่เรียบง่ายที่สุดmonadsนั้นเกี่ยวกับการประกอบกันลูกศรKleisliอย่าง
-> m b a
ที่m
คือfunctorที่คือmonad ถ้าเราใช้ตัวอักษรw
(ที่ก็คือm
กลับหัว)สำหรับcomonad เราสามารถนิยามลูกศรco-Kleisliในฐานะmorphismของtypeนี้
-> b w a
operator fishในแบบเดียวกันสำหรับลูกศรco-Kleisliนั้นถูกนิยามไว้ว่า
(=>=) :: (w a -> b) -> (w b -> c) -> (w a -> c)
สำหรับลูกศรco-Kleisliในการก่อให้เกิดcategory เราก็ต้องมีลูกศรco-Kleisliที่เป็นidentityที่ถูกเรียกว่าextract
extract :: w a -> a
มันคือdualของreturn
เราก็ได้ทำการบังคับกฏของการสลับกลุ่มและรวมไปถึงกฏidentityด้านช้ายและขวา นำสิ่งเหล่านี้เข้าด้วยกัน เราสามารถนิยามcomonadในHaskellว่า
class Functor w => Comonad w where
(=>=) :: (w a -> b) -> (w b -> c) -> (w a -> c)
extract :: w a -> a
ในการปฏิบัติเราจะใช้primitivesที่แต่ต่างเล็กน้อย ในที่เราจะได้เห็นในอีกไม่ช้า
คำถามคือ อะไรคือการใช้งานของcomonadsในการเขียนโปรแกรม
24.1 เขียนโปรแกรมกับComonads
เรามาเปรียบเทียบmonadกับcomonad monadให้วิธีการในการใล่ค่าไปยังในภาชนะโดยการใช้return
มันไม่ได้ให้คุณการเข้าถึงของค่าหรือค่าต่างๆที่ถูกเก็บข้างใน แน่นอนว่าdata structuresที่เขียนmonadsอาจจะให้การเข้าถึงเนื้อหาของพวกมัน แต่นั้นถูกมองว่าเป็นสิ่งเสริมเพิ่มเติม ได้ไม่มีinterfaceเดียวกันสำหรับการดึงค่าจากmonad และเราได้เห็นตัวอย่างของmonadIO
ที่มีความภูมิใจมันเองที่ไม่เคยที่จะเเปิดเผยเนื้อหาข้างใน
comonadในอีกทางหนึ้งให้วิธีการในการดีงค่าๆหนึ่งจากมัน มันไม่ได้ให้ให้วิธีการในการใส่ค่าเข้าไป ดังนั้นถ้าคุณต้องการที่จะคิดถึงcomonadในฐานะภาชนะ มันนั้นในทุกๆครั้งมาด้วยเนื้อหาขางในที่มีอยู่แล้วและมันอนุญาตให้คุณได้ส่องเข้าไปในมัน
เหมือนกับลูกศรKleisliนำค่าและสร้างผลัพธ์ที่ถูกประดับแล้ว(มันกประดับมันด้วยบริบท)ลูกศรco-Kleisliนำค่าคู่กับบริบททั้งหมดและสร้างผลลัพธ์ออกมา มันคือตัวแทนของการคำนวนที่มีบริบท (contextual computation)
24.2 ComonadsแบบProduct
จำmonad monadได้หรือเปล่า? เราได้นำเสนอมันในการแก้ใขปัญหาของการเขียนการคํานวณที่ต้องการบางenvironmentที่อ่านได้อย่างเดียวe
การคำนวณแบบนี้สามารถถูกแสดงในฐานะfunctionแบบpureในรูปของ
-> b (a, e)
เราได้ใช้การcurryในการแปลงมันไปยังลูกศรKleisliอย่าง
-> (e -> b) a
แต่สังเกตว่าfunctionเหล่านี้นั้นมีรูปแบบของลูกศรco-Kleisliอยู่แล้ว เรามาแปลงargumentsของพวกมันไปยังรูปแบบfunctorที่สะดวกกว่านี้
data Product e a = Prod e a deriving Functor
เราสามารถนิยามการoperatorของการประกอบกันย่างง่ายดายโดยการทำให้environmentเดียวกันพร้อมใช้กับลูกศรที่เรากำลังทำการประกอบ
(=>=) :: (Product e a -> b) -> (Product e b -> c) -> (Product e a -> c)
=>= g = \(Prod e a) -> let b = f (Prod e a)
f = g (Prod e b)
c in c
การเขียนของextract
แค่ทิ้งenvironmentออกไป
Prod e a) = a extract (
ไม่แปลกใจที่comonad productสามารถถูกใช้ในการกระทำการคำนวณแบบเดียวกับเป๊ะๆในฐานะmonad reader ในแบบหนึ่ง การเขียนแบบcomonadicของenvironmentนั้นเป็นธรรมชาติมากว่า (มันตาม จิตวิญญาณของ”การคำนวณในบริบท”) ในอีกทางหนึ่งmonadsมากับารแต่งsyntaxให้ง่ายขึ้น(syntactic sugar)ที่สะดวกของเครื่องหมายdo
ความเชื่อมต่อระหว่างmonad readerและcomonad productนั้นลึกกว่านี้ ที่ต้องเกี่ยวกับความจริงที่ว่าfunctor readerคือadjointด้านขวาของfunctor product แต่โดยทั่วไปแล้วcomonadsครอบคลุมความมายการคำนวณที่แต่ต่างกับmonads เราจะเห็นตัวอย่างมากขึ้นหลังจากนี้
มันง่ายมากที่จะgeneralizecomonadProduct
ไปยังtypesแบบproductทั่วๆไปจนรวมไปถึงtuplesและrecords
24.3 แยกชิ้นส่วนการคำนวณ
ต่ามต่อจากขบวนการของการทำให้เป็นduality เราสามารถเริ่มไปและทำให้เป็นdualityของbindและjoinทางmonad ในอีกแบบหนึ่งเราสามารถที่ขบวนการเดียวกันที่เราได้ใช้กับmonads ที่เราศึกษาโครวสร้างของoperator fish แนวทางแบบนี้ดู่น่าจะให้ความรู้ได้มากกว่า
จุดเริ่มคือการรู้ว่าoperatorของการประกอบกันต้องสร้างลูกศรco-Kleisliที่นำw a
และสร้างc
วิธีทางเดียวในการสร้างc
คือการใช้functionที่สองไปกับargumentของtypew b
อย่าง
(=>=) :: (w a -> b) -> (w b -> c) -> (w a -> c)
=>= g = g ... f
แต่เราสามารถในการสร้างtypew b
ที่อาจจะถูกให้กับg
? เราได้มีargumentของtypew a
พร้อมอยู่แล้วและfunctionf :: w a -> b
คำตอบคือการนิยามdualของbindที่ถูกเรียกว่าextend
extend :: (w a -> b) -> w a -> w b
ในการใช้extend
เราสามารถเขียนการประกอบกันว่า
=>= g = g . extend f f
เราสามารถทำการแยกชิ้นส่วนextend
หรือเปล่า? คุณอาจจะอยากที่จะพูดว่าทำไมไม่แค่ใช้งานfunctionw a -> b
ไปกับargumentw a
แต่คุณก็จะรู้ในทันที่ว่าคุณไม่มีทางในการแปลงb
ไปยังw b
จำได้ว่าcomonadไม่ให้ทางในการliftค่า ในจุดๆนี้ ในการสร้างแบบคล้ายๆกับของmonads เราได้ใช้fmap
วิธีเดียวเราอาจจะใช้fmap
ก็ถ้าเรามีบางอย่างของtypew (w a)
ที่พร้อมใช้ ถ้าเราสามารถเปลี่ยนw a
ไปยังw (w a)
และเพื่อความสะดวกที่ก็อาจจะเป็นdualของjoin
เป๊ะๆ เราเรียกมันว่าduplicate
duplicate :: w a -> w (w a)
ดังนั้นแค่เหมือนกับนิยามของmonadเราได้มีสามนิยามที่เหมือนกันสำหรับcomonadอย่าง การใช้ลูกศรco-Kleisli extend
หรือduplicate
ในที่นี้คือนิยามของHaskellที่นำมาโดยตรงจากlibraryControl.Comonad
class Functor w => Comonad w where
extract :: w a -> a
duplicate :: w a -> w (w a)
= extend id
duplicate extend :: (w a -> b) -> w a -> w b
= fmap f . duplicate extend f
ในการที่มีการเขียนแบบมาตราฐานของextend
ในรูปแบบของduplicate
และในทางกลับกัน ดังนั้นคุณแค่ต้องoverrideตัวไดตัวหนึ่ง
แนวคิดข้างหลังfunctionsเหล่านี้มีมาจากแนวคิดที่ว่าโดนทั่วไปแล้วcomonadสามารถถูกคิดในฐานะภาชนะที่มีวัตถุต่างของtypea
(comonadแบบproductนั้นคือสิ่งที่เป็นพิเศษของแค่ค่าๆหนึ่ง) แนวคิดของค่า”ตอนๆนี้” สิ่งที่สามารถเข้าถึงได้ง่ายผ่านextract
ลูกศรco-Kleisliได้แสดงการคำนวณบางอย่างที่เพ่งเล็งไปที่ค่าในปัจจุบันแต่มันก็มีการเข้าถึงสำหรับทุกๆค่าที่อยู่รอบๆทั้งหมด ให้คิดถึงเกมของชีวิต(game of life)ของConway ในแต่ละช่อง(cell)ได้มีค่า(ที่โดยทั่วไปแล้วเป็นแค่True
หรือFalse
) comonadที่ตรงกับเกมของชีวิตก็จะเป็นตารางของช่อง”ในตอนๆนี้”
ดังนั้นแล้วduplicate
ทำอะไร? มันนำภาชนะแบบcomonadicw a
และสร้างภาชนะของภาชนะw (w a)
แนวคิดคือว่าแต่ละภาชนะนั้นเพ่งเล็งไปยังa
ในw a
ที่แตกต่างกัน ในเกมของชีวิตคุณอาจจะมีตารางของตาราง ที่แต่ละช่องของตารางข้างนอกนั้นเก็บตารางข้างในเอาไว้ที่ทำการเพ่งเล็งบนช่องที่แตกต่างออกไป
ในตอนนี้เรามาดูที่extend
มันนำลูกศรco-Kleisliและภาชนะแบบcomonadicw a
ที่เต็มไปด้วยa
ต่างๆ มันนำการคำนวณมาใช้สำหรับทุกๆa
ต่างๆเหล่านี้แทนที่มันด้วยb
ต่างๆ ผลลัพธ์คือภาชนะแบบcomonadicที่เต็มไปด้วยb
ต่างๆ extend
นำมันโดยการ เคลื่อนสอ่งที่เพ่งเล็งจากa
ตัวหนึ่งไปยังอีกตัวและทำการใช้ลูกศรco-Kleisliไปยังพวมมันแต่ละตัว ในเกมของชีวิตลูกศรco-Kleisliก็จะคำนวณแต่ละสถานะใหม่ของช่องในตอนๆนี้ ในการทำแบบนั้น มันอาจจะดูไปในบริบท(ที่ก็เดาว่าเป็นช่องรอบๆที่ใกล้ที่สุด) การเขียนแบบมาตราฐานของextend
แสดงให้เห็นถึงขบวนการนี้ ในตอนแรกเราเรียกduplicate
ในการสร้างจุดที่ต้องเพ่งเล็งทั้งหมดแล้วก็ทำการใช้f
ไปยังพวมมันแต่ละอัน
24.4 ComonadแบบStream
ขบวนการนี้ของการเลื่อนจุดที่สนใจจากสมาชิกหนึ่งไปยังภาชนะของอีกอันหนึ่งนั้นแลดงให้เห็นอย่างดีที่สุดโดยกับตัวอย่างของstreamไม่มีที่สิ้นสุด streamอย่างนี้คือเหมือนกับlistแต่แค่มันไม่ต้องมีconstructorว่างอย่าง
data Stream a = Cons a (Stream a)
มันคือFunctor
อย่างชัดๆ
instance Functor Stream where
fmap f (Cons a as) = Cons (f a) (fmap f as)
จุดสนใจของstreamคือสมาชิกแรกของมันดังนั้นนี้คือการเขียนของextract
Cons a _) = a extract (
duplicate
สร้างstreamของstream ที่แต่ละตัวเพ่งเล็งไปที่สมาชิกที่แตกต่างกัน
Cons a as) = Cons (Cons a as) (duplicate as) duplicate (
สมาชิกแรกคือstreamดังเดิม สมาชิกที่สองคือหางของstreamดังเดิม มาชิกที่สองคือหางของมันเองและไปเรื่อยๆไม่มีที่สิ้นสุด
ที่คือinstanceตัวเต็ม
instance Comonad Stream where
Cons a _) = a
extract (Cons a as) = Cons (Cons a as) (duplicate as) duplicate (
มันคือวิธีการที่functionalอย่างมากในการมองไปยังstreams ในภาษาแบบimperative เราอาจจะเรื่อมด้วยmethodadvance
ที่เลื่อนstreamโดยหนึ่งตำแหน่ง ในที่นี้duplicate
สร้างstreamที่ถูกเลื่อนใรการทำแค่ครังเดียว ความlazyของHaskellทำให้สิ่งนี้เป็นไปได้และแม้กระทั่งเป็นที่ต้องการ แน่นอน ในการทำให้Stream
นั้นใช้งานได้จริง เราอาจจะก็เขียนรูปแบบที่คล้ายกันของadvance
ว่า
tail :: Stream a -> Stream a
tail (Cons a as) = as
แต่มันไม่เคยเป็นส่วนหนึ่งของinterfaceของcomonad
ถ้าคุณมีประสบการกับการการประมวลผลสัญญาณดิจิทัล(digital signal processing)คุณจะเห็นในทันที่ว่าลูกศรco-Kleisสำหรับstreamนั้นคือแค่filterแบบdigitalและextend
นั้นสร้างstreamที่ผ่านการfilterแล้ว
ในฐานะตัวอย่างที่ง่ายๆ เรามาเขียนfilterแบบmoving average ในที่นี้คือfunctionที่บวกสมาชิกn
ตัวของstreamไว้
sumS :: Num a => Int -> Stream a -> a
Cons a as) = if n <= 0 then 0 else a + sumS (n - 1) as sumS n (
นี้คือfunctionในการคำนวณค่าเฉลี่ยของสมาชิกn
ตัวแรกเอาไว้ในstream
average :: Fractional a => Int -> Stream a -> a
= (sumS n stm) / (fromIntegral n) average n stm
ในการใช้งานบาส่วนแล้วของaverage n
คือลูกศรco-Kleisดังนั้น เราสามารถextend
มันไปยังทั้งstreamได้เลย
movingAvg :: Fractional a => Int -> Stream a -> Stream a
= extend (average n) movingAvg n
ผลลัพธ์คือของstreamของrunning averages
streamนั้นคือตัวอย่างของcomonadหนิ่งมิติทางเดียว มันสามารถทำได้เป็นสองทิศทางหรือขยายไปยังสองหรือมากกว่ามิตืได้ง่ายกว่า
24.5 ComonadแบบCategory
ในการนิยามcomonadในทฤษฎีcategoryนั้นเป็นการฝึกหัดที่ตรงไปตรงมาของduality เหมือนกับmonad เราเรืิ่มกับendofunctor\(T\) การแปลงแบบธรรมชาติสองตัวอย่าง\(\eta\)และ\(\mu\)ที่นิยามmonadนั้นแค่กลับทางสำหรับcomonadอย่าง
\[ \begin{align*} \varepsilon & :: T \to I \\ \delta & :: T \to T^2 \end{align*} \]
ส่นประกอบของการแปลงเหล่านี้ตรงกับextract
และduplicate
กฏของComonadนั้นคือถาพสะท้อนของกฎของmonad ไม่มีsurpriseในที่นี้
แล้วก็ได้มีการได้มาของmonadจากadjunction dualityนั้นย้อนadjunctionคือadjointด้านช้ายกลายมาเป็นadjointด้านขวาและในทางกลับกัน และเนื่องด้วยการประกอบกัน\(R\circ L\)นิยามmonad\(L\circ R\)ต้องนิยามcomonad counitของadjunctionอย่าง
\[ \varepsilon :: L \circ R \to I \]
นั้นก็คือ\(\varepsilon\)เดียวกันที่เราเห็นในนิยามของcomonad(หรือในส่วนแระกอบ ในฐานะextract
ของHaskell) เราก็สามารถใช้unitของadjunctionอย่าง
\[ \eta :: I \to R \circ L \]
ในการใส่\(R\circ L\)ในตรงการของ\(L\circ R\)และทำการสร้าง\(L \circ R \circ L \circ R\) การสร้าง\(T^2\)จาก\(T\)ได้นิยาม\(\delta\)และสิ่งนี้ได้ทำให้นิยามของcomonadสมบูรณ์
เราก็ได้เห็นว่าmonadนั้นคือmonoid dualของประโยคนี้ก็อาจจะต้องการใช้งานของcomonoid แล้วอะไรคือcomonoid? นิยามดั้งเดิมของmonoidในฐานะcategoryที่มีสมาชิกเดียวไม่ได้มีdualที่น่าสนใจอะไร ในตอนที่คุณย้อนทิศทางของendomorphismsทั้งหมด คุณได้มีอีกmonoidหนึ่ง แต่จำได้ว่าในแนวทางของเรากับmonad เราใช้นิยามที่กว้างกว่าของmonoidในฐานะวัตถุในcategoryแบบmonoidal การสร้างนั้นมาจากสองmorphismsอย่าง
\[ \begin{align*} \mu & :: m \otimes m \to m \\ \eta & :: i \to m \end{align*} \]
การย้อนของMorphismเหล่านี้สร้างcomonoidในcategoryแบบmonoid
\[ \begin{align*} \delta & :: m \to m \otimes m \\ \varepsilon & :: m \to i \end{align*} \]
เราสามารถเขียนนิยามของcomonoidในHaskell
class Comonoid m where
split :: m -> (m, m)
destroy :: m -> ()
แต่มันก็จะตรงไปตรงมา แน่นอนว่าdestroy
ไม่สนใจargumentของมัน
= () destroy _
split
คือแค่คู่ของfunctions
= (f x, g x) split x
ในตอนนี้พิจารณากฏของcomonoidที่คือdualของกฏunitของmonoid
. bimap destroy id . split = id
lambda . bimap id destroy . split = id rho
ในที่นี้lambda
และrho
คือunitorsด้านช้ายและขวาตามลำดับ(ลองดูนิยามของcategoryแบบmonoid) นำมันเข้ามาในนิยามเราก็ได้
id (split x))
lambda (bimap destroy = lambda (bimap destroy id (f x, g x))
= lambda ((), g x)
= g x
ที่จะพิสูจน์ว่าg = id
ในทางเดียวกันกฏที่สองนั้นขยายไปยังf = id
โดยสรุปแล้ว
= (x, x) split x
ที่แสดงว่าในHaskell(และโดยทั่วไปในcategory\(\textbf{Set}\))ทุกๆวัตถุนั้นคือcomonoidที่ตรงไปตรงมา
โชคดีที่ว่าได้มีcategoryแบบmonoidอื่นๆที่น่าสนใจมากกว่าที่ทำการนิยามcomonoid หนึ่งในนั้นคือcategoryของendofunctors และมันกลับมาเป็นที่ว่า เหมือนกับmonadคือmonoidในcategoryของendofunctors
comonadคือcomonoidในcategoryของendofunctors
24.6 Comonadกักเก็บ
อีกตัวอย่างหนึ่งที่สำคัญของcomonadคือdualของmonadสถานะ มันถูกเรียกว่าcomonad costateหรือcomonadกักเก็บ
เราได้เห็นแล้วว่าmonadสถานะนั้นถูกสร้างโดยadjunctionที่นิยามexponentialsว่า
\[ \begin{align*} L\ z & = z\times{}s \\ R\ a & = s \Rightarrow a \end{align*} \]
เราจะใช้adjunctionเดียวกันในการนิยามcomonadของcostate comonadนั้นถูกนิยามโดยการประกอบกันอย่าง\(L\circ R\)
\[ L\ (R\ a) = (s \Rightarrow a)\times{} \]
แปลงสิ่งนี้ไปยังHaskell เราเริ่มด้วยadjunctionระหว่างfunctorProduct
ในทางด้านช้ายและfunctorReader
ในทางด้านขวา การประกอบกันของProduct
หลังReader
นั้นตรงกันกับนิยามดังต่อไปนี้
data Store s a = Store (s -> a) s
counitของadjunctionที่นำมาที่วัตถุ\(a\)คือmorphismอย่าง
\[ \varepsilon_a :: ((s \Rightarrow a)\times{}s) \to a \]
หรือในสัญลักษณ์ของHaskell
Prod (Reader f) s)) = f s counit (
มันกลายมาเป็นextract
ของเรา
Store f s) = f s extract (
unitของadjunctionคือ
unit :: a -> Reader s (Product a s)
= Reader (\s -> Prod a s) unit a
สามารถถูกเขียนใหม่ในฐานะconstructorข้อมูลที่ถูกใช้บางส่วนว่า
Store f :: s -> Store f s
เราสร้าง\(\delta\)หรือduplicate
ในฐานะการประกอบกันแนวนอนว่า
\[ \begin{align*} \delta & :: L \circ R \to L \circ R \circ L \circ R \\ \delta & = L \circ \eta \circ R \end{align*} \]
เราได้แอบ\(\eta\)ผ่าน\(L\)ช้ายสุดที่คือfunctorProduct
มันหมายความถึงการกระทำกับ\(\eta\)หรือStore f
ในส่วนประกอบด้านช้ายของคู่(นั้นคือสิ่งที่fmap
สำหรับProduct
จะทำ) เรามี
Store f s) = Store (Store f) s duplicate (
(จำไว้ว่าในสูตรสำหรับ\(\delta,L\)และ\(R\)เป็นตัวแทนสำหรับการ แปลงแบบธรรมชาติแบบidentityที่ส่วนประกอบนั้นคือmorphisms identity)
ในที่นี้นิยามโดยสมบูรณ์ของcomonadStore
คือ
instance Comonad (Store s) where
Store f s) = f s
extract (Store f s) = Store (Store f) s duplicate (
คุณอาจจะคิดถึงส่วนของReader
ในStore
ในฐานะภาชนะที่ทั่วไปของa
ต่างๆที่คู่กับกุญแจของสมาชิกในtypes
ตัวอย่างเช่นถ้าs
คือInt
แล้วReader Int a
คือstreamสองทางที่ไม่มีที่สิ้นสุดของa
Store
จับคู่ภาชนะนี้กับค่าของtypeของkey ตัวอย่างเช่นReader Int a
นั้นถูกจับคู่กับInt
ในกรณีนี้extract
ได้ใช้จำนวณเต็มนี้ที่เป็นตัวชี้ไปยังstreamที่ไม่มีที่สิ้นสุด คุณอาจจะคิดถึงส่วนประกอบที่สองของStore
ในฐานะตำแหน่งปัจจุบัน
ตามมาต่อจากตัวอย่างนี้duplicate
สร้างstreamที่ไม่มีที่สิ้นสุดใหม่ที่มีตัวชี้เป็นInt
streamนี้เก็บstreamsในฐานะสมาชิกของมัน โดยเฉพาะเช่นที่ตำแหน่งในตอนนี้ มันเก็บstreamดั้งเดิมเอาไว้ แต่ถ้าคุณใช้บางInt
อีกตัว(ทั้งบวกและลบ)ในฐานะกุญแจ คุณก็จะได้streamที่ถูกเลื่อนที่ในตัวชี้ใหม่
โดนทั่วไปคุณสามารถทำให้ตัวเองมั่นใจว่าในตอนที่extract
กระทำบนStore
ที่ถูกduplicate
มันได้สร้างStore
ดั้งเดิม(ในความเป็นจริงแล้ว กฏทางidentityสำหรับcomonadบอกว่าextract . duplicate = id
)
comonadStore
มีบทบาทที่สำคัญในฐานะพื้นฐานทางทฤษฎีสำหรับlibraryLens
ในแนวคิดแล้วcomonadอย่างStore s a
นั้นรวมแนวคิดที่ของ”การเพ่งเล็ง” (เหมือนlens) ไปยังบางโครงสร้างย่อยของtypea
โดยการใช้types
ในฐานะตัวชี้ โดยเฉพาะเช่นfunctionของtype
-> Store s a a
นั้นเท่ากับคู่ของfunctionsต่างๆว่า
set :: a -> s -> a
get :: a -> s
ถ้าa
คือtypeของproductset
ก็อาจจะถูกเขียนในฐานะการจัดวางfieldของtypes
ข้างในa
ในขณะเดียวกันก็ทำการreturning รูปแบบของa
ที่ถูกดัดแปลง ในทางเดียวกันget
ก็อาจจะถูกเขียนให้อ่านค่าของfields
จากa
เราจะมาสำรวจแนวคิดเหล่านี้ในบทถัดไป
24.7 โจทย์ท้าทาย
- ลองเขียนเกมของชีวิตของConwayโดยการใช้comonad
Store
คำใบ้: อะไรคือtypeคุณต้องเลือกสำหรับs
?