Какова логика, позволяющая объединять только те же типы монад с оператором `>>`?
Хотя это нормально связывать IO [[Char]]
а также IO ()
но его нельзя связывать Maybe
с IO
, Может кто-нибудь привести пример, как это расслабление приведет к плохому дизайну? Почему свобода в полиморфном типе Монады допускается, но не самой Монадой?
2 ответа
Есть много хороших теоретических причин, в том числе "это не то, что Monad
есть. "Но давайте на минутку отойдем от этого и просто посмотрим на детали реализации.
Прежде всего - Monad
не волшебство Это просто класс стандартного типа. Экземпляры Monad
создается только тогда, когда кто-то пишет.
Написание этого экземпляра - это то, что определяет, как (>>)
работает. Обычно это делается неявно через определение по умолчанию с точки зрения (>>=)
, но это только доказательство того, что (>>=)
является более общим оператором, и его написание требует принятия тех же решений, что и написание (>>)
взял бы.
Если у вас был другой оператор, который работал с более общими типами, вы должны ответить на два вопроса. Во-первых, какими будут типы? Во-вторых, как бы вы пошли о предоставлении реализации? Из вашего вопроса действительно не ясно, какими будут желаемые типы. Я думаю, одно из следующего:
class Poly1 m n where
(>>) :: m a -> n b -> m b
class Poly2 m n where
(>>) :: m a -> n b -> n b
class Poly3 m n o | m n -> o where
(>>) :: m a -> n b -> o b
Все они могут быть реализованы. Но вы теряете два действительно важных фактора для их практического использования.
- Вам нужно написать экземпляр для каждой пары типов, которые вы планируете использовать вместе. Это значительно более сложное мероприятие, чем просто экземпляр для каждого типа. Кое-что о
n
противn^2
, - Вы теряете предсказуемость. Что вообще делает операция? Здесь теория и практика пересекаются. Теория позади
Monad
накладывает множество ограничений на операции. Эти ограничения называются "законами монады". Они за пределами возможности проверить в Хаскеле, но любойMonad
экземпляр, который не подчиняется им, считается ошибочным. Конечным результатом является то, что вы можете быстро построить интуицию для того, чтоMonad
операции делают и не делают. Вы можете использовать их, не просматривая детали каждого типа, потому что вы знаете свойства, которым они подчиняются. Ни один из тех возможных классов, которые я предложил, не дает вам никаких подобных заверений. Вы просто не представляете, что они делают.
Я не уверен, что правильно понимаю ваш вопрос, но определенно можно составить Maybe
с IO
или же []
в том же смысле, что вы можете сочинить IO
с []
,
Например, если вы проверяете типы в GHCI, используя :t
,
getContents >>= return . lines
дает вам IO [String]
, Если вы добавите
>>= return . map Text.Read.readMaybe
вы получаете тип IO [Maybe a]
, который является составом IO
, []
а также Maybe
, Затем вы можете передать его
>>= return . Data.Maybe.catMaybes
сгладить IO [a]
, Затем вы можете передать список проанализированных допустимых входных строк в функцию, которая снова выравнивает его и вычисляет вывод.
Собирая это вместе, программа
import Text.Read (readMaybe)
import Data.Maybe (catMaybes)
main :: IO ()
main = getContents >>= -- IO String
return . lines >>= -- IO [String]
return . map readMaybe >>= -- IO [Maybe Int]
return . catMaybes >>= -- IO [Int]
return . (sum :: [Int] -> Int) >>= -- IO Int
print -- IO ()
с входом:
1
2
Ignore this!
3
печать 6
,
Также было бы возможно работать с IO (Maybe [String])
, Maybe [IO String]
, так далее.
Вы можете сделать это с >>
также. Придуманный пример: getContents >> (return . Just) False
читает входные данные, игнорирует их и возвращает IO (Maybe Bool)
,