Exponentiation ใน Haskell


92

ใครช่วยบอกหน่อยได้ไหมว่าเหตุใด Haskell Prelude จึงกำหนดฟังก์ชันแยกกันสองฟังก์ชันสำหรับการยกกำลัง (เช่น^และ**) ฉันคิดว่าระบบประเภทควรจะกำจัดการทำซ้ำประเภทนี้

Prelude> 2^2
4
Prelude> 4**0.5
2.0

คำตอบ:


132

มีจริงยกกำลังสามผู้ประกอบการ: (^), และ(^^) เป็นเลขชี้กำลังอินทิกรัลที่ไม่เป็นลบคือการยกกำลังจำนวนเต็มและเป็นการยกกำลังจุดลอยตัว:(**)^^^**

(^) :: (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.


32

ระบบประเภทของ 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 อนุญาตในปัจจุบัน


4
คำแถลงเกี่ยวกับเรื่องนี้ไม่สามารถนำไปใช้ได้จริงหรือไม่? IIRC, haskell มีตัวเลือกสำหรับพารามิเตอร์ที่สองของคลาสชนิดหลายพารามิเตอร์ที่กำหนดโดยพารามิเตอร์แรกอย่างเคร่งครัด มีปัญหาอื่นนอกเหนือจากนี้ที่ไม่สามารถแก้ไขได้หรือไม่?
RussellStewart

2
@singular มันยังคงเป็นเรื่องจริง อาร์กิวเมนต์แรกไม่ได้ตรวจสอบสองตัวอย่างเช่นคุณต้องการสัญลักษณ์ที่จะเป็นทั้งสองและInt Integerเพื่อให้สามารถประกาศอินสแตนซ์ทั้งสามนี้ได้ความละเอียดของอินสแตนซ์ต้องใช้การย้อนรอยและไม่มีคอมไพเลอร์ Haskell ดำเนินการดังกล่าว
ส.ค.

7
ไม่"ระบบการพิมพ์ไม่เพียงพอที่มีประสิทธิภาพ"อาร์กิวเมนต์ยังคงถือเป็นของเดือนมีนาคมปี 2015?
Erik Kaplun

3
คุณไม่สามารถเขียนตามที่ฉันแนะนำได้อย่างแน่นอน แต่อาจมีวิธีเข้ารหัสได้บ้าง
สิงหาคม

2
@ErikAllik อาจใช้กับ Haskell มาตรฐานเนื่องจากไม่มีรายงาน Haskell ฉบับใหม่ออกมาตั้งแต่ปี 2010
Martin Capodici

10

มันไม่ได้กำหนดตัวดำเนินการสองตัว - มันกำหนดสามตัว! จากรายงาน:

มีการดำเนินการยกกำลังสองอาร์กิวเมนต์สามรายการ: ( ^) ยกจำนวนใด ๆ ให้เป็นเลขกำลังจำนวนเต็มที่ไม่เป็นลบ, ( ^^) ยกจำนวนเศษส่วนเป็นกำลังจำนวนเต็มใด ๆ และ ( **) รับอาร์กิวเมนต์ทศนิยมสองตัว ค่าx^0หรือx^^0เท่ากับ 1 สำหรับค่าใด ๆxรวมถึงศูนย์ 0**yไม่ได้กำหนด

ซึ่งหมายความว่ามีอัลกอริทึมที่แตกต่างกันสามแบบซึ่งสองวิธีให้ผลลัพธ์ที่แน่นอน ( ^และ^^) ในขณะที่**ให้ผลลัพธ์โดยประมาณ การเลือกโอเปอเรเตอร์ที่จะใช้คุณจะเลือกอัลกอริทึมที่จะเรียกใช้


4

^ต้องการอาร์กิวเมนต์ที่สองเป็นIntegralไฟล์. ถ้าฉันจำไม่ผิดการใช้งานจะมีประสิทธิภาพมากขึ้นถ้าคุณรู้ว่าคุณกำลังทำงานกับเลขชี้กำลังอินทิกรัล นอกจากนี้หากคุณต้องการบางอย่างเช่น2 ^ (1.234)แม้ว่าฐานของคุณจะเป็นอินทิกรัล 2 ผลลัพธ์ของคุณจะเป็นเศษส่วน คุณมีตัวเลือกมากขึ้นเพื่อให้คุณสามารถควบคุมประเภทที่จะเข้าและออกจากฟังก์ชันเลขชี้กำลังของคุณได้อย่างรัดกุมยิ่งขึ้น

ระบบประเภทของ Haskell ไม่มีเป้าหมายเดียวกันกับระบบประเภทอื่น ๆ เช่น C's, Python's หรือ Lisp's การพิมพ์เป็ด (เกือบ) ตรงข้ามกับความคิดของ Haskell


4
ฉันไม่เห็นด้วยโดยสิ้นเชิงว่าความคิดของประเภท Haskell นั้นตรงกันข้ามกับการพิมพ์เป็ด ชั้นเรียนประเภท Haskell ค่อนข้างเหมือนกับการพิมพ์เป็ด class Duck a where quack :: a -> Quackกำหนดสิ่งที่เราคาดหวังจากเป็ดจากนั้นแต่ละอินสแตนซ์จะระบุสิ่งที่สามารถทำตัวเหมือนเป็ดได้
สิงหาคม

9
@augustss ฉันเห็นว่าคุณมาจากไหน แต่คำขวัญที่ไม่เป็นทางการที่อยู่เบื้องหลังการพิมพ์เป็ดคือ "ถ้ามันดูเหมือนเป็ดทำตัวเหมือนเป็ดและต้มตุ๋นเหมือนเป็ดก็คือเป็ด" ใน Haskell ไม่ใช่เป็ดเว้นแต่จะมีการประกาศตัวอย่างของDuck.
Dan Burton

1
นั่นเป็นความจริง แต่นั่นคือสิ่งที่ฉันคาดหวังจาก Haskell คุณสามารถทำอะไรก็ได้ที่คุณต้องการเป็ด แต่คุณต้องชัดเจนเกี่ยวกับเรื่องนี้ เราไม่อยากผิดพลาดอะไรที่เราไม่ได้ขอให้เป็นเป็ด
augustss

มีความแตกต่างที่เฉพาะเจาะจงมากขึ้นระหว่างวิธีการทำสิ่งต่างๆของ Haskell และการพิมพ์เป็ด ใช่คุณสามารถให้คลาสเป็ดประเภทใดก็ได้ แต่ไม่ใช่เป็ด มันสามารถต้มตุ๋นได้แน่นอน แต่ก็ยังคงเป็นรูปธรรมไม่ว่าจะเป็นประเภทใดก็ตาม คุณยังไม่มีรายชื่อเป็ด ฟังก์ชันที่รับรายชื่อเป็ดและการผสมและจับคู่คลาสประเภทต่างๆ Duck จะไม่ทำงาน ในแง่นี้ Haskell ไม่อนุญาตให้คุณพูดว่า "ถ้ามันต้มตุ๋นเหมือนเป็ดก็คือเป็ด" ใน Haskell เป็ดของคุณทุกคนต้องเป็นนักต้มตุ๋นประเภทเดียวกัน ซึ่งแตกต่างจากการพิมพ์เป็ดอย่างแน่นอน
mmachenry

คุณสามารถมีรายชื่อเป็ดผสมกันได้ แต่คุณต้องมีส่วนขยายของ Existential Quantification
Bolpat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.