13

モナドの説明のほとんどは、モナドが値をラップする例を使用しています。たとえばMaybe aa型変数はラップされたものです。しかし、何もラップしないモナドについて疑問に思っています。

人為的な例として、制御できるがセンサーを持たない現実世界のロボットがあるとします。多分私はそれを次のように制御したいと思います:

robotMovementScript :: RobotMonad ()
robotMovementScript = do
  moveLeft 10
  moveForward 25
  rotate 180

main :: IO ()
main = 
  liftIO $ runRobot robotMovementScript connectToRobot

架空の API では、connectToRobot何らかの種類のハンドルを物理デバイスに返します。この接続が の「コンテキスト」になりRobotMonadます。ロボットへの接続は値を返すことができないため、モナドの具象型は常にRobotMonad ()です。

いくつかの質問:

  1. 私の不自然な例は正しいように見えますか?
  2. モナドの「コンテキスト」の概念を正しく理解していますか? ロボットの接続をコンテキストとして説明するのは正しいですか?
  3. RobotMonad-- 値を決してラップしない-- のような -- モナドを持つことは理にかなっていますか? それともモナドの基本概念に反するのでしょうか?
  4. モノイドはこの種のアプリケーションにより適していますか? ロボット制御アクションを と連結することを想像でき<>ます。表記doの方が読みやすいようですが。
  5. モナドの定義では、型が常にであることを保証する何かがあるでしょうか/あるでしょうRobotMonad ()か?

Data.Binary.Put例として見てきた。それは私が考えているものと似ているように見えます(またはおそらく同一ですか?)。しかし、それには Writer モナドと Builder モノイドも含まれます。これらの追加されたしわと私の現在のスキルレベルを考慮すると、Putモナドは最も有益な例ではないかもしれません.

編集

このようなロボットや API を実際に構築する必要はありません。この例は完全にわざとです。モナドから値を引き出す理由が決してない例が必要でした。ですから、ロボットの問題を解決する最も簡単な方法を求めているわけではありません。むしろ、内部値のないモナドに関するこの思考実験は、モナド全般をよりよく理解するための試みです。

4

4 に答える 4

16

TL;DR値がラップされていない Monad はそれほど特別なものではなく、リストとしてモデル化したものとまったく同じパワーを得ることができます。

Freeモナドというものがあります。ある意味で他のすべてのモナドの良い表現者であるため、便利です---ある状況でのモナドの動作を理解できれば、sがそこで一般的Freeにどのように動作するかについての良い洞察が得られます.Monad

このように見えます

data Free f a = Pure a
              | Free (f (Free f a))

そして はいつfでもFunctorFree fMonad

instance Functor f => Monad (Free f) where
  return       = Pure
  Pure a >>= f = f a
  Free w >>= f = Free (fmap (>>= f) w)

aでは、が常に の場合はどうなる()でしょうか。aパラメータはもう必要ありません

data Freed f = Stop 
             | Freed (f (Freed f))

明らかに、これMonadは間違った種類 (型の型) を持っているため、もはや ではありません。

Monad f ===> f       :: * -> *
             Freed f :: *

しかし、パーツMonadを取り除くことで、ic 機能のようなものを定義することができます。a

returned :: Freed f
returned = Stop

bound :: Functor f                          -- compare with the Monad definition
   => Freed f -> Freed f                    -- with all `a`s replaced by ()
   -> Freed f
bound Stop k      = k                       Pure () >>= f = f ()
bound (Freed w) k =                         Free w  >>= f =
  Freed (fmap (`bound` k) w)                  Free (fmap (>>= f) w)

-- Also compare with (++)
(++) []     ys = ys
(++) (x:xs) ys = x : ((++) xs ys)

のように見える (そして!)Monoidです。

instance Functor f => Monoid (Freed f) where
  mempty  = returned
  mappend = bound

そしてMonoids は最初にリストによってモデル化できます。リストのユニバーサル プロパティを使用します Monoid。関数があればMonoid m => (a -> m)、リスト[a]を に変換できmます。

convert :: Monoid m => (a -> m) -> [a] -> m
convert f = foldr mappend mempty . map f

convertFreed :: Functor f => [f ()] -> Freed f
convertFreed = convert go where
  go :: Functor f => f () -> Freed f
  go w = Freed (const Stop <$> w)

あなたのロボットの場合、アクションのリストを使用するだけで済みます。

data Direction = Left | Right | Forward | Back
data ActionF a = Move Direction Double a
               | Rotate Double a
               deriving ( Functor )

-- and if we're using `ActionF ()` then we might as well do

data Action = Move Direction Double
            | Rotate Double

robotMovementScript = [ Move Left    10
                      , Move Forward 25
                      , Rotate       180
                      ]

これを にキャストするとIO、この方向のリストが に明確に変換されます。これはMonad、イニシャルを取得してMonoidに送信し、必要なアクションのイニシャルとしてFreed扱い、解釈することがわかります。Freed fFree f ()MonadIO

しかし、「ラップされた」値を使用していない場合、Monad構造を実際に使用していないことは明らかです。リストだけでも構いません。

于 2013-11-10T04:00:11.910 に答える
3

これらの部分について部分的な答えを出そうとします:

  • RobotMonad--値を決してラップしない-- のようなモナドを持つことは理にかなっていますか? それともモナドの基本概念に反するのでしょうか?
  • モノイドはこの種のアプリケーションにより適していますか? ロボット制御アクションを と連結することを想像でき<>ます。do表記の方が読みやすいようですが。
  • モナドの定義では、型が常にであることを保証する何かがあるでしょうか/あるでしょうRobotMonad ()か?

モナドのコア操作はモナドバインド操作です

(>>=) :: (Monad m) => m a -> (a -> m b) -> m b

これは、アクションが前のアクションの値に依存する (または依存できる) ことを意味します。したがって、(継続モナドのような複雑な形式であっても) 値と見なすことができるものを本質的に持たない概念がある場合、モナドは良い抽象化ではありません

放棄>>=すると、基本的に残りますApplicative。また、アクションを構成することもできますが、それらの組み合わせは前の値に依存することはできません。

Applicativeあなたが提案したように、値を持たないインスタンスもあります: Data.Functor.Constant。その型のアクションは、a一緒に構成できるようにモノイドである必要があります。これはあなたのアイデアに最も近いコンセプトのようです。もちろん、代わりにaを直接Constant使用することもできます。Monoid


とはいえ、おそらくより簡単な解決策はRobotMonad a、値を運ぶモナドを持つことです (これはWriter、すでに述べたように、本質的にモナドと同形です)。runRobotそして、 requireを宣言するとRobotMonad ()、値のないスクリプトのみを実行できるようになります。

runRobot :: RobotMonad () -> RobotHandle -> IO ()

これにより、表記法を使用してdo、ロボット スクリプト内で値を操作できるようになります。ロボットにセンサーがない場合でも、値を渡すことができると便利なことがよくあります。そして、概念を拡張すると、RobotMonadT m a(似てWriterTいる) のようなモナド変換子を次のようなもので作成できます。

runRobotT :: (Monad m) => RobotMonadT m () -> RobotHandle -> IO (m ())

多分

runRobotT :: (MonadIO m) => RobotMonadT m () -> RobotHandle -> m ()

これは、ロボットのアクションを任意のモナドと組み合わせることができる強力な抽象化です。

于 2013-11-10T08:42:29.003 に答える
1

まああります

data Useless a = Useless
instance Monad Useless where
  return = const Useless
  Useless >>= f = Useless

しかし、私が示したように、それは役に立ちません。

必要なのはWriterモナドです。これはモノイドをモナドとしてラップするため、do 記法を使用できます。

于 2013-11-09T23:44:43.707 に答える
1

まあ、あなたはただサポートするタイプを持っているようです

(>>) :: m a -> m b -> m b

ただし、s のみを使用できるようにすることをさらに指定しますm ()。この場合、私はに投票します

foo = mconcat
      [ moveLeft 10
      , moveForward 25
      , rotate 180]

簡単な解決策として。別の方法は、次のようなことをすることです

type Robot = Writer [RobotAction]
inj :: RobotAction -> Robot ()
inj = tell . (:[])

runRobot :: Robot a -> [RobotAction]
runRobot = snd . runWriter

foo = runRobot $ do
  inj $ moveLeft 10
  inj $ moveForward 25
  inj $ rotate 180

Writerモナドの使用。

値をラップしないことの問題は、

return a >>= f === f a

値を無視するが、他の興味深い情報を含むモナドがあるとします。

newtype Robot a = Robot {unRobot :: [RobotAction]}

addAction :: RobotAction -> Robot a -> Robot b

f a = Robot [a]

値を無視すると、

instance Monad Robot where
  return = const (Robot [])
  a >>= f = a -- never run the function

それで

return a >>= f  /= f a

したがって、モナドはありません。したがって、モナドに興味深い状態を持たせたい場合、==false を返す場合は、その値を保存する必要があります。

于 2013-11-10T00:13:47.220 に答える