Haskellでの「Just」構文の意味は何ですか?


118

このキーワードの実際の説明をインターネットで調べました。私が見てきたすべてのHaskellチュートリアルは、ランダムにそれを使い始めただけで、それが何をするものであるかを説明することはありません(そして私はたくさん見てきました)。

以下は、を使用するReal World Haskellの基本的なコードJustです。私はコードが何をするかを理解していますが、目的や機能が何であるかを理解していませんJust

lend amount balance = let reserve    = 100
                      newBalance = balance - amount
                  in if balance < reserve
                     then Nothing
                     else Just newBalance

私が観察したことから、それはMaybeタイピングに関連していますが、それは私が何とか学んだすべてです。

何をJust意味するのかをよく説明していただければ幸いです。

回答:


211

これは実際には、たまたまPreludeで定義されている通常のデータコンストラクターに過ぎません。これは、すべてのモジュールに自動的にインポートされる標準ライブラリです。

多分、構造的に

定義は次のようになります。

data Maybe a = Just a
             | Nothing

その宣言は、型Maybe a変数によってパラメーター化される型を定義しますa。これは、の代わりに任意の型で使用できることを意味しますa

構築と破壊

型には2つのコンストラクターとがJust aありNothingます。型に複数のコンストラクターがある場合、それは型の値が可能なコンストラクターの1つだけで構成されていなければならないことを意味します。このタイプの場合、値はJustまたはを介して構築されNothing、他の(エラーではない)可能性はありません。

Nothingパラメータ型がないため、コンストラクタとして使用される場合Maybe a、すべての型の型のメンバーである定数値を指定しますa。ただし、Justコンストラクターには型パラメーターがあります。つまり、コンストラクターとして使用すると、型aからへの関数のように機能しますMaybe a。つまり、型がa -> Maybe a

したがって、型のコンストラクターはその型の値を作成します。もう一方は、その値を使用したい場合であり、パターンマッチングが重要な役割を果たします。関数とは異なり、コンストラクターはパターンバインディング式で使用できます。これは、複数のコンストラクターを持つ型に属する値のケース分析を行うことができる方法です。

Maybe aパターンマッチで値を使用するには、次のように、コンストラクタごとにパターンを指定する必要があります。

case maybeVal of
    Nothing   -> "There is nothing!"
    Just val  -> "There is a value, and it is " ++ (show val)

その場合の式では、最初のパターンは値がの場合に一致Nothingし、2番目のパターンは値がで構成された場合に一致しJustます。2番目のものも一致する場合は、一致する値が作成されたときにコンストラクターにval渡されたパラメーターに名前もバインドしJustます。

たぶん意味

たぶん、あなたはすでにこれがどのように機能するかを知っていました。実際にはMaybe値に魔法はありません。それは通常のHaskell Algebraic Data Type(ADT)にすぎません。ただしInteger、例からなど、型を効果的に「リフト」または拡張して、値Nothingの欠如を表す追加の値()を持つ新しいコンテキストに拡張するため、かなり使用されています!型システムではInteger、そこにある可能性のある値に到達する前に、その追加の値を確認する必要があります。これにより、驚くべき数のバグが防止されます。

今日、多くの言語がこのような「値なし」の値をNULL参照で処理しています。著名なコンピュータサイエンティストであるトニーホアレ(彼はQuicksortを発明し、チューリングアワードの受賞者です)は、これを彼の「10億ドルの間違い」として所有しています。Maybeタイプはこれを修正する唯一の方法ではありませんが、効果的な方法であることが証明されています。

たぶんファンクターとして

ある型を別の型に変換して、古い型の操作新しい型で機能するように変換できるという考え方は、という便利なインスタンスFunctorMaybe a持つHaskell型クラスの背後にある概念です。

Functorはと呼ばれるメソッドを提供fmapします。これは、基本型(などInteger)の値に及ぶ関数を、リフト型(などMaybe Integer)の値に及ぶ関数にマッピングします。値を処理fmapするために変換された関数は、次のように機能しますMaybe

case maybeVal of
  Nothing  -> Nothing         -- there is nothing, so just return Nothing
  Just val -> Just (f val)    -- there is a value, so apply the function to it

したがって、Maybe Integerm_xInt -> Int関数がある場合、実際に値を取得するかどうかを心配することなく、関数をに直接適用fできます。実際、リフトされた関数のチェーン全体を値に適用でき、終了時に明示的に1回だけチェックすることを心配するだけで済みます。fmap f m_xfMaybe IntegerInteger -> IntegerMaybe IntegerNothing

たぶんモナドとして

の概念にMonadまだ慣れているかはわかりませんが、少なくともIO a以前に使用したことがあり、型シグネチャIO aはと非常によく似ていMaybe aます。けれどもIO、それはあなたにそのコンストラクタを公開していないので、唯一のHaskellのランタイムシステムによって、「走る」ことができるという点で特別で、それもまだだFunctorことに加えてMonad。実際、a MonadFunctorいくつかの追加機能を備えた特別な種類であるという重要な意味がありますが、これはそのための場所ではありません。

とにかく、のようなモナドIO「値の計算その結果」を表す新しいタイプのマップタイプと、あなたはに機能を持ち上げることができるMonad非常に介して、タイプfmapと呼ばれる様機能 liftM値の結果が評価したことを「計算になり、通常の関数であること関数。"

その(あなたがこれまでのところを読んでいる場合)あなたはおそらく推測しているMaybeにもありますMonad。「値を返せない可能性のある計算」を表します。fmap例と同様に、これにより、各ステップの後にエラーを明示的にチェックする必要なく、一連の計算を行うことができます。そして実際、Monadインスタンスの構築方法では、に遭遇するとすぐにMaybe値の計算が停止するNothingため、計算の途中での即時の中止や値のない戻りのようなものです。

あなたは多分書いたかもしれない

前に言ったようにMaybe、言語構文やランタイムシステムに組み込まれている型には固有のものはありません。Haskellがデフォルトで提供していない場合は、自分ですべての機能を提供できます。実際、とにかく自分でもう一度名前を変えて書いて、同じ機能を得ることができます。

うまくいけば、Maybeタイプとそのコンストラクターを理解できたと思いますが、それでも不明な点がある場合は、お知らせください!


19
なんて素晴らしい答えでしょう!Haskell Maybeは他の言語が使用する場所nullnil(いたるところにひどい言葉がNullPointerException潜んでいる)場所をよく使用することを述べておく必要があります。現在、他の言語もこの構造を使用し始めています。Scalaas Option、さらにはJava 8でもOptional型が使用されます。
ランデイ2013

3
これはすばらしい説明です。私が読んだ説明の多くは、JustがMaybe型のコンストラクターであるという考えをほのめかしていましたが、実際に明示したものはありません。
2013

@ランデイ、提案をありがとう。null参照の危険性を参照するように編集しました。
Levi Pearson

@Landeiオプションタイプは70年代のMLから存在します。ScalaはMLの命名規則を使用しているため、Scalaがそこから取得した可能性が高く、Optionsにはコンストラクターがあり、コンストラクターはありません。
ストーンメタル2013

@Landei AppleのSwiftもオプションをかなり利用しています
Jamin

37

現在の回答のほとんどは、Just友達や友達の働きについての非常に技術的な説明です。私はそれが何のためにあるのかを説明することに私の手を試みるかもしれないと思った。

多くの言語には、null少なくとも一部のタイプでは、実際の値の代わりに使用できるような値があります。これは多くの人々を非常に怒らせ、悪い動きと広く見なされています。それでも、null物がないことを示すような値を持つと便利な場合があります。

Haskellは、Nothing(のバージョンであるnull)を使用できる場所を明示的にマークすることにより、この問題を解決します。基本的に、関数が通常はtypeを返す場合、Foo代わりにtypeを返す必要がありMaybe Fooます。値がないことを示したい場合は、を返しNothingます。値を返したいbar場合は、代わりに返す必要がありますJust bar

したがって、基本的には、を使用できない場合Nothingは必要ありませんJust。あなたが持つことができるならNothing、あなたは必要Justです。

不思議なことは何もありませんMaybe。Haskell型システムに基づいて構築されています。つまり、通常のHaskell パターンマッチングトリックをすべて使用できるということです。


1
他の答えの隣にある非常に良い答えですが、コード例からそれでも利益が得られると思います:)
PascalVKooten 2017

13

タイプが与えられたt場合Just t、の値はタイプの既存の値でありt、はNothing値に到達できないことを表します。または、値を持つことは意味がない場合です。

あなたの例では、負のバランスを持つことは意味がありません、そしてそのようなことが起こるなら、それはに置き換えられNothingます。

別の例として、これは除算で使用でき、およびを取りa、ゼロでない場合はbそれを返し、そうでないJust a/b場合bは返す除算関数を定義しNothingます。これは、このように、例外の便利な代替として、または前の例のように、意味のない値を置き換えるためによく使用されます。


1
上記のコードでJustを削除したとしましょう。なぜそれが機能しないのでしょうか。あなたは何も持っていたいときにいつでも持っている必要がありますか?式内の関数がNothingを返す式はどうなりますか?
2013

7
を削除したJust場合、コードはタイプチェックされません。の理由は、Just適切なタイプを維持するためです。タイプ(実際にはモナドですが、単にタイプであると考える方が簡単です)Maybe tがあり、フォームJust tとの要素で構成されていNothingます。にNothingはtype Maybe tがあるため、typeのいずれかNothingまたは一部の値に評価できる式tは正しく型付けされていません。関数がNothingいくつかのケースで戻る場合、その関数を使用するすべての式はisJust、可能性のあるすべてのケースを処理するために、それ(またはケースステートメント)をチェックする何らかの方法を備えている必要があります。
qaphla 2013

2
したがって、通常のtはMaybe型ではないため、Justは単にMaybe型内で一貫している必要があります。すべてがはるかに明確になりました。ありがとうございました!
2013

3
@qaphla:「モナド、実際、[...]」であるというあなたのコメントは誤解を招くものです。Maybe t あるだけのタイプ。のMonadインスタンスがあるという事実は、Maybeそれを型ではないものに変換しません。
サラ

2

合計関数a-> bは、タイプaのすべての可能な値に対してタイプbの値を見つけることができます。

Haskellでは、すべての機能が完全であるとは限りません。この特定のケースでは、関数lendは合計ではありません-残高が予約より少ない場合の定義ではありません(私の好みでは、newBalanceが予約より少ないことを許可しない方が理にかなっています-そのまま、101を借りることができます100のバランス)。

非合計関数を扱う他のデザイン:

  • 入力値が範囲に適合しないことを確認すると例外をスローします
  • 特別な値を返す(プリミティブ型):好きな選択は、自然数を返すための整数関数の負の値です(たとえば、String.indexOf-サブストリングが見つからない場合、返されるインデックスは通常負になるように設計されています)
  • 特別な値(ポインタ)を返す:NULLなど
  • 何もせずに静かに返却する:たとえば、lend貸付の条件が満たされない場合、古い残高を返却するように書き込むことができます
  • 特別な値を返します:なし(またはいくつかのエラー説明オブジェクトを左にラップ)

これらは、機能全体を強制することができない言語では必要な設計上の制限です(たとえば、Agdaはできますが、チューリングが不完全になるなど、他の複雑化を招きます)。

特別な値を返すか、例外をスローすることの問題は、呼び出し側がそのような可能性の処理を誤って省略しやすいことです。

失敗を静かに破棄することの問題も明らかです-呼び出し側が関数で実行できることを制限しています。たとえば、lend古い残高が返された場合、呼び出し元は残高が変更されたかどうかを知る方法がありません。意図する目的に応じて、問題になる場合と問題にならない場合があります。

Haskellの解決策は、部分関数の呼び出し元に、のような型Maybe a、またはEither error a関数の戻り型が原因で処理するように強制します。

このようにlend定義されているこの関数は、常に新しいバランスを計算するわけではありません。状況によっては、新しいバランスが定義されていません。特別な値Nothingを返すか、新しいバランスをJustでラップすることにより、この状況を発信者に知らせます。呼び出し元は、自由に選択できます。貸出の失敗を特別な方法で処理するか、古い残高を無視して使用します(例:)maybe oldBalance id $ lend amount oldBalance


-1

関数のif (cond :: Bool) then (ifTrue :: a) else (ifFalse :: a)タイプはifTrueおよびでなければなりませんifFalse

したがって、を記述する場合はthen NothingMaybe atype inを使用する必要がありますelse f

if balance < reserve
       then (Nothing :: Maybe nb)         -- same type
       else (Just newBalance :: Maybe nb) -- same type

1
ここで本当に深いことを言うつもりだったと思います
sehe

1
Haskellには型推論があります。種類述べる必要はありませんNothingし、Just newBalance明示的には。
2013

初心者へのこの説明のために、明示的なタイプはの使用の意味を明確にします Just
フィリップ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.