web-dev-qa-db-ja.com

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

このキーワードの機能の実際の説明をインターネットで探しました。私が見たすべてのHaskellチュートリアルは、ランダムに使用し始め、それが何をするのか決して説明しません(そして多くのことを見てきました)。

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

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

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

Justが何を意味するのかについての良い説明は大歓迎です。

108
reem

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

たぶん、構造的に

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

data Maybe a = Just a
             | Nothing

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

構築と破壊

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

Nothingにはパラメータータイプがないため、コンストラクターとして使用する場合、すべてのタイプaのタイプMaybe 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の場合、最初のパターンが一致し、値がJustで構築された場合、2番目のパターンが一致します。 2番目のものも一致する場合、一致する値が構築されたときにvalコンストラクターに渡されたパラメーターにJustという名前をバインドします。

どういう意味か

たぶん、あなたはすでにこれがどのように機能するかを知っていたかもしれません。 Maybe値には実際には魔法はありません。これは通常のHaskell代数データ型(ADT)です。しかし、あなたの例のIntegerなどの型を、値の不足を表す追加の値(Nothing)を持つ新しいコンテキストに効果的に「持ち上げ」または拡張するため、かなり使用されています。型システムでは、 might が存在するIntegerに到達する前に、追加の値を確認する必要があります。これにより、非常に多くのバグが防止されます。

今日の多くの言語は、NULL参照を介してこの種の「値なし」値を処理しています。著名なコンピューター科学者(彼はQuicksortを発明し、Turing Awardの受賞者)であるTony Hoareは、これを 「10億ドルの間違い」 として所有しています。多分タイプはこれを修正する唯一の方法ではありませんが、それを行う効果的な方法であることが証明されています。

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

古い型の操作を also 新しい型で動作するように変換できるように、ある型を別の型に変換するという考え方は、FunctorというHaskell型クラスの背後にある概念です。これはMaybe aの便利なインスタンスがあります。

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がある場合、fmap f m_xを実行して、関数fMaybe Integerに直接適用することができます。実際、リフトされたInteger -> Integer関数のチェーン全体をMaybe Integer値に適用することができ、完了時にNothingを明示的に1回だけチェックすることを心配するだけで済みます。

モナドとして

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

とにかく、IOのようなMonadは、タイプを「値をもたらす計算」を表す新しいタイプにマップし、通常の関数を「値をもたらす計算」に変えるMonadのようなfmapのような関数を介して関数をliftMタイプに持ち上げることができます関数を評価することにより得られます。」

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

たぶん書かれたかもしれない

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

Maybe型とそのコンストラクターを理解したことを願っていますが、まだ不明な点がある場合はお知らせください!

191
Levi Pearson

現在の答えのほとんどは、Justと友人がどのように機能するかについての非常に技術的な説明です。私はそれが何のためにあるのかを説明するのに手を試すかもしれないと思った。

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

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

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

Maybe;に魔法のようなものはありません。 Haskell型システム上に構築されています。つまり、通常のHaskell パターンマッチング トリックをすべて使用できます。

36

タイプtを指定すると、Just tの値はタイプtの既存の値になります。ここで、Nothingは値に到達できないか、値を持つことが無意味な場合を表します。

あなたの例では、マイナスのバランスをとることは理にかなっていないので、そのようなことが起こる場合、それはNothingに置き換えられます。

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

13
qaphla

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

Haskellでは、すべての機能が完全ではありません。この特定の場合、関数lendは合計ではありません-残高がリザーブ未満の場合は定義されていません(ただし、私の好みでは、newBalanceがリザーブ未満になることを許可しない方が理にかなっています) 、残高100から101を借りることができます)。

非合計関数を扱う他の設計:

  • 入力値が範囲に収まらないことを確認すると例外をスローします
  • 特別な値(プリミティブ型)を返します:お気に入りの選択は、自然数を返すことを意図した整数関数の負の値です(たとえば、String.indexOf-サブストリングが見つからない場合、返されるインデックスは一般に負になるように設計されています)
  • 特別な値(ポインタ)を返します:NULLなど
  • 何もせずに静かに戻る:たとえば、lendは、貸出の条件が満たされない場合に古い残高を返すように記述できます。
  • 特別な値を返します:Nothing(または、左にいくつかのエラー記述オブジェクトをラップ)

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

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

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

Haskellのソリューションは、関数の戻り値の型のため、部分関数の呼び出し元にMaybe aEither error aなどの型を処理するように強制します。

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

2
Sassa NF