ใครช่วยบอกหน่อยได้ไหมว่าเหตุใด Haskell Prelude จึงกำหนดฟังก์ชันแยกกันสองฟังก์ชันสำหรับการยกกำลัง (เช่น^
และ**
) ฉันคิดว่าระบบประเภทควรจะกำจัดการทำซ้ำประเภทนี้
Prelude> 2^2
4
Prelude> 4**0.5
2.0
ใครช่วยบอกหน่อยได้ไหมว่าเหตุใด Haskell Prelude จึงกำหนดฟังก์ชันแยกกันสองฟังก์ชันสำหรับการยกกำลัง (เช่น^
และ**
) ฉันคิดว่าระบบประเภทควรจะกำจัดการทำซ้ำประเภทนี้
Prelude> 2^2
4
Prelude> 4**0.5
2.0
คำตอบ:
มีจริงยกกำลังสามผู้ประกอบการ: (^)
, และ(^^)
เป็นเลขชี้กำลังอินทิกรัลที่ไม่เป็นลบคือการยกกำลังจำนวนเต็มและเป็นการยกกำลังจุดลอยตัว:(**)
^
^^
**
(^) :: (Num a, Integral b) => a -> b -> a
(^^) :: (Fractional a, Integral b) => a -> b -> a
(**) :: Floating a => a -> a -> a
เหตุผลคือความปลอดภัยของประเภท: ผลลัพธ์ของการดำเนินการเชิงตัวเลขโดยทั่วไปจะมีประเภทเดียวกับอาร์กิวเมนต์อินพุต แต่คุณไม่สามารถเพิ่มInt
กำลังทศนิยมและรับผลลัพธ์ของประเภทInt
ได้ ดังนั้นระบบประเภทจึงป้องกันไม่ให้คุณทำสิ่งนี้: (1::Int) ** 0.5
สร้างข้อผิดพลาดประเภท (1::Int) ^^ (-1)
เดียวกันจะไปสำหรับ
อีกวิธีหนึ่งที่จะนำนี้: Num
ประเภทจะปิดใต้^
(พวกเขาไม่จำเป็นต้องมีความผกผัน) Fractional
ประเภทอยู่ภายใต้การปิด^^
, ประเภทที่อยู่ภายใต้ปิดFloating
**
เนื่องจากไม่มีFractional
อินสแตนซ์สำหรับInt
คุณจึงไม่สามารถยกระดับเป็นพลังลบได้
ตามหลักการแล้วอาร์กิวเมนต์ที่สองของ^
จะถูก จำกัด แบบคงที่ให้ไม่เป็นลบ (ปัจจุบัน1 ^ (-2)
มีข้อยกเว้นรันไทม์) แต่ไม่มีประเภทของจำนวนธรรมชาติในPrelude
.
ระบบประเภทของ Haskell ไม่มีประสิทธิภาพเพียงพอที่จะแสดงตัวดำเนินการเลขชี้กำลังทั้งสามเป็นหนึ่งเดียว สิ่งที่คุณต้องการจริงๆมีดังนี้:
class Exp a b where (^) :: a -> b -> a
instance (Num a, Integral b) => Exp a b where ... -- current ^
instance (Fractional a, Integral b) => Exp a b where ... -- current ^^
instance (Floating a, Floating b) => Exp a b where ... -- current **
สิ่งนี้ใช้ไม่ได้จริงแม้ว่าคุณจะเปิดส่วนขยายคลาสประเภทหลายพารามิเตอร์เนื่องจากการเลือกอินสแตนซ์จะต้องฉลาดกว่าที่ Haskell อนุญาตในปัจจุบัน
Int
Integer
เพื่อให้สามารถประกาศอินสแตนซ์ทั้งสามนี้ได้ความละเอียดของอินสแตนซ์ต้องใช้การย้อนรอยและไม่มีคอมไพเลอร์ Haskell ดำเนินการดังกล่าว
มันไม่ได้กำหนดตัวดำเนินการสองตัว - มันกำหนดสามตัว! จากรายงาน:
มีการดำเนินการยกกำลังสองอาร์กิวเมนต์สามรายการ: (
^
) ยกจำนวนใด ๆ ให้เป็นเลขกำลังจำนวนเต็มที่ไม่เป็นลบ, (^^
) ยกจำนวนเศษส่วนเป็นกำลังจำนวนเต็มใด ๆ และ (**
) รับอาร์กิวเมนต์ทศนิยมสองตัว ค่าx^0
หรือx^^0
เท่ากับ 1 สำหรับค่าใด ๆx
รวมถึงศูนย์0**y
ไม่ได้กำหนด
ซึ่งหมายความว่ามีอัลกอริทึมที่แตกต่างกันสามแบบซึ่งสองวิธีให้ผลลัพธ์ที่แน่นอน ( ^
และ^^
) ในขณะที่**
ให้ผลลัพธ์โดยประมาณ การเลือกโอเปอเรเตอร์ที่จะใช้คุณจะเลือกอัลกอริทึมที่จะเรียกใช้
^
ต้องการอาร์กิวเมนต์ที่สองเป็นIntegral
ไฟล์. ถ้าฉันจำไม่ผิดการใช้งานจะมีประสิทธิภาพมากขึ้นถ้าคุณรู้ว่าคุณกำลังทำงานกับเลขชี้กำลังอินทิกรัล นอกจากนี้หากคุณต้องการบางอย่างเช่น2 ^ (1.234)
แม้ว่าฐานของคุณจะเป็นอินทิกรัล 2 ผลลัพธ์ของคุณจะเป็นเศษส่วน คุณมีตัวเลือกมากขึ้นเพื่อให้คุณสามารถควบคุมประเภทที่จะเข้าและออกจากฟังก์ชันเลขชี้กำลังของคุณได้อย่างรัดกุมยิ่งขึ้น
ระบบประเภทของ Haskell ไม่มีเป้าหมายเดียวกันกับระบบประเภทอื่น ๆ เช่น C's, Python's หรือ Lisp's การพิมพ์เป็ด (เกือบ) ตรงข้ามกับความคิดของ Haskell
class Duck a where quack :: a -> Quack
กำหนดสิ่งที่เราคาดหวังจากเป็ดจากนั้นแต่ละอินสแตนซ์จะระบุสิ่งที่สามารถทำตัวเหมือนเป็ดได้
Duck
.