動機
業務系システムによくある(要出典)"0001"のような、先頭を0で埋めた数字の固定長の文字列でなければならないという制約を表現したい。
ここでは、
- "12"のように先頭の0を省略した入力はOK("0012"を生成)
- "12345"のように、元から最終的な桁数を超えている場合はNG
- 空文字列や数字以外が含まれる場合は当然NG
としたい。
コード
ZeroPadded4Sample.fs
open System
let ifTrueThen f =
function
| true -> Some f
| false -> None
let ifFalseThen f =
function
| true -> None
| false -> Some f
let (|NullOrWhiteSpace|_|) =
String.IsNullOrWhiteSpace >> ifTrueThen NullOrWhiteSpace
let (|LengthOver|_|) l s =
(String.length s) > l |> ifTrueThen LengthOver
let (|NotInAsciiDigit|_|) (s: string) =
s.ToCharArray() |> Array.forall Char.IsAsciiDigit |> ifFalseThen NotInAsciiDigit
module ZeroPadded4 =
type T = ZeroPadded4 of string
let totalWidth = 4
let create =
function
| NullOrWhiteSpace
| LengthOver totalWidth
| NotInAsciiDigit -> None
| s -> s.PadLeft(totalWidth, '0') |> ZeroPadded4 |> Some
let inputs = [ "12"; "0"; String.Empty; " "; "a12"; "1234"; "12345" ]
let results = inputs |> List.map ZeroPadded4.create
(inputs, results) ||> List.iter2 (fun i r -> printfn "%A -> %A" i r)
(*
"12" -> Some (ZeroPadded4 "0012")
"0" -> Some (ZeroPadded4 "0000")
"" -> None
" " -> None
"a12" -> None
"1234" -> Some (ZeroPadded4 "1234")
"12345" -> None
*)
解説
冒頭のifTrueThen
とifFalseThen
は、後続のパーシャルアクティブパターンの定義で楽をするための補助関数です。
let (|NullOrWhiteSpace|_|) =
String.IsNullOrWhiteSpace >> ifTrueThen NullOrWhiteSpace
のような、アクティブパターンたちは、文字列を各バリデーションの観点で分類するもの...といったところになります。「空っぽはダメ」のような制約は、頻出しそうなので、部品として切り出しておこうといった感じですね。
例えば、「ちょうどN文字でなければならない」という制約が発生した場合は、
let (|LengthNotEqual|_|) l s =
(String.length s) <> l |> ifTrueThen LengthNotEqual
のようなコードを追加して利用すると良いわけです。
module ZeroPadded4 =
type T = ZeroPadded4 of string
let totalWidth = 4
let create =
function
| NullOrWhiteSpace
| LengthOver totalWidth
| NotInAsciiDigit -> None
| s -> s.PadLeft(totalWidth, '0') |> ZeroPadded4 |> Some
アクティブパターンたちを利用して、制約を満たした場合のみ値を生成します。
この例ではOptionを返しているため、生成できない場合は一律Noneになりますが、エラー内容を具体的に表現したい場合は、Resultを返すなどすると良さそうです。
感想など
アクティブパターンのバナナクリップ記号(| ... |)
を初めて見たときには、「なんじゃこりゃ?」となりましたが、具体的な使い方を見出せてすっきりしました。
こうやって、小さな部品を作って組み立てていくような書き方ができると、F#がより楽しく感じます。
参考にしたサイト