2012-05-11 23 views
7

phép nói rằng tôi đã có đoạn mã sauHaskell: Loại an toàn với các giá trị Boolean logic khác nhau

type IsTall = Bool 
type IsAlive = Bool 

is_short_alive_person is_tall is_alive = (not is_tall) && is_alive 

Này, sau này, tôi đã có những điều sau

a :: IsAlive 
a = False 

b :: IsTall 
b = True 

Và gọi sau , nhận được hai đối số xung quanh một cách sai lầm:

is_short_alive_person a b 

này biên dịch thành công không may, và khi chạy người cao chết thay vào đó được tìm thấy thay vì những người sống ngắn.

Tôi muốn ví dụ trên không được biên dịch.

nỗ lực đầu tiên của tôi là:

newtype IsAlive = IsAlive Bool 
newtype IsTall = IsTall Bool 

Nhưng sau đó tôi không thể làm điều gì đó như thế nào.

switch_height :: IsTall -> IsTall 
switch_height h = not h 

Như not không được định nghĩa trên IsTall s, chỉ Bool s.

Tôi có thể trích xuất rõ ràng Bool mọi lúc, nhưng điều đó phần lớn đánh bại mục đích.

Về cơ bản, tôi muốn IsTall s tương tác với IsTall s khác, giống như họ đang Bool s, ngoại trừ họ sẽ không tương tác với Bool s và IsAlive s mà không có một diễn viên rõ ràng.

Cách tốt nhất để đạt được điều này là gì.


p.s. Tôi nghĩ rằng tôi đã đạt được điều này với những con số bằng cách thực hiện trong GHC:

{-# LANGUAGE GeneralizedNewtypeDeriving #-} 

newtype UserID = UserID Int deriving (Eq, Ord, Num) 
newtype GroupID = GroupID Int deriving (Eq, Ord, Num) 

(tức là của UserID và GROUPID không nên tương tác)

nhưng tôi dường như không thể làm điều này với Bool s (bắt nguồn Bool không hoạt động). Tôi thậm chí không chắc chắn ở trên là cách tiếp cận tốt nhất anyway.

+1

Các loại 'IsTall' và' IsAlive' mong muốn của bạn là một ý tưởng tồi tệ. Đó là một khái quát sai lầm của ý tưởng thường phong nha của việc sử dụng các loại rời rạc để đảm bảo an toàn loại. So sánh điều này với 'UserID' và' GroupID' của bạn; trong trường hợp đó, có ý nghĩa để có các kiểu riêng biệt bởi vì nó không có ý nghĩa khi chuyển một 'UserId' trong đó một' GroupID' là cần thiết, hoặc thêm vào một nhóm khác (mặc dù có lẽ không nên thực hiện 'Num'). Tuy nhiên, nó có ý nghĩa để kiểm tra xem một người là cao * và * sống, cao * hoặc * còn sống, * không * cao * và * còn sống, v.v. –

+0

sacundim: Ở mức độ lớn, tôi đã thay đổi ý định của mình đến thời điểm này lượt xem. Tôi đã sử dụng những thứ như 'newtype Height = Tall | Short' và sau đó thực hiện 'x == Tall', v.v. Một chút gõ, nhưng tôi nghĩ nó làm cho mã dễ đọc hơn và an toàn hơn. – Clinton

Trả lời

8

Bạn có thể nhận được cách hướng này một số người, sử dụng newtype s và nếu bạn nhập Prelude ẩn các hàm boolean bạn muốn sử dụng với các giá trị IsTallIsAlive của mình. Bạn xác định lại các hàm boolean như các phương thức trong lớp, sau đó bạn tạo các phiên bản cho tất cả 3 của các loại Bool, IsTallIsAlive. Nếu bạn sử dụng GeneralizedNewtypeDeriving, bạn thậm chí có thể nhận được IsTallIsAlive trường hợp mà không cần phải viết gói/unwrapping boilerplate bằng tay.

Dưới đây là một kịch bản ví dụ tôi thực sự cố gắng ra trong ghci:

{-# LANGUAGE GeneralizedNewtypeDeriving #-} 

import Prelude hiding ((&&), (||), not) 
import qualified Prelude 

class Boolish a where 
    (&&) :: a -> a -> a 
    (||) :: a -> a -> a 
    not :: a -> a 

instance Boolish Bool where 
    (&&) = (Prelude.&&) 
    (||) = (Prelude.||) 
    not = Prelude.not 

newtype IsTall = IsTall Bool 
    deriving (Eq, Ord, Show, Boolish) 

newtype IsAlive = IsAlive Bool 
    deriving (Eq, Ord, Show, Boolish) 

Bạn có thể bây giờ &&, ||, và not giá trị của bất kỳ trong ba loại, nhưng không phải với nhau. Và chúng là các kiểu riêng biệt, vì vậy các chữ ký chức năng của bạn bây giờ có thể hạn chế cái nào trong số 3 chúng muốn chấp nhận.

cao hơn chức năng tự quy định tại các module khác sẽ làm việc tốt với điều này, như trong:

*Main> map not [IsTall True, IsTall False] 
[IsTall False,IsTall True] 

Nhưng bạn sẽ không thể vượt qua một IsTall đến bất kỳ chức năng khác được xác định ở những nơi khác mà hy vọng một Bool, bởi vì mô-đun khác vẫn sẽ sử dụng phiên bản Prelude của các hàm boolean. Cấu trúc ngôn ngữ như if ... then ... else ... vẫn sẽ là một vấn đề quá (mặc dù một bình luận của hammar về câu trả lời của Norman Ramsey nói rằng bạn có thể sửa lỗi này với một phần mở rộng GHC khác). Tôi có thể thêm phương thức toBool vào lớp đó để giúp chuyển đổi thống nhất trở lại thường xuyên Bool để giúp giảm thiểu các vấn đề như vậy.

+0

http://hackage.haskell.org/package/cond rất hữu ích nếu bạn muốn thực hiện phương pháp này. –

8

lựa chọn của bạn là một trong hai để xác định kiểu dữ liệu đại số như

data Height = Tall | Short 
data Wiggliness = Alive | Dead 

hoặc để xác định các nhà khai thác mới, ví dụ &&&, |||, complement và quá tải chúng vào loại bạn đã chọn. Nhưng ngay cả với quá tải bạn sẽ không thể sử dụng chúng với if.

Tôi không chắc chắn rằng các hoạt động Boolean trên chiều cao có hợp lý hay không. Làm thế nào để bạn biện minh cho một kết luận rằng "cao và ngắn bằng" ngắn "nhưng" cao hoặc ngắn bằng cao "?

Tôi khuyên bạn nên tìm các tên khác nhau cho các kết nối của bạn, sau đó bạn có thể quá tải.

P.S. Haskell luôn nhận được các tính năng mới, vì vậy tốt nhất tôi có thể nói là nếu bạn có thể quá tải if Tôi không biết về nó.Để nói về Haskell rằng "không thể thực hiện" luôn nguy hiểm ...

+3

Bạn có thể quá tải 'if' bằng cách sử dụng phần mở rộng' RebindableSyntax' trong các phiên bản gần đây của GHC. [Ví dụ nhanh] (https://gist.github.com/2657492). – hammar

+0

@Norman: Tôi đã thử câu trả lời của Ben, câu trả lời đã hoạt động khá tốt, nhưng cuối cùng tôi đã quyết định làm sạch Enums như bạn đã gợi ý. Tôi nhận ra rằng tôi có thể làm những gì tôi đã làm phần lớn mà không có các hoạt động hợp lý. – Clinton

11

Nếu bạn thay đổi kiểu dữ liệu một chút, bạn có thể biến nó thành thể hiện của Functor và sau đó bạn có thể sử dụng fmap để làm hoạt động trên Boolean

import Control.Applicative 

newtype IsAliveBase a = IsAlive a 
newtype IsTallBase a = IsTall a 

type IsAlive = IsAliveBase Bool 
type IsTall = IsTallBase Bool 

instance Functor IsAliveBase where 
    fmap f (IsAlive b) = IsAlive (f b) 

instance Functor IsTallBase where 
    fmap f (IsTall b) = IsTall (f b) 

switch_height :: IsTall -> IsTall 
switch_height h = not <$> h -- or fmap not h 

- EDIT

cho các hoạt động như & & bạn có thể làm cho nó một thể hiện của applicative

instance Applicative IsAliveBase where 
    pure = IsAlive 
    (IsAlive f) <*> (IsAlive x) = IsAlive (f x) 

và sau đó bạn có thể làm (& &) sử dụng liftA2

dụ:

*Main> let h = IsAlive True 
*Main> liftA2 (&&) h h 
IsAlive True 

bạn có thể đọc thêm về điều này tại http://en.wikibooks.org/wiki/Haskell/Applicative_Functors

+0

Làm cách nào để thực hiện h1 && h2? – Clinton

+0

Cập nhật câu trả lời bằng ví dụ cho && – Phyx

Các vấn đề liên quan