Tại sao FunctionalDependency cần thiết để xác định MonadReader?


8

Tôi chỉ cần hiểu định nghĩa của lớp MonadReader

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

Sau khi đọc tài liệu về Phụ thuộc chức năng trong Haskell, bây giờ tôi có thể hiểu rằng | m -> rchỉ định biến loại đó rđược quyết định duy nhất bởi m. Tôi nghĩ rằng yêu cầu này là hợp lý dựa trên một số trường hợp điển hình của MonadReader mà tôi đã thấy cho đến nay (ví dụ Reader), nhưng dường như chúng ta vẫn có thể định nghĩa các trường hợp như Readerngay cả khi không có điều khoản phụ thuộc chức năng này.

Câu hỏi của tôi là tại sao chúng ta cần sự phụ thuộc chức năng trong định nghĩa của MonadReader? Đây có phải là chức năng cần thiết để xác định MonadReader theo nghĩa là MonadReader không thể được xác định chính xác mà không có nó hay chỉ đơn thuần là một hạn chế để hạn chế cách sử dụng MonadReader để các phiên bản của MonadReader sẽ hoạt động theo một cách mong đợi nhất định?


Một bài tập thú vị khi được trình bày với những câu hỏi này là thử và viết chúng sử dụng một định nghĩa mà không cần dep.
Thomas M. DuBuisson

1
Nó không cần thiết để xác định MonadReader; nó cần thiết để sử dụng thuận tiện MonadReader.
Daniel Wagner

Câu trả lời:


4

Nó là cần thiết để làm cho suy luận kiểu làm việc theo cách thuận tiện hơn cho người dùng.

Ví dụ: không có quỹ này sẽ không biên dịch:

action :: ReaderT Int IO ()
action = do
  x <- ask
  liftIO $ print x

Để biên dịch ở trên, chúng ta sẽ cần phải viết

action :: ReadertT Int IO ()
action = do
  x <- ask :: ReadertT Int IO Int
  liftIO $ print x

Điều này là do, nếu không có quỹ, trình biên dịch không thể suy ra đó xlà một Int. Sau tất cả, một đơn nguyên ReadertT Int IOcó thể có nhiều trường hợp

instance MonadReader Int (ReaderT Int IO) where
   ask = ReaderT (\i -> return i)
instance MonadReader Bool (ReaderT Int IO) where
   ask = ReaderT (\i -> return (i != 0))
instance MonadReader String (ReaderT Int IO) where
   ask = ReaderT (\i -> return (show i))
-- etc.

Vì vậy, lập trình viên phải cung cấp một số chú thích mà lực lượng x :: Int, hoặc mã là mơ hồ.


2

Đây không thực sự là một câu trả lời, nhưng nó quá dài cho một nhận xét. Bạn đúng là có thể định nghĩa MonadReaderlớp mà không cần quỹ. Cụ thể, chữ ký loại của mỗi phương thức xác định mọi tham số lớp. Nó hoàn toàn có thể xác định một hệ thống phân cấp tốt hơn.

class MonadReaderA r m where
  askA :: m r
  askA = readerA id

  readerA :: (r -> a) -> m a
  readerA f = f <$> askA

-- This effect is somewhat different in
-- character and requires special lifting.
class MonadReaderA r m => MonadReaderB r m where
  localB :: (r -> r) -> m a -> m a

class MonadReaderB r m
  => MonadReader r m | m -> r

ask :: MonadReader r m => m r
ask = askA

reader
  :: MonadReader r m
  => (r -> a) -> m a
reader = readerA

local
  :: MonadReader r m
  => (r -> r) -> m a -> m a
local = localB

Vấn đề chính với cách tiếp cận này là người dùng phải viết một loạt các trường hợp.


1

Tôi nghĩ nguồn gốc của sự nhầm lẫn là trong định nghĩa của

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

Nó được ngầm hiểu là mcó chứa rchính nó (đối với các trường hợp phổ biến). Hãy để tôi sử dụng một definiton nhẹ hơn Readernhư

newtype Reader r a = Reader {runReader :: r -> a}

Khi rtham số được chọn, bạn có thể dễ dàng xác định một thể hiện đơn nguyên cho Reader r. Điều đó có nghĩa là trong định nghĩa lớp loại mnên được thay thế cho Reader r. Vì vậy, hãy xem làm thế nào expresion kết thúc là:

instance MonadReader r (Reader r) where -- hey!! r is duplicated now
  {- ... -}                             -- The functional dependecy becomes Reader r -> r which makes sense

Nhưng tại sao chúng ta cần điều này? Nhìn vào định nghĩa của asktrong MonadReaderlớp.

class Monad m => MonadReader r m | m -> r where
  ask :: m r -- r and m are polymorphic here
  {- ... -}

Không có fun-dep, không có gì có thể ngăn tôi xác định asktheo cách trả về một loại khác như trạng thái. Thậm chí, tôi có thể định nghĩa nhiều phiên bản của trình đọc đơn âm cho loại của mình. Ví dụ, đây sẽ là định nghĩa hợp lệ mà không cần func-dep

instance MonadReader Bool (Reader r) where
--                   ^^^^         ^
--                   |            |- This is state type in the user defined newtype 
--                   |- this is the state type in the type class definition
  ask :: Reader r Bool
  ask = Reader (\_ -> True) -- the function that returns True constantly
  {- ... -}                             
instance MonadReader String (Reader r) where
--                   ^^^^^^         ^
--                   |              |- This is read-state type in the user defined newtype 
--                   |- this is the read-state type in the type class definition
  ask :: Reader r String
  ask = Reader (\_ -> "ThisIsBroken") -- the function that returns "ThisIsBroken" constantly
  {- ... -}                             

Vì vậy, nếu tôi có một giá trị val :: ReaderT Int IO Double, kết quả của nó là gì ask. Chúng tôi cần xác định một chữ ký loại như dưới đây

val :: Reader Int Double
val = do
  r <- ask :: Reader Int String
  liftIO $ putStrLn r   -- Just imagine you can use liftIO
  return 1.0

> val `runReader` 1
"ThisIsBroken"
1.0

val :: Reader Int Double
val = do
  r <- ask :: Reader Int Bool
  liftIO $ print r   -- Just imagine you can use liftIO
  return 1.0

> val `runReader` 1
True
1.0

Bên cạnh việc vô cảm, thật không thuận tiện khi chỉ định loại này nhiều lần.

Như một kết luận sử dụng định nghĩa thực tế của ReaderT. Khi bạn có một cái gì đó giống như val :: ReaderT String IO Intsự phụ thuộc chức năng nói một kiểu như vậy có thể chỉ có một trường hợp duy nhất của MonadReadertypeclass được định nghĩa là một trong đó sử dụng Stringnhưr

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.