พิจารณา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.map
reusing
อย่างไรก็ตามใน 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
การดำเนินการที่ให้ผลลัพธ์ประเภทอื่นการรับประกันแบบนี้จะยากขึ้นมาก