関数型プログラミングの設計方法は 違法な状態を表現できないようにする です。私はいつもこれが型の構造で達成されているのを見ますが、型の値はどうですか?
Emailという文字列があり、有効な電子メールアドレスのみを保持したい場合はどうなりますか(一部の正規表現に対してチェックされます)? (OOPを使用せずに)機能的な方法でこれを行うにはどうすればよいですか?
私はあなたがあなたのすべてをするのと同じ方法でランタイムエラー処理を仮定しますか?
「カプセル化用のクラスとプロパティを使用して」実行した場合、呼び出しチェーンの上位にあるコードがhave注意するという例外をスローします(つまり、セッター内)。 。これを魔法のように解決するのはあなたの「クラスとプロパティ」ではなく、例外をスローしてキャッチするのはあなたの規律です。ほとんどのFP言語では、単純なMaybe
またはより詳細なEither
(またはこれらが何であれ)から、誤った値/入力を通知するための表現の幅広い武器がありますF#で呼び出されます;)、本格的な例外、stderr-messageによる即時停止の強制。現在のapp/libコンテキストに合わせて。
型の「違法な状態を表現できないようにする」とは、型システム/コンパイラが理解している限り、簡単に作成できる瞬間を先取りするためのものです開発者の間違い- to:not ユーザーエラー。
もちろん、これまで以上に多くのクラスのバグの処理を静的(コンパイル)側にシフトする方法についての学術的な調査と研究があります。HaskellにはLiquidHaskellの顕著な例があります。しかし、タイムマシンができるまで、コンパイル後に読み取った入力に誤りがある場合、プログラムのコンパイルをさかのぼって防ぐことはできません:D言い換えると、間違った電子メールアドレスを防ぐ唯一の方法は、おそらく不可能なGUIを課すことです。 1つを通過させます。
一般的なイディオムは、スマートコンストラクターを使用することです。
module Email (email, fromEmail, Email()) where
-- export the type, but not the constructor
newtype Email = Email String
-- export this
email :: String -> Maybe Email
email s | validEmail s = Just (Email s)
| otherwise = Nothing
-- and this
fromEmail :: Email -> String
fromEmail (Email s) = s
これにより、コンパイル時ではなく、実行時に電子メールが検証されます。
コンパイル時の検証では、GADTを多用するString
のバリアントを悪用するか、Template Haskell(メタプログラミング)を使用してチェックを行う必要があります(電子メールの値がリテラルの場合)。
依存型は、値をサポートする言語(Agda、Idris、Coqなど)に対して、値が正しい形式であることを保証することもできます。 F-starは、前提条件/事後条件を検証し、いくつかの高度な静的チェックを実行できるF#のバリアントです。
私は通常、@ chiが行った方法で行います。彼が述べたように、テンプレートHaskellを使用して、コンパイル時に提供された電子メールをチェックすることもできます。それを行う例:
#!/usr/bin/env stack
{- stack
--resolver lts-8.2
exec ghci
--package email-validate
--package bytestring
-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE QuasiQuotes #-}
import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Language.Haskell.TH.Syntax
import Data.ByteString.Char8
import Text.Email.Validate
instance Lift ByteString where
lift b = [|pack $(lift $ unpack b)|]
instance Lift EmailAddress where
lift email = lift (toByteString email)
email :: QuasiQuoter
email =
QuasiQuoter
{ quoteExp =
\str ->
let (item :: EmailAddress) =
case (validate (pack str)) of
Left msg -> error msg
Right email -> email
in [|item|]
}
さて、これをghci
にロードすると:
> :set -XQuasiQuotes
> [email|[email protected]|]
"[email protected]"
> [email|invalidemail|]
<interactive>:6:1: error:
• Exception when trying to run compile-time code:
@: not enough input
CallStack (from HasCallStack):
error, called at EmailV.hs:36:28 in main:EmailV
Code: quoteExp email "invalidemail"
• In the quasi-quotation: [email|invalidemail|]
無効な入力でコンパイルエラーが発生する方法を確認できます。
表示されているように、@ chiと@Sibiの両方の回答が、詳細化タイプに関するものです。つまり、バリデーターでサポートされる値の範囲を制限しながら、他のタイプを囲むタイプ。検証は、ユースケースに応じて、実行時とコンパイル時の両方で実行できます。
たまたま、私が作成した "refined" 、両方の場合の抽象化を提供するライブラリです。詳細な紹介については、リンクをたどってください。
このライブラリをシナリオに適用するには、1つのモジュールで述語を定義します。
import Refined
import Data.ByteString (ByteString)
data IsEmail
instance Predicate IsEmail ByteString where
validate _ value =
if isEmail value
then Nothing
else Just "ByteString form an invalid Email"
where
isEmail =
error "TODO: Define me"
-- | An alias for convenince, so that there's less to type.
type EmailBytes =
Refined IsEmail ByteString
次に、それを他のモジュールで使用します(これはテンプレートHaskellのために必要です)。
コンパイル時と実行時の両方で値を作成できます。
-- * Constructing
-------------------------
{-|
Validates your input at run-time.
Abstracts over the Smart Constructor pattern.
-}
dynamicallyCheckedEmailLiteral :: Either String EmailBytes
dynamicallyCheckedEmailLiteral =
refine "[email protected]"
{-|
Validates your input at compile-time with zero overhead.
Abstracts over the solution involving Lift and QuasiQuotes.
-}
staticallyCheckedEmailLiteral :: EmailBytes
staticallyCheckedEmailLiteral =
$$(refineTH "[email protected]")
-- * Using
-------------------------
aFunctionWhichImpliesThatTheInputRepresentsAValidEmail :: EmailBytes -> IO ()
aFunctionWhichImpliesThatTheInputRepresentsAValidEmail emailBytes =
error "TODO: Define me"
where
{-
Shows how you can extract the "refined" value at zero cost.
It makes sense to do so in an enclosed setting.
E.g., here you can see `bytes` defined as a local value,
and we can be sure that the value is correct.
-}
bytes :: ByteString
bytes =
unrefine emailBytes
また、これはリファインメントタイプがカバーできる範囲のほんの一部であることに注意してください。実際には、はるかに便利なプロパティがあります。
これは最近私のために答えられました。
これが 投稿 です。
あなたの質問の文脈は、有効な電子メールに関するものです。コードの全体的な構造は、アクティブパターンを活用します。
module File1 =
type EmailAddress =
private
| Valid of string
| Invalid of string
let createEmailAddress (address:System.String) =
if address.Length > 0
then Valid address
else Invalid address
// Exposed patterns go here
let (|Valid|Invalid|) (input : EmailAddress) : Choice<string, string> =
match input with
| Valid str -> Valid str
| Invalid str -> Invalid str
module File2 =
open File1
let validEmail = Valid "" // Compiler error
let isValid = createEmailAddress "" // works
let result = // also works
match isValid with
| Valid x -> true
| _ -> false