このキーワードの機能の実際の説明をインターネットで探しました。私が見たすべての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
が何を意味するのかについての良い説明は大歓迎です。
実際には、たまたま Prelude で定義されている通常のデータコンストラクターです。これは、すべてのモジュールに自動的にインポートされる標準ライブラリです。
定義は次のようになります。
data Maybe a = Just a
| Nothing
この宣言は、タイプ変数a
によってパラメーター化されるタイプMaybe a
を定義します。これは、a
の代わりに任意のタイプで使用できることを意味します。
この型には、Just a
とNothing
の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 Integer
値m_x
とInt -> Int
関数f
がある場合、fmap f m_x
を実行して、関数f
をMaybe Integer
に直接適用することができます。実際、リフトされたInteger -> Integer
関数のチェーン全体をMaybe Integer
値に適用することができ、完了時にNothing
を明示的に1回だけチェックすることを心配するだけで済みます。
Monad
の概念にまだ慣れているかどうかはわかりませんが、少なくともIO a
を使用したことがあり、タイプシグネチャIO a
はMaybe a
と非常によく似ています。 IO
は、コンストラクターを公開しないという点で特別であるため、Haskellランタイムシステムでのみ実行できますが、Functor
であることに加えて、Monad
でもあります。実際、Monad
は特別な種類のFunctor
であり、いくつかの追加機能があるという重要な意味がありますが、これはそこに入るべき場所ではありません。
とにかく、IO
のようなMonadは、タイプを「値をもたらす計算」を表す新しいタイプにマップし、通常の関数を「値をもたらす計算」に変えるMonad
のようなfmap
のような関数を介して関数をliftM
タイプに持ち上げることができます関数を評価することにより得られます。」
Maybe
もMonad
であると推測しているでしょう(ここまで読んだことがあるなら)。 「値を返せない可能性のある計算」を表します。 fmap
の例と同様に、これにより、各ステップの後に明示的にエラーをチェックすることなく、一連の計算を実行できます。そして実際、Monad
インスタンスが構築される方法、Maybe
の値の計算 stops はNothing
に遭遇するとすぐに実行されるため、即時中断または値なしの戻り値のようなものです。計算の途中。
前に言ったように、言語構文またはランタイムシステムに組み込まれているMaybe
型に固有のものはありません。 Haskellがデフォルトで提供していなかった場合、すべての機能を自分で提供できます。実際、とにかく自分で名前を変えて書き直し、同じ機能を得ることができます。
Maybe
型とそのコンストラクターを理解したことを願っていますが、まだ不明な点がある場合はお知らせください!
現在の答えのほとんどは、Just
と友人がどのように機能するかについての非常に技術的な説明です。私はそれが何のためにあるのかを説明するのに手を試すかもしれないと思った。
多くの言語には、少なくとも一部のタイプでは、実際の値の代わりに使用できるnull
のような値があります。 これは多くの人々を非常に怒らせ、広く悪い動きと見なされています。 それでも、null
のような値が存在しないことを示すと便利な場合があります。
Haskellは、Nothing
(そのバージョンのnull
)を持つことができる場所を明示的にマークすることにより、この問題を解決します。基本的に、関数が通常Foo
型を返す場合、代わりにMaybe Foo
型を返す必要があります。値がないことを示すには、Nothing
を返します。値bar
を返したい場合は、代わりにJust bar
を返す必要があります。
したがって、基本的にNothing
を使用できない場合は、Just
は必要ありません。 Nothing
を使用できる場合は、Just
が必要です。
Maybe
;に魔法のようなものはありません。 Haskell型システム上に構築されています。つまり、通常のHaskell パターンマッチング トリックをすべて使用できます。
タイプt
を指定すると、Just t
の値はタイプt
の既存の値になります。ここで、Nothing
は値に到達できないか、値を持つことが無意味な場合を表します。
あなたの例では、マイナスのバランスをとることは理にかなっていないので、そのようなことが起こる場合、それはNothing
に置き換えられます。
別の例として、これは除算で使用でき、a
とb
を取り、b
がゼロ以外の場合はJust a/b
を返し、そうでない場合はNothing
を返す除算関数を定義します。多くの場合、例外の便利な代替手段として、または意味のない値を置き換えるための以前の例のように、このように使用されます。
合計関数a-> bは、タイプaのすべての可能な値に対してタイプbの値を見つけることができます。
Haskellでは、すべての機能が完全ではありません。この特定の場合、関数lend
は合計ではありません-残高がリザーブ未満の場合は定義されていません(ただし、私の好みでは、newBalanceがリザーブ未満になることを許可しない方が理にかなっています) 、残高100から101を借りることができます)。
非合計関数を扱う他の設計:
lend
は、貸出の条件が満たされない場合に古い残高を返すように記述できます。これらは、機能の全体を強制できない言語で必要な設計上の制限です(たとえば、Agdaはできますが、チューリングが不完全になるなど、他の複雑な問題につながります)。
特別な値を返すか例外をスローすることの問題は、呼び出し側がそのような可能性の処理を誤って簡単に省略してしまうことです。
静かに失敗を破棄することの問題も明らかです-あなたは呼び出し側が関数でできることを制限しています。たとえば、lend
が古い残高を返した場合、発信者は残高が変更されたかどうかを知る方法がありません。意図する目的に応じて、問題が発生する場合と発生しない場合があります。
Haskellのソリューションは、関数の戻り値の型のため、部分関数の呼び出し元にMaybe a
やEither error a
などの型を処理するように強制します。
このように定義されたlend
は、常に新しいバランスを計算するわけではありません-状況によっては、新しいバランスが定義されていません。特別な値Nothingを返すか、新しいバランスをJustにラップすることにより、この状況を発信者に知らせます。呼び出し元は、自由に選択できるようになりました。特別な方法で貸し出しの失敗を処理するか、maybe oldBalance id $ lend amount oldBalance
などの古い残高を無視して使用します。