複数の機能を試し、成功するとすぐに停止するHaskellイディオムはありますか?


9

Haskellでは、タイプa -> Maybe bを使用して、タイプの値を返すかb、何も返さない(失敗する)関数をモデル化できます。

私がタイプしている場合a1, ..., a(n+1)や機能f1, ..., fnを持つ、fi :: ai -> Maybe a(i+1)すべてのためにi1 <= i <= n私は使用して機能をチェーンできる>>=のオペレータMaybeモナドと書き込みを:

f1 x >>= f2 >>= f3 >>=... >>= fn

>>=各関数があれば、その前身は、意味のある値を返したように適用されていることをオペレータを確実にします。チェーン内の関数が失敗するとすぐに、チェーン全体が失敗し(戻り値Nothing)、チェーン内の以降の関数は評価されません。

同じ入力で複数の関数を試し、1つの関数が成功したらすぐに戻りたいという似たようなパターンがあります。すべての関数が失敗すると(return Nothing)、計算全体が失敗します。より正確には、私には関数がf1, ..., fn :: a -> Maybe bあり、関数を定義します

tryFunctions :: [a -> Maybe b] -> a -> Maybe b
tryFunctions []       _ = Nothing
tryFunctions (f : fs) x = case f x of
                            Nothing    -> tryFunctions fs x
                            r@(Just _) -> r

ある意味では、これはMaybeモナドと二重であり、最初の失敗ではなく最初の成功で計算が停止します。

もちろん、上で書いた関数を使用することもできますが、Haskellでこのパターンを表現するためのよりよく確立された慣用的な方法があるかどうか疑問に思いました。


:ないHaskellのが、C#では、あなたはそのように使用(??)、nullで合体演算子を時折表示されますreturn f1 ?? f2 ?? f3 ?? DefaultValue;
Telastyn

4
はい、あります。Alternativeこれは、シンボルのインフィックス演算子で<|>あり、モノイドの観点から定義されています
Jimmy Hoffa

回答:


8

S要素{a..z}と2項演算子を持つ閉じたセット(要素数は固定)が与えられた場合*

次のiような単一のアイデンティティー要素があります。

forall x in S: i * x = x = x * i

演算子は、次のような連想性があります。

forall a, b, c in S: a * (b * c) = (a * b) * c

あなたはモノイドを持っています。

これで、任意のモノイドを指定すると、バイナリ関数fを次のように定義できます。

f(i, x) = x
f(x, _) = x

これが意味することは、Maybeモノイドの例の場合(Nothing上記のように示されるアイデンティティー要素i)です。

f(Nothing, Just 5) = Just 5
f(Just 5, Nothing) = Just 5
f(Just 5, Just 10) = Just 5
f(Nothing, f(Nothing, Just 5)) = Just 5
f(Nothing, f(Just 5, Nothing)) = Just 5

驚いたことに、この正確な関数をデフォルトのライブラリーで見つけることができません。これは、おそらく私自身の経験不足が原因です。他の誰かがこれをボランティアできるなら、私は心からそれを感謝します。

上記の例から手作業で推定した実装は次のとおりです。

(<||>) :: (Monoid a, Eq a) => a -> a -> a
x <||> y
     | x == mempty = y
     | True = x

例:

λ> [] <||> [1,2] <||> [3,4]
[1,2]
λ> Just "foo" <||> Nothing <||> Just "bar"
Just "foo"
λ> Nothing <||> Just "foo" <||> Just "bar"
Just "foo"
λ> 

次に、関数のリストを入力として使用したい場合...

tryFunctions x funcs = foldl1 (<||>) $ map ($ x) funcs

例:

instance Monoid Bool where
         mempty = False
         mconcat = or
         mappend = (||)

λ> tryFunctions 8 [odd, even]
True
λ> tryFunctions 8 [odd, odd]
False
λ> tryFunctions 8 [odd, odd, even]
True
λ> 

なぜ<|>モノイドのアイデンティティを特別な方法で扱うのか理解できません。その特別な役割を果たすために、任意のセットの任意の要素を選択することはできませんか?なぜセットはモノイドでなければならず、特別な要素は<|>そのアイデンティティに関係するのですか?
ジョルジオ

1
@Giorgioおそらくそれ<|>がモノイドに依存しない理由であり、私はこれをすべて混同していますか?Alternativeタイプクラスに依存しています。私は自分の答えを見て、実現しています、非常に適切ではないようだ-一定であるためには[1,2] <|> [3]、予期せぬ与え[1,2,3]身元を識別するために、モノイド型クラスを使用してそんなにのすべてが右である-と、他のキーが結合性であること期待される動作を取得する必要が、おそらくAlternative私が思いついた行動を与えていない...
Jimmy Hoffa '27 / 07/15

@JimmyHoffa typclassに依存します。たぶん<|>はList <|>とは異なりますか?
jk。

@ジョルジオは私の最後の2つの例fを見て、なぜ連想性が必要なのかを確認します。
ジミーホファ2015

リストのすなわちモノイドは空のリストと連結される
JK。

5
import Data.Monoid

tryFunctions :: a -> [a -> Maybe b] -> Maybe b
tryFunctions x = getFirst . mconcat . map (First . ($ x))

これはクリーンでシンプルですが、次のように修正されていMaybeます...私のソリューションはによって制約されEqています。どういうわけか、私たち2人は、Monoid一般的に利用できるものがないと感じています...
Jimmy Hoffa

@JimmyHoffa:このソリューションを他のデータ型で機能するように一般化したいという意味eitherですか?
ジョルジオ

@ジョルジオ正確に。唯一の制約がMonoid、identity要素を持つものに2つの関数のセット(これは一種のバイナリフィールドであると思います)があり、他のすべての関数が常にidを選択し、他の関数が常に選択するようにすることです。非アイデンティティ要素。私はこれを行う方法がわからないのですEqidentity、どの値がそうであるかを知る必要はありません。明らかに加法的または乗法的モノイドでは、デフォルトでラダー関数を取得します(常にモノイドバイナリ関数を使用して非同一要素を選択します)
ジミーホファ

foldMap代わりに使用できますmconcat . map
4castle

4

これはよく聞こえる 失敗を成功のリストで置き換えるています

Maybe aではなくについて話している[a]が、実際には非常によく似Maybe aている[a]。多くても1つの要素(つまり、Nothing ~= []Just x ~= [x])。

リストの場合は、tryFunctions非常に単純です。すべての関数を指定された引数に適用し、すべての結果を連結します。concatMapこれはうまくいきます:

tryFunctions :: [a -> [b]] -> a -> [b]
tryFunctions fs x = concatMap ($ x) fs

このように、<|>演算子Maybeは「最大で1つの要素を持つリスト」の連結のように機能することがわかります。


1

Conalの精神に従い、それをより小さな単純な操作に分割します。

この場合、asumfrom Data.Foldableは主要部分を実行します。

tryFunction fs x = asum (map ($ x) fs)

あるいは、Jimmy Hoffaの返答でMonoidインスタンスを使用でき(->)ますが、Monoidインスタンスが必要でMaybeあり、標準のインスタンスでは希望どおりに動作しません。あなたはFirstから欲しいData.Monoid

tryFunction = fmap getFirst . fold . map (fmap First)

(またはmconcat、より古く、より特殊なバージョンのfold。)


興味深い解決策(+1)。最初の方が直感的です。
ジョルジョ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.