パターンマッチが失敗したときにHaskellリストの内包表記がエラーを引き起こさないのはなぜですか?

StackOverflow https://stackoverflow.com/questions/649274

質問

Haskellリスト内包表記がどのように機能するかを理解しようとしています<!> quot;ボンネットの下<!> quot;パターンマッチングに関して。次のghci出力は私のポイントを示しています:

Prelude> let myList = [Just 1, Just 2, Nothing, Just 3]
Prelude> let xs = [x | Just x <- myList]
Prelude> xs
[1,2,3]
Prelude>

ご覧のとおり、<!> quot; Nothing <!> quot;をスキップできます。 <!> quot; Just <!> quot;のみを選択します。値。 Listはモナドであり、(Real World Haskellからのソース、14章)と定義されていることを理解しています:

instance Monad [] where
    return x = [x]
    xs >>= f = concat (map f xs)
    xs >> f = concat (map (\_ -> f) xs)
    fail _ = []

したがって、リスト内包表記は基本的に、リスト内包表記で選択されたすべての要素に対してシングルトンリストを作成し、それらを連結します。あるステップでパターンマッチが失敗した場合、<!> quot; fail <!> quot;の結果。代わりに関数が使用されます。つまり、<!> quot; Just x <!> quot;パターンは一致しないため、[concat]が呼び出されるまで[]がプレースホルダーとして使用されます。 <!> quot; Nothing <!> quot;スキップされているようです。

私が理解できないのは、Haskellが<!> quot; fail <!> quot;関数? <!> quot; compiler magic <!> quot ;、またはHaskellで自分で記述できる機能ですか?次の<!> quot; select <!> quot;を書くことは可能ですか?関数はリストの内包表記と同じように機能しますか?

select :: (a -> b) -> [a] -> [b]
select (Just x -> x) myList       -- how to prevent the lambda from raising an error?
[1,2,3]
役に立ちましたか?

解決

Haskellの実装は内部的にこのように直接行うことはないかもしれませんが、このように考えることは有用です:)

[x | Just x <- myList]

...は次のようになります:

do
    Just x <- myList
    return x

...次のとおりです:

myList >>= \(Just x) -> return x

質問について:

  

私が理解できないのは、Haskellが<!> quot; fail <!> quot;関数?

do-notationでは、パターンバインディングが失敗すると(つまりJust x)、failメソッドが呼び出されます。上記の例では、次のようになります。

myList >>= \temp -> case temp of
    (Just x) -> return x
    _        -> fail "..."

したがって、失敗する可能性のあるモナドコンテキストでパターンマッチがあるたびに、Haskellはfailの呼び出しを挿入します。 IOで試してみてください:

main = do
    (1,x) <- return (0,2)
    print x -- x would be 2, but the pattern match fails

他のヒント

リスト内包表記の脱糖ルールには、次の形式の表現が必要です< =>([ e | p <- l ]は式、eパターン、およびpリスト式)は次のように動作します

let ok p = [e]
    ok _ = []
in concatMap ok l

以前のバージョンのHaskellには monad内包表記がありました。 l表記では読みにくく、冗長であるため、言語から削除されました。 (リストの内包表記も冗長ですが、読むのはそれほど難しくありません。)doモナドとしてmzero脱糖することを考える ゼロのモナド)は次のようなものを生成します

let ok p = return e
    ok _ = mzero
in l >>= ok

MonadPlusは<=>クラスのものです。これは非常に近い

do { p <- l; return e }

脱糖する

let ok p = return e
    ok _ = fail "..."
in l >>= ok

リストモナドを取得すると、次のようになります

return e = [e]
mzero = fail _ = []
(>>=) = flip concatMap

つまり、3つのアプローチ(リスト内包表記、モナド内包表記、<=>式)はリストに対して同等です。

リスト内包構文は、リスト([])またはMaybeがたまたまMonadタイプクラスのインスタンスであるという事実とは関係ないと思います。

リストの内包表記は確かにコンパイラーマジックまたは構文シュガーですが、コンパイラーはfailデータ型の構造を知っているため可能です。

リスト内包表記は次のようにコンパイルされます:(まあ、実際にはGHCに対してチェックしなかったと思います)

xs = let f = \xs -> case xs of
                      Just x -> [x]
                      _      -> []
     in concatMap f myList

ご覧のとおり、コンパイラはselect関数を呼び出す必要はありません。リストが であることがわかっているため、空のリストを単純にインライン化できます。


興味深いことに、リスト内包構文の「スキップ」パターン一致の失敗というこの事実は、一部のライブラリで汎用プログラミングを行うために使用されます。 Uniplateライブラリの例を参照してください。


編集:ああ、質問に答えるために、与えたラムダでNothing関数を呼び出すことはできません。 f値を指定して呼び出すと、パターンマッチの失敗時に実際に失敗します。

上記のコードからconcatMap関数を渡すことができますが、<=>は次のタイプを持ちます:

select :: (a -> [b]) -> [a] -> [b]

これはまったく問題ありません。内部で<=>関数を使用できます:-)

また、新しい<=>には、リスト用のモナドバインド演算子のタイプがあります(引数を反転):

(>>=) :: [a] -> (a -> [b]) -> [b]
xs >>= f = concatMap f xs -- 'or as you said: concat (map f xs)
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top