значения a несут в себе пред и пост условия, закодированные типами, что позволяет ограничивать возможные варианты связывания.
Выглядит параметризованная мондада примерно так:
class PMonad m where
unit :: a -> m p p a
bind :: m p q a -> (a -> m q r b) -> m p r b
Здесь видно, что, оператор bind биндит только монады с совпадающими пред и пост условиями (т.е. m p q к m q r и
получает в результате m p r). В качестве m обычно трансформер поверх IO, либо поверх StateT или ReaderT, которые в
свою очередь надеты на IO. пред и пост условия от
ражают требования, которые должны выполняться перед вызовом действия IO и состояние
после вызова. С их помощью, например, можно гарантировать, что вы никогда не будете писать в закрытый файл, или обязательно
освободите заблокированный ресурс (если не словите deadlock). Например как-то так:
-- | здесь l - lock, m - мондада, p q - пред и пост условия, a - возвращаемое значение.newtype LockT l m p q a
-- | Фантомные типы, отображающие состояния блокируемого ресурса: Unlocked - разблокирован, Shared - разделяемая блокировка
(чтение), Exclusive - эксклюзивная блокировка (запись)
data Unlocked
data Shared
data Exclusive
-- | Получает блокировку на чтение
acquire :: LockT l IO Unlocked Shared ()
-- | Поднимает уровень блокировки с разделяемого до эксклюзивного
raise :: LockT l IO Shared Exclusive ()
-- | Понижает уровень блокировки
lower :: LockT l IO Exclusive Shared ()
-- | Снимает блокировку
release :: LockT l IO Shared Unlocked ()
-- | выполняет последовательность действий с блокируемым ресурсом
withLockT :: l -> (forall l' . LockT l' IO Unlocked Unlocked a) -> a
Последовательность действий может быть запущена только с помощью функции withLockT, которая принимает последовательности, стартующие с разблокированным ресурсом и снимающие блокировку перед завершением. Внутри они могут сколько угодно раз брать, снимать, повышать и понижать блокировку, но только в правильной последовательности. Функция требует от монады быть полиморфной по l', чтобы не было возможности "вытащить" действие наружу (например, написать функцию типа :: LockT <concreteType> IO Shared Unlocked (LockT <concreteType> IO Shared Unlocked)) и вызвать его в другой цепочке, не относящейся к данному ресурсу. Казалось бы, какая замечательная монада
--> тот самый последний абзац
но засада заключается в том, что поиск ванильной параметризованной монады по хакаджу не даёт результатов. Вместо неё находится мутировавший вариант — параметризованная монада имени Эдуарда Кметта (http://comonad.com/reader/2007/parameterized-monads-in-haskell/). Вопрос монадоводам: чем, собственно, она лучше, использовал ли кто-нибудь, какие подводные камни?
Решил я, что этот раздел достаточно тихий, чтобы исползовать его в качестве своего блога.
----
Дорогой дневник, продолжая свои исследования в области монадологии (http://ru.wikipedia.org/wiki/Монадология) я заметил за собой некоторое желание не привязываться к IO, а использовать MonadIO или что-нибудь подобное. Особенно сильно желание даёт о себе знать при активном использовании трансформеров. А так как Хаскель придман специально для программистов, идущих на поводу у своих желаний, то почему бы и нет?
Проблема: функции типа :: (a -> IO b) -> IO b. На деревенском это callback-функции выполняющий действие в IO. Например: mask :: ((forall a. IO a -> IO a) -> IO b) -> IO b, withMVar :: MVar a -> (a -> IO b) -> IO b (и прочие варианты with...)
Наивная реализация ведет к появлению классов вида
class MyClass m where
withSomething :: Data' -> (Data -> m b) -> m b
и экземпляров вида
instance (MonadIO m) => MyClass (MyCrazyTransformer m) where
withSomething :: Data' -> (Data -> (MyCrazyTransformer m) b) -> (MyCrazyTransformer m) b
{-# LANGUAGE GeneralizedNewtypeDeriving
, TypeFamilies
, UndecidableInstances #-}module Spice.Concurrent.MVar where
import Control.Applicative
import qualified Control.Concurrent.MVar as M
import qualified Control.Concurrent.MVar.Lifted as L
import Control.Monad
import Control.Monad.Base
import Control.Monad.Trans.Class
import Control.Monad.Trans.Control
import Control.Monad.Trans.Identity
class (Monad m) => MVarMonad m where
type MVarType m :: * -> *
takeMVar :: MVarType m a -> m a
putMVar :: MVarType m a -> a -> m ()
modifyMVar :: MVarType m a -> (a -> m (a, b)) -> m b
newtype MVarT m a = MVarT { unMVarT :: IdentityT m a }
deriving ( Applicative
, Functor
, Monad
, MonadTrans
, MonadBase b )
instance (MonadBase IO m) => MVarMonad (MVarT m) where
type MVarType (MVarT m) = M.MVar
takeMVar = L.takeMVar
putMVar = L.putMVar
-- modifyMVar = L.modifyMVar requires (MonadBaseControl IO (MVarT m))
MonadBaseControl почему-то автоматически не наследуется. Что в ней такого особенного?
Если сделать MonadBaseControl ручками, то всё работает
{-# LANGUAGE GeneralizedNewtypeDeriving
, MultiParamTypeClasses
, TypeFamilies
, UndecidableInstances
, KitchenSink #-}module Spice.Concurrent.MVar where
import Control.Applicative
import qualified Control.Concurrent.MVar as M
import qualified Control.Concurrent.MVar.Lifted as L
import Control.Monad
import Control.Monad.Base
import Control.Monad.Trans.Class
import Control.Monad.Trans.Control
import Control.Monad.Trans.Identity
class (Monad m) => MVarMonad m where
type MVar m :: * -> *
takeMVar :: MVar m a -> m a
putMVar :: MVar m a -> a -> m ()
modifyMVar :: MVar m a -> (a -> m (a, b)) -> m b
newtype MVarMonadT m a = MVarT { unMVarT :: IdentityT m a }
deriving ( Applicative
, Functor
, Monad
, MonadTrans
, MonadBase b )
runMVarT = runIdentityT . unMVarT
instance (MonadBaseControl IO m) => MonadBaseControl IO (MVarMonadT m) where
newtype StM (MVarMonadT m) a = StMVarMonad { unStMVarMonad :: StM (IdentityT m) a }
liftBaseWith f = MVarT $ liftBaseWith $ \run -> f $ liftM StMVarMonad . run . unMVarT
restoreM = MVarT . restoreM . unStMVarMonad
instance (MonadBase IO m, MonadBaseControl IO m) => MVarMonad (MVarMonadT m) where
type MVar (MVarMonadT m) = M.MVar
takeMVar = L.takeMVar
putMVar = L.putMVar
modifyMVar = L.modifyMVar
Многовато говна получается для такой тривиальной задачи.
Re: [Haskell] Еще одна статья, объясняющая что такое монады
Рано или поздно у любого нормального человека, вроде физика или математика, возникает желание использовать нормальные алгебраические структуры, вроде абелевых полугрупп или унитарных алгебр над идемпотентными полукольцами. Обычно в таких случаях предлагают воспользоваться http://hackage.haskell.org/package/numeric-prelude, но блин, какая-то она "не такая".. Возможно это из-за того, что все тайпклассы там называются C, а типы данных T, короче обычные люди привыкли программировать на Хаскелле немного не так. Специально для них Эдуардом Кметтом написана http://hackage.haskell.org/package/algebra, как обычно требующая {-# LANGUAGE KitchenSink #-} для сборки, но в целом, более привычная. Ммм.. орбит-абстрактная алгебра с ароматом абстрактной алгебры.
Re[2]: [Haskell] Еще одна статья, объясняющая что такое мона
Да, хорошая статья, хотя довольно старая и поэтому местами out-of-date; например, композиция стрелок Клейсли в Control.Monad давно есть, это оператор "рыбка" <span class='lineQuote level1'>>=></span>.
Re[3]: [Haskell] Еще одна статья, объясняющая что такое мона
Здравствуйте, deniok, Вы писали:
D>Да, хорошая статья, хотя довольно старая и поэтому местами out-of-date; например, композиция стрелок Клейсли в Control.Monad давно есть, это оператор "рыбка" <span class='lineQuote level1'>>=></span>.
Я даже не заметил сначала, что в статье написано, что такой операции нет :3
{-# LANGUAGE TypeSynonymInstances #-}module Yoba where
import Control.Monad.Identity hiding (fix)
-- fixnewtype FixF f a = FixF { unFixF :: f (a (FixF f a)) }
fixF = FixF
-- basedata ListB a r = Empty | Cons a r deriving (Show)
-- puretype Fix = FixF Identity
fix :: a (FixF Identity a) -> FixF Identity a
fix = fixF . Identity
unFix :: FixF Identity a -> a (FixF Identity a)
unFix = runIdentity . unFixF
type List a = (Fix (ListB a))
instance (Show a) => Show (List a) where
show a = '(' : shows (unFix a) ")"
fromList :: [Int] -> List Int
fromList [] = fix Empty
fromList (x:xs) = fix $ Cons x $ fromList xs
-- iotype FixIO = FixF IO
fixIO :: a (FixF IO a) -> FixF IO a
fixIO = fixF . return
unFixIO :: FixF IO a -> a (FixF IO a)
unFixIO = error"impossible (requires unsafePerformIO)"type ListIO a = (FixIO (ListB a))
fromConsole :: ListIO Int
fromConsole = fixF $ do
l <- getLine
if null l then return Empty
else return $ Cons (read l) fromConsole
toConsole :: ListIO Int -> IO ()
toConsole list = do
a <- unFixF list
case a of
Empty -> return ()
Cons x xs -> do
putStrLn $ show x
toConsole xs
Рекурсивные типы с эффектом (генерализованный катаморфизм потом напишу). Обычный рекурсивный тип — частный случай при f = Identity.
import Data.Functor.Foldable
import Data.Stream.Future
data instance Prim (Future a) b = FutureP a b | LastP a deriving (Eq, Ord, Show, Read)
instance Functor (Prim (Future a)) where
fmap f (FutureP a b) = FutureP a (f b)
fmap _ (LastP a) = (LastP a)
type instance Base (Future a) = Prim (Future a)
instance Foldable (Future a) where
project (x :< xs) = FutureP x xs
project (Last x) = LastP x
instance Unfoldable (Future a) where
embed (FutureP x xs) = x :< xs
embed (LastP a) = Last a
Не знаю, насколько это корректно, и не хочу разбираться, поэтому отправил письмо Кметту. Para и apo реализовывать не стал. Самому мне нужно было для ana, а конечный поток я использую в качестве непустого списка. Зачем нужны непустые списки, если есть конечные потоки? Или зачем нужны конечные потоки, если есть непустые списки?