10

次の単純な関数は、指定されたモナディック関数をNothingに達するまで繰り返し適用し、その時点で最後のNothing以外の値を返します。それは私が必要とすることを行い、私はそれがどのように機能するかを理解しています。

lastJustM :: (Monad m) => (a -> m (Maybe a)) -> a -> m a
lastJustM g x = g x >>= maybe (return x) (lastJustM g)

Haskellでの独学の一環として、可能な限り明示的な再帰を避けようとしています(または少なくともその方法を理解しています)。この場合、単純で非明示的に再帰的な解決策があるはずですが、私はそれを理解するのに苦労しています。

のモナディックバージョンのようなものは必要ありませんtakeWhile。これは、Nothingより前の値をすべて収集するのに費用がかかる可能性があり、とにかくそれらを気にしないためです。

Hoogleの署名を確認しましたが、何も表示されません。ここm (Maybe a)ではモナド変換子が役立つかもしれないと少し思わせますが、詳細を考え出すのに必要な直感はまだありません。

おそらく、これを行うのは恥ずかしいほど簡単であるか、それができない、またはできない理由を理解するのは恥ずかしいほど簡単ですが、私が教育戦略として自己恥ずかしさを使用したのはこれが初めてではありません。

更新:もちろん、使用する代わりに述語を提供することもできます:(述語が真である最後の値を返す)のMaybeようなものも同様に機能します。(a -> Bool) -> (a -> m a) -> a私が興味を持っているのは、標準のコンビネータを使用して、明示的な再帰なしでどちらかのバージョンを作成する方法です。


背景:コンテキストの簡略化された作業例を次に示します。単位正方形内のランダムウォークに関心があるとしますが、出口のポイントのみを考慮します。次のステップ関数があります。

randomStep :: (Floating a, Ord a, Random a) =>
              a -> (a, a) -> State StdGen (Maybe (a, a))
randomStep s (x, y) = do
  (a, gen') <- randomR (0, 2 * pi) <$> get
  put gen'
  let (x', y') = (x + s * cos a, y + s * sin a)
  if x' < 0 || x' > 1 || y' < 0 || y' > 1
    then return Nothing
    else return $ Just (x', y')

のようなものevalState (lastJustM (randomStep 0.01) (0.5, 0.5)) <$> newStdGenは、新しいデータポイントを提供します。

4

2 に答える 2

9

明示的な再帰を回避することの多くは、組み込みの再帰コンビネータを構成することです。これは通常、非常に一般的な持ち上げられていない値で機能します。Functor、Monad、またはその他の持ち上げられた型で同じことを行うと、 、 、 などの基本的な持ち上げ操作を使用して機能することfmap<*>あり>>=ます。mapM、 などのように、事前にリフトされたバージョンがすでに存在する場合がzipWithMあります。また、 のようにtakeWhile、リフティングが簡単ではなく、組み込みバージョンが提供されていない場合もあります。

あなたの関数には、標準的なコンビネータの持ち上げられたバージョンであるべきものの特徴があります。まず、モナドを取り除いて、暗黙的に持ち上げている関数を再構築しましょう。

lastJust :: (a -> Maybe a) -> a -> a

ここで「最後」という言葉がヒントになります。非明示的な再帰は、多くの場合、一時リストを制御構造として使用します。したがって、last取得するまで関数を反復することによって生成されたリストに、必要なものが適用されますNothing。型を少し一般化すると、ジェネレーターが見つかります。

unfoldr :: (b -> Maybe (a, b)) -> b -> [a]

したがって、次のようなものがあります。

dup x = (x, x)
lastJust f x = last $ unfoldr (fmap dup . f) x

(私の知る限り) モナドの展開はなく、それを持ち上げるのtakeWhileは自明ではありません。もう 1 つの理にかなっている可能性があるのは、署名のようなもの(MonadMaybe m) => (b -> m (a, b)) -> b -> m [a]とそれに付随MaybeTする変換子を使用した、より一般的な展開ですが、それも標準ライブラリには存在せず、とにかくモナド変換子は一種の絶望の穴です。Maybe3 番目のアプローチは、未知のモナドの両方を一般化する何らかの方法を見つけることかもしれませんMonadPlus

もちろん、構造が異なる他のアプローチもあるかもしれませんが、再帰的にモナドに「入る」必要がある関数では、同様の扱いにくさを感じる可能性が高いと思います。>>=joinなどで削除されます。

要約すると、最初の検査では、記述された関数は、明示的な再帰を使用せずにunfoldM、存在しない再帰コンビネータ (のフレーバー) を使用して表現するのが最適です。コンビネータを自分で作成するか (人々が で行ったようにtakeWhileM)、Hackage でモナド再帰コンビネータを掘り下げるか、コードをそのままにしておくことができます。

于 2010-05-18T18:17:48.247 に答える
3

のモナド バージョンのようなものは必要ありません。Nothing よりtakeWhileも前の値をすべて収集するにはコストがかかる可能性があり、とにかく気にしないからです。

Monadic-liststakeWhileは、明示的に収集したい場合を除き、Nothing より前の値をすべて収集するわけではありません。これは、リンク先の質問に対するこの回答で使用されている「リスト」パッケージtakeWhileからのものです。

実装したい機能については:

{-# LANGUAGE ScopedTypeVariables #-}

import Control.Monad.ListT (ListT) -- from "List" package on hackage
import Data.List.Class (takeWhile, iterateM, lastL)
import Prelude hiding (takeWhile)

thingM :: forall a m. Monad m => (a -> Bool) -> (a -> m a) -> m a -> m a
thingM pred stepM startM =
    lastL $ takeWhile pred list
    where
        list :: ListT m a
        list = iterateM stepM startM
于 2010-05-18T22:37:08.923 に答える