Haskell printf hoạt động như thế nào?


104

An toàn kiểu của Haskell không ai sánh kịp, chỉ sau các ngôn ngữ được gõ phụ thuộc. Nhưng có một số phép thuật sâu sắc đang xảy ra với Text.Printf có vẻ khá khó hiểu.

> printf "%d\n" 3
3
> printf "%s %f %d" "foo" 3.3 3
foo 3.3 3

Điều kỳ diệu đằng sau điều này là gì? Làm cách nào để Text.Printf.printfhàm có thể nhận các đối số khác nhau như thế này?

Kỹ thuật chung được sử dụng để cho phép các đối số khác nhau trong Haskell là gì và nó hoạt động như thế nào?

(Lưu ý: một số loại an toàn dường như bị mất khi sử dụng kỹ thuật này.)

> :t printf "%d\n" "foo"
printf "%d\n" "foo" :: (PrintfType ([Char] -> t)) => t

15
Bạn chỉ có thể nhận được một loại printf an toàn bằng cách sử dụng các loại phụ thuộc.
augustss

9
Lennart khá đúng. Độ an toàn về kiểu của Haskell đứng thứ hai sau các ngôn ngữ có nhiều kiểu phụ thuộc hơn Haskell. Tất nhiên, bạn có thể làm cho một loại thứ giống như printf an toàn nếu bạn chọn một loại nhiều thông tin hơn là String cho định dạng.
pigworker

3
xem oleg để biết nhiều biến thể của printf: okmij.org/ftp/typed-formatting/FPrintScan.html#DSL-In
sclv

1
@augustss Bạn chỉ có thể nhận được printf loại an toàn bằng cách sử dụng các loại phụ thuộc HOẶC TEMPLATE HASKELL! ;-)
MathOrchid

3
@MatheatologyOrchid Mẫu Haskell không được tính. :)
August

Câu trả lời:


131

Bí quyết là sử dụng các lớp kiểu. Trong trường hợp của printf, khóa là PrintfTypeloại kiểu. Nó không tiết lộ bất kỳ phương pháp nào, nhưng phần quan trọng là ở các loại.

class PrintfType r
printf :: PrintfType r => String -> r

Vì vậy, printfcó một kiểu trả về quá tải. Trong trường hợp tầm thường, chúng tôi không có lập luận thêm, vì vậy chúng tôi cần để có thể nhanh chóng rđến IO (). Đối với điều này, chúng tôi có ví dụ

instance PrintfType (IO ())

Tiếp theo, để hỗ trợ một số lượng biến đối số, chúng ta cần sử dụng đệ quy ở cấp cá thể. Đặc biệt, chúng ta cần một thể hiện để nếu rlà a PrintfType, thì một kiểu hàm x -> rcũng là a PrintfType.

-- instance PrintfType r => PrintfType (x -> r)

Tất nhiên, chúng tôi chỉ muốn hỗ trợ các đối số thực sự có thể được định dạng. Đó là nơi mà lớp loại thứ hai PrintfArgxuất hiện. Vì vậy, trường hợp thực tế là

instance (PrintfArg x, PrintfType r) => PrintfType (x -> r)

Đây là một phiên bản đơn giản hóa lấy bất kỳ số lượng đối số nào trong Showlớp và chỉ in chúng:

{-# LANGUAGE FlexibleInstances #-}

foo :: FooType a => a
foo = bar (return ())

class FooType a where
    bar :: IO () -> a

instance FooType (IO ()) where
    bar = id

instance (Show x, FooType r) => FooType (x -> r) where
    bar s x = bar (s >> print x)

Ở đây, barthực hiện một hành động IO được tạo đệ quy cho đến khi không còn đối số nào nữa, lúc này chúng ta chỉ cần thực thi nó.

*Main> foo 3 :: IO ()
3
*Main> foo 3 "hello" :: IO ()
3
"hello"
*Main> foo 3 "hello" True :: IO ()
3
"hello"
True

QuickCheck cũng sử dụng kỹ thuật tương tự, trong đó Testablelớp có một thể hiện cho trường hợp cơ sở Boolvà một bản đệ quy cho các hàm nhận đối số trong Arbitrarylớp.

class Testable a
instance Testable Bool
instance (Arbitrary x, Testable r) => Testable (x -> r) 

Câu trả lời chính xác. Tôi chỉ muốn chỉ ra rằng haskell đang tìm ra loại Foo dựa trên các đối số được áp dụng. Để hiểu điều này, bạn có thể muốn chỉ định loại biểu tượng Foo như sau: λ> (foo :: (Hiển thị x, Hiển thị y) => x -> y -> IO ()) 3 "xin chào"
redfish64

1
Trong khi tôi hiểu cách phần đối số độ dài biến được triển khai, tôi vẫn không hiểu cách trình biên dịch từ chối printf "%d" True. Điều này rất bí ẩn đối với tôi, vì có vẻ như giá trị thời gian chạy (?) Được "%d"giải mã tại thời điểm biên dịch để yêu cầu một Int. Điều này hoàn toàn gây khó hiểu cho tôi. . . đặc biệt là kể từ khi mã nguồn không sử dụng những thứ như DataKindshay TemplateHaskell(tôi đã kiểm tra mã nguồn, nhưng không hiểu nó.)
Thomas Việc chỉnh sửa

2
@ThomasEding Lý do trình biên dịch từ chối printf "%d" Truelà vì không có Boolphiên bản nào của PrintfArg. Nếu bạn vượt qua một cuộc tranh cãi của các loại sai mà không có một thể hiện của PrintfArg, nó biên dịch và ném một ngoại lệ khi chạy. Ví dụ:printf "%d" "hi"
Travis Sunderland
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.