newtypeとsmart constructorのmodule
以下の組み合わせのmoduleを設計する
型は公開
値コンストラクタは非公開
公開
unwrapする関数
公開
Password.hsmodule Password
( Password -- ←これは型
, unPassword
, mkPassword
) where
import Data.ByteString (ByteString)
import qualified Data.ByteString as ByteString
newtype Password = Password ByteString
unPassword :: Password -> ByteString
unPassword (Password password) = password
mkPassword :: ByteString -> Maybe Password
mkPassword pwd
| ByteString.null pwd = Nothing
| otherwise = Just (Password pwd)
Password
の値constructorは公開せずに、 mkPassword
のみを公開している
Password型を生成するために、 mkPassword
を使うように強制できる
mkPassword
では、validationを行い、不正なデータでPasswordを定義させない
ここでは空文字のPasswordを指定させないようにしている
Password
の値consturctorを公開していないので、値を取り出すために unPassword
関数が必要
「Usernameは空文字ではいけない」という仕様があって、それをどう表現するか
A.hsnewtype Username = Username String
mkUsername :: String -> Maybe Username
mkUsername "" = Nothing
mkUsername s = Just (Username s)
B.hsnewtype Username = Username NonEmptyString
Aのほうは、Stringでnewtypeして、空文字validationするために、smart constructorを使っている
Bの方は、そもそも型レベルで空文字を受け付けない
今後の仕様の変わりようも考えると、Aの方が柔軟な気もする
validationに「記号から始まってはいけない」とか「3文字以上」とかの要件が加わる可能性はある
Cとして、NonEmptySringにsmart consturctorを使う、というのもあり得る
これが良いと思う

mkする場所やunwrapする場所は、外部との境界値になる
unwrapさせない工夫
unwrapするのではなく、unwrapしたものに適用する関数を渡すようにする
こうではなく
before.fsaddress |> EmailAddress.value |> printfn "the value is %s"
こうする
after.fsaddress |> EmailAddress.apply (printfn "the value is %s")
こうすることで、unwrapされた値を持ち回ることを防ぐことができる
unwrapした値を取り出せないので
でも
id
を適用すれば取り出せるか

簡潔でわかりやすい
ここでは、返り値を Either ソレ String
にしようというふうに書かれている
smart constructorの話ではないけど
String50
と String100
などを個別に作りつつも互換性をもたせる方法