LoginSignup
0
0

ゼロ詰め4桁の文字列を表現したい

Posted at

動機

業務系システムによくある(要出典)"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
*)

解説

冒頭のifTrueThenifFalseThenは、後続のパーシャルアクティブパターンの定義で楽をするための補助関数です。

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#がより楽しく感じます。

参考にしたサイト

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0