2012-04-23 24 views
11

Tôi thường cần tạo một chức năng cốt lõi được sử dụng ở nhiều nơi có thể cấu hình được - tức là nó có thể sử dụng thuật toán A hoặc thuật toán B tùy thuộc vào công tắc dòng lệnh; hoặc có nó in thông tin chi tiết hơn để stdout nếu cờ 'debug' được thiết lập bằng cách nào đó.Cách thích hợp để xử lý cờ toàn cầu trong Haskell

Tôi nên triển khai cờ toàn cầu như thế nào?

Tôi thấy 4 tùy chọn, tất cả đều không thực sự tốt.

1) Đọc đối số dòng lệnh từ hàm - xấu, vì cần IO đơn nguyên và các hàm tính toán lõi đều thuần, tôi không muốn nhận IO trong đó;

2) Truyền tham số từ chính/IO tới chức năng 'lá' cần thay đổi hành vi - hoàn toàn không sử dụng được, điều đó có nghĩa là thay đổi hàng chục hàm không liên quan trong các mô-đun khác nhau để chuyển tham số này, và Tôi muốn thử các tùy chọn cấu hình như vậy nhiều lần mà không phải thay đổi mã gói mỗi lần;

3) Sử dụng không an toànPerformIO để nhận biến toàn cục thực sự - cảm thấy xấu xí và quá mức cần thiết cho một vấn đề đơn giản như vậy;

4) Ngay ở giữa funcion có mã cho cả hai tùy chọn và nhận xét một trong số chúng. Hoặc có các hàm do_stuff_A và do_stuff_B, và thay đổi cái nào được gọi là tùy thuộc vào hàm toàn cục 'needDebugInfo = True' nói. Đó là những gì tôi đang làm bây giờ cho debuginfo, nhưng nó không thể được thay đổi w/o biên dịch lại, và nó không thực sự là cách tốt nhất có sẵn ...

Tôi không cần hoặc muốn toàn cầu có thể thay đổi nhà nước - Tôi muốn có một lá cờ toàn cầu đơn giản là không thay đổi trong thời gian chạy nhưng có thể bằng cách nào đó được thiết lập khi chương trình được khởi chạy. Có bất kỳ lựa chọn nào không?

Trả lời

3

HFlags thư viện mới của chúng tôi là chính xác cho việc này.

Nếu bạn muốn nhìn thấy một cách sử dụng ví dụ như ví dụ của bạn, nhìn vào điều này:

https://github.com/errge/hflags/blob/master/examples/ImportExample.hs

https://github.com/errge/hflags/blob/master/examples/X/B.hs

https://github.com/errge/hflags/blob/master/examples/X/Y_Y/A.hs

Không loại truyền thông số là cần thiết giữa các module và bạn có thể xác định cờ mới bằng cú pháp dễ dàng. Nó sử dụng unsafePerformIO nội bộ, nhưng chúng tôi nghĩ rằng nó làm điều đó một cách an toàn, và bạn sẽ không phải lo lắng cho chính mình với điều đó.

Có một bài viết trên blog về công cụ này tại địa chỉ: http://blog.risko.hu/2012/04/ann-hflags-0.html

14

Những ngày này, tôi thích sử dụng a Reader monad để cấu trúc trạng thái chỉ đọc của ứng dụng. Môi trường được initalized lúc khởi động, và sau đó có sẵn trong suốt cấp cao nhất của chương trình.

Một ví dụ is xmonad:

newtype X a = X (ReaderT XConf (StateT XState IO) a) 
    deriving (Functor, Monad, MonadIO, MonadReader XConf) 

Các bộ phận cấp cao nhất của chạy chương trình trong X thay vì IO; nơi XConf là cấu trúc dữ liệu được initalized bởi cờ dòng lệnh (và biến môi trường).

Trạng thái XConf sau đó có thể được chuyển thành dữ liệu thuần túy cho các chức năng cần đến. Với newtype, bạn cũng có thể sử dụng lại tất cả mã MonadReader để truy cập trạng thái.

Cách tiếp cận này giữ lại độ tinh khiết ngữ nghĩa của 2. nhưng cung cấp cho bạn ít mã để viết, vì đơn nguyên thực hiện hệ thống ống nước.

Tôi nghĩ đó là cách "đúng" Haskell để thực hiện trạng thái cấu hình chỉ đọc.

-

Các phương pháp sử dụng unsafePerformIO để khởi tạo trạng thái toàn cầu cũng làm việc, tất nhiên, nhưng có xu hướng cắn bạn cuối cùng (ví dụ khi bạn thực hiện chương trình của bạn đồng thời hay song song). Họ cũng có funny initialization semantics.

9

Bạn có thể sử dụng đơn lẻ Reader để có được hiệu ứng tương tự như truyền tham số ở khắp mọi nơi.Phong cách áp dụng có thể làm cho chi phí khá thấp so với mã chức năng bình thường, nhưng nó vẫn có thể khá khó xử. Đây là giải pháp phổ biến nhất cho vấn đề cấu hình, nhưng tôi không thấy nó rất thỏa đáng; thực sự, truyền tham số xung quanh một cách rõ ràng thường ít xấu xí hơn.

Một giải pháp thay thế là gói reflection, cho phép bạn chuyển các dữ liệu cấu hình chung như thế này xung quanh thông qua các bối cảnh kiểu chữ, có nghĩa là không có mã nào của bạn phải thay đổi để giảm giá trị bổ sung. Về cơ bản, bạn thêm một tham số kiểu mới cho mọi kiểu đầu vào/kết quả trong chương trình của bạn, để mọi thứ hoạt động trong ngữ cảnh của một cấu hình nhất định có kiểu tương ứng với cấu hình đó trong kiểu của nó. Loại đó dừng bạn vô tình trộn các giá trị bằng nhiều cấu hình và cung cấp cho bạn quyền truy cập vào cấu hình được liên kết khi chạy.

Điều này tránh chi phí viết mọi thứ theo phong cách ứng dụng, trong khi vẫn an toàn và cho phép bạn kết hợp nhiều cấu hình. Nó đơn giản hơn nhiều so với âm thanh; đây là an example.

(Full discloure:. Tôi đã làm việc trên bao bì phản ánh)

2

lựa chọn khác là GHC implicit parameters. Chúng cung cấp một phiên bản ít đau đớn hơn cho lựa chọn của bạn (2): chữ ký kiểu trung gian bị nhiễm, nhưng bạn không phải thay đổi bất kỳ mã trung gian nào.

Dưới đây là một ví dụ:

{-# LANGUAGE ImplicitParams #-} 
import System.Environment (getArgs)  

-- Put the flags in a record so you can add new flags later 
-- without affecting existing type signatures. 
data Flags = Flags { flag :: Bool } 

-- Leaf functions that read the flags need the implicit argument 
-- constraint '(?flags::Flags)'. This is reasonable. 
leafFunction :: (?flags::Flags) => String 
leafFunction = if flag ?flags then "do_stuff_A" else "do_stuff_B" 

-- Implicit argument constraints are propagated to callers, so 
-- intermediate functions also need the implicit argument 
-- constraint. This is annoying. 
intermediateFunction :: (?flags::Flags) => String 
intermediateFunction = "We are going to " ++ leafFunction 

-- Implicit arguments can be bound at the top level, say after 
-- parsing command line arguments or a configuration file. 
main :: IO() 
main = do 
    -- Read the flag value from the command line. 
    commandLineFlag <- (read . head) `fmap` getArgs 
    -- Bind the implicit argument. 
    let ?flags = Flags { flag = commandLineFlag } 
    -- Subsequent code has access to the bound implicit. 
    print intermediateFunction 

Nếu bạn chạy chương trình này với lập luận True nó in We are going to do_stuff_A; với đối số False, hãy in We are going to do_stuff_B.

Tôi nghĩ phương pháp này tương tự như the reflection package mentioned in another answer và tôi nghĩ rằng HFlags mentioned in the accepted answer có lẽ là lựa chọn tốt hơn, nhưng tôi đang thêm câu trả lời này cho đầy đủ.

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