パターンマッチが失敗したときにHaskellリストの内包表記がエラーを引き起こさないのはなぜですか?
-
19-08-2019 - |
質問
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)