2015-12-08 14 views
7

Tôi muốn tuần tự soạn hai hành động đơn lẻ trong Haskell, loại bỏ bất kỳ giá trị nào được tạo ra bởi lần thứ hai và chuyển đối số cho cả hai hành động. Hiện nay tôi đang sử dụng một do-block như thế này:Thêm hành động mà không thay đổi kết quả thành ký hiệu tái cấu trúc

ask = do 
    result <- getLine 
    putStrLn result 
    return result 

Tôi đã hy vọng để viết này điểm nhiều hơn một chút miễn phí và gọn gàng, vì vậy tôi cố gắng này:

ask' = getLine <* putStrLn 

Tuy nhiên, điều này doesn' t thậm chí kiểm tra kiểu và vấn đề là <* không chuyển kết quả của hành động đầu tiên sang hành động thứ hai. Tôi muốn chuỗi các hành động như >>=, nhưng không thay đổi kết quả. Loại phải là (a -> m b) -> (a -> m c) -> (a -> m b), nhưng Hoogle cho kết quả không phù hợp. Điều gì sẽ là một nhà điều hành để đạt được thành phần chức năng này?

+1

Vì vậy, bạn muốn 'getline >> = \ x -> putStrLn x >> trở lại x' tại điểm theo phong cách tự do. Bạn đã hỏi lambdabot chưa? Nó nói 'liftM2 (>>) putStrLn return = << getLine'. –

+0

@ ThomasM.DuBuisson Có vẻ như Lambdabot tìm thấy hầu hết các chức năng chính xác mà OP muốn! 'flip (liftM2 (>>)) :: Monad m => (a -> mb) -> (a -> mc) -> (a -> mb)' - loại thực tế được tổng quát hơn một chút vì vậy nó có phần khó thấy. – user2407038

+0

@ ThomasM.DuBuisson Cảm ơn bạn, tôi đã hỏi công cụ dòng lệnh 'pointfree', và nó không thể xử lý' do', vì vậy tôi đã từ bỏ. Tôi đã kết thúc bằng cách sử dụng 'ask = getLine >> = liftM2 (>>) putStrLn return', có vẻ ổn, cảm ơn một lần nữa! Bạn có thể đặt câu trả lời này vào câu trả lời nếu bạn muốn, sau đó tôi có thể đánh dấu nó là đã được giải quyết. – amoebe

Trả lời

8

Là một xu hướng, nếu bạn sử dụng một giá trị trong hai nơi khác nhau thì có lẽ nó một ý tưởng tốt để cung cấp cho nó một cái tên trong một do khối rõ ràng, chứ không phải bức xúc về phong cách vô nghĩa.

Khái niệm trừu tượng về tách luồng thông tin thành các hành động khác nhau được ghi lại bởi cartesian monoidal categories, được biết đến với Haskellers là arrows. Trong trường hợp của bạn, bạn đang làm việc cơ bản trong các loại IO Kleisli:

import Prelude hiding (id) 
import Control.Arrow 

ask' :: Kleisli IO() String 
ask' = Kleisli (\()->getLine) >>> (putStrLn &&& id) >>> arr snd 

Tôi không nghĩ đó là một ý tưởng tốt để viết mã như vậy.

+0

Bạn nói đúng, nó có lẽ không phải là một ý tưởng hay. Các mũi tên trông rất phức tạp. Tôi không chắc chắn nếu các giải pháp với 'liftM2' trong đủ rõ ràng hoặc khó hiểu? – amoebe

+0

Vấn đề với 'liftM2 (>>)' là nó trộn hai monads khác nhau ('(a ->)' và 'IO') theo một cách khá phức tạp. Nếu bạn làm một cái gì đó như thế, tốt hơn hãy làm điều đó đúng với 'ReaderT', nhưng điều đó chỉ đáng giá nếu bạn đọc cùng một giá trị từ một nhóm toàn bộ địa điểm. – leftaroundabout

2

Tôi muốn tuần tự soạn hai hành động đơn lẻ trong Haskell, loại bỏ bất kỳ giá trị nào được tạo ra bởi lần thứ hai và chuyển đối số cho cả hai hành động.

này nghe có vẻ với tôi như một Reader -the chức năng loại r -> m a là đẳng cấu với ReaderT r m a, và các đơn nguyên hoạt động bằng cách ngầm cắm trong cùng một giá trị r vào tất cả các "lỗ". Vì vậy, ví dụ:

import Control.Applicative 
import Control.Monad.Reader 

example :: IO String 
example = getLine >>= discarding putStrLn 

discarding :: Monad m => (a -> m b) -> a -> m a 
discarding action = runReaderT (ReaderT action *> ask) 

Nhà điều hành bạn muốn là sau đó một cái gì đó như:

action `thingy` extra = action >>= discarding extra 

Nhưng tất nhiên discarding có một thực hiện đơn giản hơn:

discarding :: Applicative f => (a -> f b) -> a -> f a 
discarding action a = action a *> return a 

... nên cuối cùng Tôi nghĩ đây thực sự là chơi golf. Nhưng trong một chương trình phức tạp hơn, đây là một mô hình phổ biến ở quy mô lớn hơn, nó có thể đáng để quay.Về cơ bản, nếu bạn có:

a0 :: r -> m a0 
a1 :: r -> m a1 
    . 
    . 
    . 
an :: r -> m an 

Sau đó, nó sau đó:

ReaderT a0 :: ReaderT r m a0 
ReaderT a1 :: ReaderT r m a1 
    . 
    . 
    . 
ReaderT an :: ReaderT r m an 

Và sau đó:

2

Để hoàn chỉnh, trong trường hợp cụ thể này (IO) đơn nguyên, bạn có thể cũng lạm dụng bracket cho mục đích này:

bracket getLine putStrLn return 

Nhưng tôi mạnh mẽ không khuyến khích nó, vì điều này sẽ ít dễ đọc hơn so với khối do chú thích ban đầu, nó chỉ là xấu xí.

Như đã đề cập, trong trường hợp cụ thể này, việc đặt tên kết quả có vẻ là cách tốt nhất.

Xem thêm Should do-notation be avoided in Haskell?

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