2014-10-20 29 views
6

Giả sử bạn có định nghĩa quy nạp tốt đẹp và bạn muốn xác định nó dưới dạng kiểu dữ liệu trong Haskell. Tuy nhiên, định nghĩa quy nạp của bạn là (như nhiều định nghĩa quy nạp) của một dạng như vậy mà các quy tắc tạo ra yêu cầu 'tiền đề' của chúng để có một cấu trúc nhất định. Ví dụ, giả sử chúng ta có định nghĩa sau đây:Loại bỏ các loại dữ liệu

  • nếu x là một số nguyên thậm chí, sau đó T x là một vũ khí,
  • nếu x là một số nguyên lẻ, sau đó S x là một vũ khí.

Nếu tôi muốn xác định này (dưới dạng đĩa đơn) kiểu dữ liệu trong Haskell, tôi sẽ viết một cái gì đó giống như

data Weapon = T Int | S Int 

Rõ ràng, điều này sẽ không làm việc như bây giờ bạn có thể tạo ra T 5S 4, cho ví dụ. Có một cách tự nhiên để vượt qua trên các hạn chế về các đối số constructor, để tôi có thể viết một cái gì đó tương tự như mã trên mà sẽ cung cấp cho các định nghĩa chính xác?

+2

Nhà xây dựng thông minh, chủ yếu.Cấm sử dụng 'T' và' S' trực tiếp và tạo các hàm 'newT' và' newS' để xác minh các số đã qua. –

Trả lời

9

bắn tốt nhất của bạn không phải là để xuất khẩu TS một cách rõ ràng, nhưng cho phép một nhà xây dựng tùy chỉnh:

module YourModule (Weapon, smartConstruct) where 

data Weapon = T Int | S Int 

smartConstruct :: Int -> Weapon 
smartConstruct x 
    | even x  = T x 
    | otherwise = S x 

Bây giờ khi nhập YourModule, người dùng sẽ không thể tạo TS một cách rõ ràng, nhưng chỉ với chức năng smartConstruct của bạn.

10

Đây là một chút un-Haskelly, nhưng là thành ngữ hơn trong ví dụ. Agda: thay đổi cách diễn giải của biểu diễn của bạn để nó bị buộc phải chính xác bằng cách xây dựng.

Trong trường hợp này, hãy lưu ý rằng nếu n :: Int, thì even (2 * n)odd (2 * n + 1). Nếu chúng ta loại bỏ trường hợp quá lớn Int s, chúng ta có thể nói có một sự đánh dấu giữa các số IntInt giây; và một số khác giữa các số lẻ Int s và Int s.

Vì vậy, sử dụng này, bạn có thể chọn đại diện này:

data Weapon = T Int | S Int 

và thay đổi cách giải thích nó như vậy mà giá trị T n thực sự đại diện cho T (2 * n) và giá trị S n đại diện S (2 * n + 1). Vì vậy, bất kể bạn chọn n :: Int, T n sẽ hợp lệ vì bạn sẽ coi nó là giá trị "T -of-2 * n".

+0

Đẹp. Bạn có biết tôi có thể tìm thêm ví dụ về loại lừa này ở đâu không? – danidiaz

+2

Và bạn có thể loại bỏ các handwave với 'Integer'. – dfeuer

+2

@danidiaz: có một số trường hợp của mẫu này trong thư viện chuẩn Agda, ví dụ: hãy xem http://agda.github.io/agda-stdlib/html/Data.Integer.html#915 – Cactus

5

Nếu bạn sẵn sàng giới hạn bản thân bằng cách sử dụng Nats, và được sử dụng phép thuật loại tiên tiến hợp lý, bạn có thể sử dụng GHC.TypeLits.

{-# LANGUAGE DataKinds, GADTs, TypeOperators, KindSignatures #-} 

import GHC.TypeLits 

data Weapon (n :: Nat) where 
    Banana  :: n ~ (2 * m) => Weapon n 
    PointyStick :: n ~ (2 * m + 1) => Weapon n 

banana :: Weapon 2 
banana = Banana 

pointyStick :: Weapon 3 
pointyStick = PointyStick 

hãy thử cho chính mình, nó sẽ không biên dịch với số sai (lẻ/chẵn).

Chỉnh sửa: Thực tế hơn có lẽ là cách tiếp cận của cây xương rồng.

+0

Điều này dường như là loại kép đối với yêu cầu. Thú vị, mặc dù. – dfeuer

+0

Tôi cũng đánh giá cao tài liệu tham khảo Monty Python. – dfeuer

+0

tại sao bạn nghĩ nó là kép? bạn cần phải đi đến một số độ dài để xây dựng những vũ khí tự nhiên này trong thời gian chạy, nhưng nó không khó (và một loại câu hỏi khác). nó đảm bảo rằng chỉ ngay cả chuối và gậy nhọn được xây dựng mặc dù. – ibotty

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