11

次のコードでやや当惑しました。問題のおもちゃではないバージョンでは、モナド結果でモナド計算を実行しようとしています。その値は、IO 内からのみ構築できます。IO の背後にある魔法がそのような計算を厳密にするようですが、それがどのように起こるのか正確にはわかりません。

コード:

data Result a = Result a | Failure deriving (Show)

instance Functor Result where
  fmap f (Result a) = Result (f a)
  fmap f Failure = Failure

instance Applicative Result where
  pure = return
  (<*>) = ap

instance Monad Result where
  return = Result
  Result a >>= f = f a
  Failure >>= _ = Failure

compute :: Int -> Result Int
compute 3 = Failure
compute x = traceShow x $ Result x

compute2 :: Monad m => Int -> m (Result Int)
compute2 3 = return Failure
compute2 x = traceShow x $ return $ Result x

compute3 :: Monad m => Int -> m (Result Int)
compute3 = return . compute

main :: IO ()
main = do
  let results = mapM compute [1..5]
  print $ results
  results2 <- mapM compute2 [1..5]
  print $ sequence results2
  results3 <- mapM compute3 [1..5]
  print $ sequence results3
  let results2' = runIdentity $ mapM compute2 [1..5]
  print $ sequence results2'

出力:

1
2
Failure
1
2
4
5
Failure
1
2
Failure
1
2
Failure
4

2 に答える 2

10

素敵なテストケース。何が起こっているかは次のとおりです。

  • mapM computeいつものように、仕事で怠惰が見られます。ここで驚きはありません。

  • では、リスト全体を定義するmapM compute2IO モナドの内部で作業します。リストの末尾が見つかるとすぐにスキップするのとは異なり、常にリスト全体をスキャンします。コードに注意してください:mapMResultFailureIO

    compute2 x = traceShow x $ return $ Result x
    

    したがって、上記は、IO アクションのリストの各要素にアクセスするとすぐにデバッグ メッセージを出力します。すべてあるので、すべてを印刷します。

  • ではmapM compute3、大まかに次のように使用します。

    compute3 x = return $ traceShow x $ Result x
    

    現在、returnIO は遅延しているため、IO アクションを返すときにトリガーされません。traceShowそのため、mapM compute3を実行してもメッセージは表示されません。代わりに、sequence results3実行時にのみメッセージが表示されます。これにより、 -- すべてではなく、必要な分だけが強制Resultされます。

  • 最後のIdentity例も非常にトリッキーです。これに注意してください:

    > newtype Id1 a = Id1 a
    > data Id2 a = Id2 a
    > Id1 (trace "hey!" True) `seq` 42
    hey!
    42
    > Id2 (trace "hey!" True) `seq` 42
    42
    

    を使用するnewtype場合、実行時にボクシング/アンボクシング (AKA リフティング) は関与しないため、Id1 x値を強制xすると強制されます。型の場合、これdataは起こりません: 値はボックスで囲まれます (たとえば、Id2 undefinedは と同等ではありませんundefined)。

    あなたの例では、Identityコンストラクターを追加しますが、それはnewtype Identity!!からのものです なので、電話するときは

    return $ traceShow x $ Result x
    

    returnhere は何もラップせず、が実行traceShowされるとすぐにトリガーされmapMます。

于 2016-05-31T07:07:39.113 に答える
1

あなたのResultタイプは と実質的に同一Maybeのようです。

Result <-> Just
Failure <-> Nothing

私の貧弱な頭脳のために、Maybeこの回答の残りの部分では用語に固執します.

chi はIO (Maybe a)、あなたが期待したように短絡しない理由を説明しました。でもこんな時に使えるタイプがあるんです実際、本質的に同じタイプですが、Monadインスタンスが異なります。で見つけることができますControl.Monad.Trans.Maybe。次のようになります。

newtype MaybeT m a = MaybeT
  { runMaybeT :: m (Maybe a) }

ご覧のとおり、これは単なるnewtypeラッパーm (Maybe a)です。しかし、そのMonadインスタンスは非常に異なります。

instance Monad m => Monad (MaybeT m) where
  return a = MaybeT $ return (Just a)
  m >>= f = MaybeT $ do
    mres <- runMaybeT m
    case mres of
      Nothing -> return Nothing
      Just a -> runMaybeT (f a)

つまり、基礎となるモナドで計算をm >>= f実行し、何かを取得します。を取得すると、停止して を返します。何かを取得すると、それを に渡し、結果を実行します。fromを使用して、任意のアクションを「成功した」アクションに変えることもできます。mMaybeNothingNothingfmMaybeT mliftControl.Monad.Trans.Class

class MonadTrans t where
  lift :: Monad m => m a -> t m a

instance MonadTrans MaybeT where
  lift m = MaybeT $ Just <$> m

のような場所で定義されたこのクラスを使用することもできますControl.Monad.IO.Class。これは多くの場合、より明確で、はるかに便利です。

class MonadIO m where
  liftIO :: IO a -> m a

instance MonadIO IO where
  liftIO m = m

instance MonadIO m => MonadIO (MaybeT m) where
  liftIO m = lift (liftIO m)
于 2016-05-31T19:51:09.283 に答える