2015-05-23 29 views
6

tôi nhận được lỗi như thế này:Tại sao tôi không thể xếp chồng lên nhau hai đầu đọc của nhau?

Hãy nói rằng tôi có một monadStack ReaderT A (ReaderT B m), bất cứ khi nào tôi sử dụng ask hoặc asks, tôi nhận được một lỗi như thế này:

Types.hs:21:10: 
    Couldn't match type ‘A’ with ‘B’ 
    arising from a functional dependency between: 
     constraint ‘MonadReader B m’ 
     arising from the instance declaration 
     instance ‘MonadReader A m2’ at Types.hs:21:10-63 
    In the instance declaration for ‘MonadReader A m’ 

Tại sao Haskell không thể tìm ra dụ để sử dụng? Ngoài ra, làm cách nào tôi có thể giải quyết vấn đề này? Giả sử đặt AB vào cùng một kiểu dữ liệu không phải là một tùy chọn, vì tôi cần một phiên bản MonadReader A m.

+0

Oh, 'instance (MonadReader r m) => MonadReader r (ReaderT s m)' thậm chí không tồn tại ?! Tại sao vậy? –

Trả lời

13

Lớp MonadReader được xác định bằng cách sử dụng phần mở rộng FunctionalDependencies, cho phép khai báo như

class Monad m => MonadReader r m | m -> r where 
    ... 

Điều này có nghĩa rằng đối với bất kỳ đơn nguyên m, các r được xác định duy nhất bởi nó. Do đó, bạn không thể có một đơn lẻ m xác định hai loại khác nhau r. Nếu không có điều này như là một hạn chế, trình biên dịch sẽ không thể đánh dấu việc sử dụng kiểm tra của lớp đó.

Các giải pháp này là để viết các chức năng của bạn như

getA'sInt :: A -> Int 
getA'sInt = undefined 

getB'sString :: B -> String 
getB'sString = undefined 

foo :: (MonadReader A m) => m Int 
foo = do 
    a <- asks getA'sInt 
    return $ a + 1 

bar :: (MonadReader B m) => m String 
bar = do 
    b <- asks getB'sString 
    return $ map toUpper b 

Sau đó, chỉ cần sử dụng một tuple (A, B) trong việc thực hiện thực tế của bạn:

baz :: Reader (A, B) (Int, String) 
baz = do 
    a <- withReader fst foo 
    b <- withReader snd bar 
    return (a, b) 

Ngoài ra còn có một withReaderT đối với trường hợp phức tạp hơn.

Như một ví dụ cho lý do tại sao nó không được phép để ngăn xếp ReaderT s, xem xét các trường hợp

type App = ReaderT Int (Reader Int) 

Khi bạn gọi ask, mà Int được bạn đề cập đến? Nó có vẻ hiển nhiên rằng đối với trường hợp như

type App = ReaderT A (Reader B) 

trình biên dịch sẽ có thể tìm ra để sử dụng, nhưng vấn đề là các ask chức năng ở đây sẽ có các loại

ask :: App ??? 

đâu ??? thể là A hoặc B. Bạn có thể làm được việc này một cách khác, bằng cách không sử dụng MonadReader trực tiếp và xác định cụ thể askAaskB chức năng:

type App = ReaderT A (Reader B) 

askA :: App A 
askA = ask 

askB :: App B 
askB = lift ask 

baz :: App (Int, String) 
baz = do 
    a <- askA 
    b <- askB 
    return (getA'sInt a, getB'sString b) 

Nhưng bạn sẽ chỉ có thể có MonadReader A App, bạn có thể không còn có MonadReader B App. Cách tiếp cận này có thể được gọi là "nâng cao rõ ràng", và nó làm cho các chức năng đó đặc trưng cho loại App và do đó ít có thể tổng hợp hơn.

+0

Cảm ơn câu trả lời, cho nhận xét cuối cùng, làm thế nào để bạn chọn một trong những "MonadReader A App' hoặc' MonadReader B App' bạn nhận được, bởi vì điều đó trong thực tế sẽ giải quyết vấn đề của tôi. –

+1

@SydKerckhove Cho dù ai đứng đầu. – bheklilr

+2

@SydKerckhove Vì vậy, nếu bạn có 'ReaderT B (Reader A)' thì bạn sẽ có thể hiện 'MonadReader B'. – bheklilr

3

Nó có thể được thực hiện bằng cách sử dụng một số phần mở rộng, mặc dù nó có thể không phải là một ý tưởng tốt để có trường hợp trùng lặp.

{-# LANGUAGE MultiParamTypeClasses #-} 
{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE UndecidableInstances #-} 
{-# LANGUAGE OverlappingInstances #-} 

import Control.Monad.Reader 

import Data.Functor.Identity 

data A = A deriving Show 
data B = B deriving Show 

type SomeMonad a = ReaderT A (ReaderT B Identity) a 

instance MonadReader a m => MonadReader a (ReaderT b m) where 
    ask = lift ask 
    local f mx = do 
    b <- ask 
    lift $ local f $ runReaderT mx b 

main :: IO() 
main = do 
    let res = runIdentity $ flip runReaderT B $ flip runReaderT A $ do 
       a <- ask :: SomeMonad A 
       b <- ask :: SomeMonad B 
       return (a, b) 
    print res 

Không cần phải nói, bất cứ khi nào có thể, tốt hơn bạn nên sử dụng một cái gì đó như ReaderT (A, B) IO.

+0

Đây là những gì tôi vừa tìm ra, và nó hoạt động! Tôi chỉ hy vọng cho một giải pháp rõ ràng hơn (an toàn hơn). –

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