พิจารณาFunctorประเภทคลาสใน Haskell ซึ่งfเป็นตัวแปรประเภทที่สูงกว่า:
class Functor f where
fmap :: (a -> b) -> f a -> f b
สิ่งที่ลายเซ็นประเภทนี้กล่าวคือ fmap จะเปลี่ยนพารามิเตอร์ type ของfจากaถึงbเป็น แต่ปล่อยfให้เหมือนเดิม ดังนั้นหากคุณใช้fmapกับรายการคุณจะได้รับรายชื่อหากคุณใช้กับโปรแกรมแยกวิเคราะห์คุณจะได้โปรแกรมแยกวิเคราะห์และอื่น ๆ และนี่คือการรับประกันแบบคงที่และเวลาคอมไพล์
ฉันไม่รู้ F # แต่ลองพิจารณาว่าจะเกิดอะไรขึ้นถ้าเราพยายามแสดงสิ่งที่Functorเป็นนามธรรมในภาษาเช่น Java หรือ C # ด้วยการถ่ายทอดทางพันธุกรรมและชื่อสามัญ แต่ไม่มีชื่อสามัญที่สูงกว่า ครั้งแรกลอง:
interface Functor<A> {
Functor<B> map(Function<A, B> f);
}
ปัญหานี้ลองครั้งแรกก็คือว่าการดำเนินการของอินเตอร์เฟซที่ได้รับอนุญาตให้กลับใด ๆFunctorคลาสที่ใช้ ใครบางคนสามารถเขียนวิธีFunnyList<A> implements Functor<A>ที่มีmapวิธีการส่งคืนคอลเลกชันประเภทอื่นหรือแม้แต่อย่างอื่นที่ไม่ใช่คอลเล็กชันเลย แต่ยังคงเป็นFunctorไฟล์. นอกจากนี้เมื่อคุณใช้mapวิธีนี้คุณจะไม่สามารถเรียกใช้เมธอดเฉพาะประเภทย่อยใด ๆ ในผลลัพธ์ได้เว้นแต่คุณจะดาวน์แคสต์เป็นประเภทที่คุณต้องการจริงๆ ดังนั้นเราจึงมีปัญหาสองประการ:
- ระบบประเภทไม่อนุญาตให้เราแสดงความไม่แปรเปลี่ยนที่
mapเมธอดส่งคืนFunctorคลาสย่อยเดียวกันกับตัวรับเสมอ
- ดังนั้นจึงไม่มีลักษณะที่ปลอดภัยแบบคงที่ในการเรียกใช้
Functorวิธีที่ไม่ใช่วิธีการที่เป็นผลมาจากmap.
มีวิธีอื่นที่ซับซ้อนกว่าที่คุณสามารถลองได้ แต่ไม่มีวิธีใดที่ใช้ได้ผลจริง ตัวอย่างเช่นคุณอาจลองเพิ่มความพยายามครั้งแรกโดยกำหนดประเภทย่อยของFunctorประเภทผลลัพธ์ที่ จำกัด :
interface Collection<A> extends Functor<A> {
Collection<B> map(Function<A, B> f);
}
interface List<A> extends Collection<A> {
List<B> map(Function<A, B> f);
}
interface Set<A> extends Collection<A> {
Set<B> map(Function<A, B> f);
}
interface Parser<A> extends Functor<A> {
Parser<B> map(Function<A, B> f);
}
สิ่งนี้ช่วยห้ามไม่ให้ผู้ใช้งานของอินเทอร์เฟซที่แคบกว่านั้นส่งคืนประเภทที่ไม่ถูกต้องFunctorจากmapวิธีการ แต่เนื่องจากFunctorคุณสามารถมีการใช้งานได้ไม่ จำกัด จำนวนจึงไม่ จำกัด จำนวนอินเทอร์เฟซที่แคบกว่าที่คุณต้องการ
( แก้ไข:และโปรดทราบว่าสิ่งนี้ใช้งานได้เพราะFunctor<B>ปรากฏเป็นประเภทผลลัพธ์เท่านั้นดังนั้นอินเทอร์เฟซลูกจึงสามารถ จำกัด ให้แคบลงได้ดังนั้น AFAIK เราจึงไม่สามารถ จำกัด การใช้ทั้งสองอย่างให้แคบลงMonad<B>ในอินเทอร์เฟซต่อไปนี้:
interface Monad<A> {
<B> Monad<B> flatMap(Function<? super A, ? extends Monad<? extends B>> f);
}
ใน Haskell ที่มีตัวแปรประเภทที่มีอันดับสูงกว่านี่คือ(>>=) :: Monad m => m a -> (a -> m b) -> m b)
อีกวิธีหนึ่งคือการใช้ชื่อเรียกซ้ำเพื่อลองและให้อินเทอร์เฟซ จำกัด ประเภทผลลัพธ์ของประเภทย่อยไว้ที่ประเภทย่อยเอง ตัวอย่างของเล่น:
interface Semigroup<T extends Semigroup<T>> {
T append(T arg);
}
class Foo implements Semigroup<Foo> {
Foo append(Foo arg);
}
class Bar implements Semigroup<Bar> {
Semigroup<Bar> append(Semigroup<Bar> arg);
Semigroup<Foo> append(Bar arg);
Semigroup append(Bar arg);
Foo append(Bar arg);
}
แต่เทคนิคประเภทนี้ (ซึ่งค่อนข้างจะเป็นความลับสำหรับนักพัฒนา OOP ที่ดำเนินการโดยโรงงานของคุณ แต่สำหรับนักพัฒนาที่ทำงานได้โดยใช้โรงงานของคุณเช่นกัน) ยังไม่สามารถแสดงFunctorข้อ จำกัดที่ต้องการได้:
interface Functor<FA extends Functor<FA, A>, A> {
<FB extends Functor<FB, B>, B> FB map(Function<A, B> f);
}
นี่คือปัญหานี้ไม่ได้ จำกัดFBที่จะมีเหมือนกันFเป็นFA-So ว่าเมื่อคุณประกาศประเภทList<A> implements Functor<List<A>, A>ที่mapวิธีการที่สามารถยังคงNotAList<B> implements Functor<NotAList<B>, B>กลับมาเป็น
ลองขั้นสุดท้ายใน Java โดยใช้ประเภทดิบ (คอนเทนเนอร์ที่ไม่ได้พารามิเตอร์):
interface FunctorStrategy<F> {
F map(Function f, F arg);
}
ที่นี่Fจะได้รับ instantiated ประเภท unparametrized เช่นเดียวหรือList Mapสิ่งนี้รับประกันได้ว่า a FunctorStrategy<List>สามารถคืนค่า a Listได้เท่านั้นแต่คุณได้ละทิ้งการใช้ตัวแปรประเภทเพื่อติดตามประเภทองค์ประกอบของรายการ
หัวใจของปัญหาที่นี่คือภาษาเช่น Java และ C # ไม่อนุญาตให้พารามิเตอร์ประเภทมีพารามิเตอร์ ใน Java ถ้าTเป็นตัวแปรชนิดที่คุณสามารถเขียนTและแต่ไม่List<T> T<String>ประเภทที่สูงกว่าจะลบข้อ จำกัด นี้ออกเพื่อที่คุณจะได้มีอะไรแบบนี้ (ไม่ได้คิดออกทั้งหมด):
interface Functor<F, A> {
<B> F<B> map(Function<A, B> f);
}
class List<A> implements Functor<List, A> {
<B> List<B> map(Function<A, B> f) {
}
}
และกล่าวถึงบิตนี้โดยเฉพาะ:
(ฉันคิดว่า) ฉันเข้าใจว่าแทนที่จะเป็นประเภทที่ดีกว่าmyList |> List.map fหรือmyList |> Seq.map f |> Seq.toListสูงกว่าช่วยให้คุณเขียนได้ง่ายๆmyList |> map fและมันจะส่งกลับ a List. มันเยี่ยมมาก (สมมติว่าถูกต้อง) แต่ดูเหมือนจะไม่สุภาพ? (และไม่สามารถทำได้ง่ายๆโดยปล่อยให้ฟังก์ชันโอเวอร์โหลด) ฉันมักจะแปลงเป็นSeqอย่างไรก็ตามจากนั้นฉันสามารถแปลงเป็นอะไรก็ได้ที่ฉันต้องการในภายหลัง
มีหลายภาษาที่สรุปแนวคิดของmapฟังก์ชันด้วยวิธีนี้โดยการสร้างแบบจำลองราวกับว่าหัวใจสำคัญคือการทำแผนที่เกี่ยวกับลำดับ คำพูดของเธอนี้อยู่ในจิตวิญญาณว่าถ้าคุณมีประเภทที่แปลงสนับสนุนไปและกลับจากSeqคุณจะได้รับการดำเนินการแผนที่ "ฟรี" โดย Seq.mapreusing
อย่างไรก็ตามใน Haskell Functorชั้นเรียนทั่วไปมากกว่านั้น มันไม่ได้เชื่อมโยงกับแนวคิดของลำดับ คุณสามารถนำไปใช้fmapกับประเภทที่ไม่มีการแมปกับลำดับที่ดีเช่นIOการดำเนินการตัวรวมตัวแยกวิเคราะห์ฟังก์ชัน ฯลฯ :
instance Functor IO where
fmap f action =
do x <- action
return (f x)
newtype Function a b = Function (a -> b)
instance Functor (Function a) where
fmap f (Function g) = Function (f . g)
แนวคิดของ "การทำแผนที่" ไม่ได้เชื่อมโยงกับลำดับ ควรทำความเข้าใจกับกฎหมาย functor:
(1) fmap id xs == xs
(2) fmap f (fmap g xs) = fmap (f . g) xs
อย่างไม่เป็นทางการ:
- กฎข้อแรกกล่าวว่าการทำแผนที่ด้วยฟังก์ชัน identity / noop เหมือนกับการไม่ทำอะไรเลย
- กฎข้อที่สองกล่าวว่าผลลัพธ์ใด ๆ ที่คุณสามารถสร้างได้โดยการทำแผนที่สองครั้งคุณสามารถสร้างได้โดยการทำแผนที่เพียงครั้งเดียว
นี่คือเหตุผลที่คุณต้องการfmapรักษาประเภทไว้เพราะทันทีที่คุณได้รับmapการดำเนินการที่ให้ผลลัพธ์ประเภทอื่นการรับประกันแบบนี้จะยากขึ้นมาก