Luỹ thừa trong Haskell


91

Ai đó có thể cho tôi biết tại sao Haskell Prelude định nghĩa hai hàm riêng biệt cho lũy thừa (tức là ^**) không? Tôi nghĩ rằng hệ thống loại phải loại bỏ loại trùng lặp này.

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

Câu trả lời:


130

Thực tế, có ba nhà khai thác lũy thừa: (^), (^^)(**). ^là lũy thừa tích phân không âm, ^^là lũy thừa số nguyên và **là lũy thừa dấu phẩy động:

(^) :: (Num a, Integral b) => a -> b -> a
(^^) :: (Fractional a, Integral b) => a -> b -> a
(**) :: Floating a => a -> a -> a

Lý do là kiểu an toàn: kết quả của các phép toán số thường có cùng kiểu với (các) đối số đầu vào. Nhưng bạn không thể tăng Intmột lũy thừa dấu phẩy động và nhận được một kết quả kiểu Int. Và do đó, hệ thống kiểu ngăn bạn làm điều này: (1::Int) ** 0.5tạo ra lỗi kiểu. Cũng vậy (1::Int) ^^ (-1).

Một cách khác để đặt điều này: Numcác loại được đóng dưới ^(chúng không bắt buộc phải có một phép nghịch đảo nhân), Fractionalcác loại được đóng dưới ^^, Floatingcác loại được đóng dưới **. Vì không có Fractionalví dụ nào Intnên bạn không thể nâng nó lên mức phủ định.

Lý tưởng nhất, đối số thứ hai của ^sẽ bị hạn chế tĩnh là không âm (hiện tại, 1 ^ (-2)ném một ngoại lệ thời gian chạy). Nhưng không có loại cho số tự nhiên trong Prelude.


31

Hệ thống kiểu của Haskell không đủ mạnh để thể hiện ba toán tử lũy thừa làm một. Những gì bạn thực sự muốn là một cái gì đó như thế này:

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 **

Điều này không thực sự hoạt động ngay cả khi bạn bật tiện ích mở rộng lớp loại đa tham số, vì lựa chọn cá thể cần phải thông minh hơn Haskell hiện cho phép.


4
Tuyên bố về điều này không thể thực hiện được vẫn đúng? IIRC, haskell có một tùy chọn cho tham số thứ hai của một lớp kiểu đa tham số được xác định chặt chẽ bởi tham số đầu tiên. Có một vấn đề nào khác ngoài vấn đề này mà không thể giải quyết được không?
RussellStewart

2
@singular Nó vẫn đúng. Ví dụ, đối số đầu tiên không xác định đối số thứ hai, bạn muốn số mũ là cả hai IntInteger. Để có thể có ba khai báo cá thể đó, giải pháp đối tượng phải sử dụng tính năng quay lui và không có trình biên dịch Haskell nào thực hiện điều đó.
augustss

7
Liệu các "hệ thống kiểu không đủ mạnh" tranh luận vẫn giữ tính đến tháng 3 năm 2015?
Erik Kaplun

3
Bạn chắc chắn không thể viết nó theo cách tôi gợi ý, nhưng có thể có một số cách để mã hóa nó.
augustss

2
@ErikAllik lẽ làm cho tiêu chuẩn Haskell, như là không có Báo cáo Haskell mới đã đi ra kể từ năm 2010.
Martin Capodici

10

Nó không định nghĩa hai toán tử - nó định nghĩa ba! Từ báo cáo:

Có ba phép toán lũy thừa hai đối số: ( ^) nâng một số bất kỳ lên lũy thừa số nguyên không âm, ( ^^) nâng một số phân số lên lũy thừa số nguyên bất kỳ và ( **) nhận hai đối số dấu phẩy động. Giá trị của x^0hoặc x^^0là 1 cho bất kỳ x, bao gồm cả 0; 0**ykhông định nghĩa được.

Điều này có nghĩa là có ba thuật toán khác nhau, hai trong số đó cho kết quả chính xác ( ^^^), trong khi **cho kết quả gần đúng. Bằng cách chọn toán tử sẽ sử dụng, bạn chọn thuật toán sẽ gọi.


4

^yêu cầu đối số thứ hai của nó là một Integral. Nếu tôi không nhầm, việc triển khai có thể hiệu quả hơn nếu bạn biết bạn đang làm việc với số mũ tích phân. Ngoài ra, nếu bạn muốn một cái gì đó như 2 ^ (1.234), mặc dù cơ số của bạn là một tích phân, 2, kết quả của bạn rõ ràng sẽ là phân số. Bạn có nhiều tùy chọn hơn để có thể kiểm soát chặt chẽ hơn những loại nào đang vào và ra khỏi hàm lũy thừa của bạn.

Hệ thống kiểu của Haskell không có cùng mục tiêu với các hệ thống kiểu khác, chẳng hạn như C, Python hoặc Lisp. Kiểu gõ vịt (gần như) trái ngược với tư duy Haskell.


4
Tôi không hoàn toàn đồng ý rằng tư duy kiểu Haskell trái ngược với kiểu gõ vịt. Các lớp kiểu Haskell khá giống kiểu gõ vịt. class Duck a where quack :: a -> Quackxác định những gì chúng ta mong đợi ở một con vịt, và sau đó mỗi trường hợp chỉ định một cái gì đó có thể hoạt động như một con vịt.
tám

9
@augustss Tôi biết bạn đến từ đâu. Nhưng phương châm thân mật đằng sau việc gõ vịt là "nếu nó trông giống như một con vịt, hoạt động như một con vịt, và lang thang như một con vịt, thì đó là một con vịt." Trong Haskell, nó không phải là một con vịt trừ khi nó được tuyên bố là một ví dụ của Duck.
Dan Burton

1
Đó là sự thật, nhưng đó là những gì tôi mong đợi từ Haskell. Bạn có thể làm bất cứ thứ gì bạn muốn từ vịt, nhưng bạn phải rõ ràng về điều đó. Chúng tôi không muốn nhầm thứ mà chúng tôi không yêu cầu là một con vịt.
augustss

Có một sự khác biệt cụ thể hơn giữa cách làm việc của Haskell và cách gõ vịt. Có, bạn có thể cho bất kỳ loại nào là lớp Vịt nhưng nó không phải là Vịt. Nó có khả năng bẻ khóa, chắc chắn nhưng nó vẫn, một cách cụ thể, bất kể là loại nào. Bạn vẫn không thể có danh sách Vịt. Chức năng chấp nhận danh sách Vịt và trộn và kết hợp nhiều loại Vịt khác nhau sẽ không hoạt động. Về mặt này, Haskell không cho phép bạn chỉ nói "Nếu nó kêu như vịt, thì đó là vịt." Trong Haskell, tất cả Vịt của bạn phải là Quackers cùng loại. Điều này thực sự khác với việc gõ vịt.
mmachenry

Bạn có thể có một danh sách hỗn hợp vịt, nhưng bạn cần phần mở rộng của Định lượng Hiện sinh.
Bolpat
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.