タイプのセキュリティとユーザーフレンドリーなRegex Builderの作成
受領番号
関数プログラミングの核心の一つは、記述と評価を分離することである.descriptionと評価の分離はlazy評価によって実現することができ、lazy評価はその名の通りdescriptionに対する評価を実行可能な最後のステップに推し進めることである.このlazy評価,dscriptおよび評価分離の概念によりscalaでよく用いられる種々のモードで発見できる.の一級関数について、この関数がどのように実行されるかについて説明したが、実際の関数の実行はパラメータを渡すときに実行される. 関数の実行時にエラーが発生した場合は,直ちにエラーを投げ出すことなく,Ether[E,A]に値を入れ,その値の評価を呼び出し者に依頼する. 例えば、 Streamは、すぐに値を投げ出すことなく、必要に応じてデータを発行するだけで、不要な演算を最大限に減らすことができます.
ADTと解釈器を用いた領域モデリングもまた,記述と評価を分離する設計モードの1つと見なすことができる.ADTはalgebric data typeの略であり,我々が興味を持っているドメインをデータ構造として抽象化しているといえる.ADTは単純なデータフォーマットであり、論理を含まないため、ADTだけでは完了したアプリケーションを作成できません.
ADTに真の意味を持たせるためには、ADTを解釈し、実際に必要な論理を実行する解釈器が必要である.
またADTは関数ではなくデータなので,それを用いてプログラムの記述を書くのは想像以上に見慣れず不便である.したがって、ADTのDSLを簡単に作成して処理することができる.
ADT、Interpreter、およびDSLを使用するレルムモデリング技術の例には、SQL query builderが含まれる.
dslで使用可能な構文は、 ADTによって定義される. dslでqueryを記述します. およびこのqueryは、解釈器によって解析され、実際のDBでqueryが送信される. この方法の長所は、 DSLによる直感的かつ宣言的なプログラム記述 DSLは、その名の通りドメイン固有です.我々が解決すべき問題分野を最適化する言語であるため,dslを用いるとプログラムの動作を非常に直感的に「述べる」ことができる.DSLによってコアビジネスロジックを管理することができ、コードのメンテナンスとコラボレーションの観点から大きなメリットをもたらします.また、dslはADT定義の構文に基づいて作成されるため、typeセキュリティは失われません. ADTを解釈器から分離することによって、関心のある を分離する.
注目点の分離は,どの部分のコードを記述する際にどの部分に注目するかの認識においても利点があるが,解釈器具に外部依存性が多くあれば,それを明確に分離することでコードのメンテナンス性を向上させることができる.実際には、上記の例のSQL query builderでは、解釈器が特定のデータベースに依存している可能性がありますが、ADT自体に依存していないため、純粋な構文しか定義できません.
今回の実験では,scalaを用いてADT&Interpreter&DSL方式でRegex Builderを作成することを試みる.regexを使用するときに感じる不便は以下の通りです. regexは実際には単純な文字列であるため、->コンパイル時に をチェックすることはありません. regex文法は非常に混乱し、困難である.使うたびにグーグル化し直す必要がある 今回の実践で作成したRegex Builderには、次のような利点が期待できます. ADT定義された構文に従ってタイプセキュリティ を保証する. regexの実際の構文が分からなくても、dslで簡単にregex(ユーザーフレンドリー) を作成できます.
Regex ADTはdslの構文を定義します.scalaは、一般に、シール特性を用いてADTを記述する.
次に、上記で定義したADTを説明して、実際のscalaを得る.解析器を作成し、utilパッケージから提供されるRegexに変換します.
前述したように,ADTを用いてプログラムの記述を完了するだけでは厄介なことである.したがって、ADTの生成および操作が容易な関数からなるDSLを作成することができる.
ex)電話番号Regexの作成
Description vs Evaluation
関数プログラミングの核心の一つは、記述と評価を分離することである.descriptionと評価の分離はlazy評価によって実現することができ、lazy評価はその名の通りdescriptionに対する評価を実行可能な最後のステップに推し進めることである.このlazy評価,dscriptおよび評価分離の概念によりscalaでよく用いられる種々のモードで発見できる.
ADT & Interpreter & DSL
ADTと解釈器を用いた領域モデリングもまた,記述と評価を分離する設計モードの1つと見なすことができる.ADTはalgebric data typeの略であり,我々が興味を持っているドメインをデータ構造として抽象化しているといえる.ADTは単純なデータフォーマットであり、論理を含まないため、ADTだけでは完了したアプリケーションを作成できません.
ADTに真の意味を持たせるためには、ADTを解釈し、実際に必要な論理を実行する解釈器が必要である.
またADTは関数ではなくデータなので,それを用いてプログラムの記述を書くのは想像以上に見慣れず不便である.したがって、ADTのDSLを簡単に作成して処理することができる.
ADT、Interpreter、およびDSLを使用するレルムモデリング技術の例には、SQL query builderが含まれる.
dslで使用可能な構文は、
注目点の分離は,どの部分のコードを記述する際にどの部分に注目するかの認識においても利点があるが,解釈器具に外部依存性が多くあれば,それを明確に分離することでコードのメンテナンス性を向上させることができる.実際には、上記の例のSQL query builderでは、解釈器が特定のデータベースに依存している可能性がありますが、ADT自体に依存していないため、純粋な構文しか定義できません.
練習:Regex Builder
今回の実験では,scalaを用いてADT&Interpreter&DSL方式でRegex Builderを作成することを試みる.regexを使用するときに感じる不便は以下の通りです.
1. Regex ADT
Regex ADTはdslの構文を定義します.scalaは、一般に、シール特性を用いてADTを記述する.
sealed trait Regex
object Regex {
case object Empty extends Regex
case object AnyChar extends Regex // except newline
sealed trait CharSpecifier extends Regex
sealed trait Quantified extends Regex
sealed trait Anchored extends Regex
sealed trait Set extends Regex
case class Grouped(regex: Regex) extends Regex
case class Concatenated(regex1: Regex, regex2: Regex) extends Regex
case class Or(regex1: Regex, regex2: Regex) extends Regex
object CharSpecifier {
case class Literal(char: Char) extends CharSpecifier
case object Word extends CharSpecifier // alphabet & underscore
case object Digit extends CharSpecifier
case object WhiteSpace extends CharSpecifier
case object NotWord extends CharSpecifier
case object NotDigit extends CharSpecifier
case object NotWhiteSpace extends CharSpecifier
case object Tab extends CharSpecifier
case object NewLine extends CharSpecifier
case class InRangeOf(from: Char, to: Char) extends CharSpecifier
}
object Quantified {
case class QuantifiedRange(regex: Regex, min: Int, max: Int) extends Quantified
case class QuantifiedMin(regex: Regex, min: Int) extends Quantified
case class QuantifiedExact(regex: Regex, quantity: Int) extends Quantified
case class OneOrMore(regex: Regex) extends Quantified
case class ZeroOrMore(regex: Regex) extends Quantified
case class Optional(regex: Regex) extends Quantified
}
object Anchored {
case object WordBoundary extends Anchored
case object NotWordBoundary extends Anchored
case object Beginning extends Anchored
case object Ending extends Anchored
}
object Set {
case class AnyOf(candidates: Seq[CharSpecifier]) extends Set
case class NotAnyOf(candidates: Seq[CharSpecifier]) extends Set
}
}
2. Interpreter
次に、上記で定義したADTを説明して、実際のscalaを得る.解析器を作成し、utilパッケージから提供されるRegexに変換します.
Interpreter[From, To]
という一般的なtypeclassが作成され、Regex Interpreterにtypeclassが継承されます.trait Interpreter[-From, +To] {
def interpret(from: From): To
}
val regexInterpreter: Interpreter[Regex, ScalaRegex] = new Interpreter[Regex, ScalaRegex] {
override def interpret(from: Regex): ScalaRegex =
toString(from).r
private def toString(from: Regex): String = from match {
case Regex.Empty => ""
case Regex.AnyChar => "."
case specifier: Regex.CharSpecifier =>
specifier match {
case CharSpecifier.Literal(char) =>
if ("+*?^$\\.[]{}()|/".contains(char)) s"\\$char"
else char.toString
case CharSpecifier.Word => "\\w"
case CharSpecifier.Digit => "\\d"
case CharSpecifier.WhiteSpace => "\\s"
case CharSpecifier.NotWord => "\\W"
case CharSpecifier.NotDigit => "\\D"
case CharSpecifier.NotWhiteSpace => "\\S"
case CharSpecifier.Tab => "\\t"
case CharSpecifier.NewLine => "\\n"
case CharSpecifier.InRangeOf(from, to) => s"[$from-$to]"
}
case quantified: Regex.Quantified =>
quantified match {
case Quantified.QuantifiedRange(regex, min, max) => s"${toString(regex)}{$min,$max}"
case Quantified.QuantifiedMin(regex, min) => s"${toString(regex)}{$min,}"
case Quantified.QuantifiedExact(regex, quantity) => s"${toString(regex)}{$quantity}"
case Quantified.OneOrMore(regex) => s"${toString(regex)}+"
case Quantified.ZeroOrMore(regex) => s"${toString(regex)}*"
case Quantified.Optional(regex) => s"${toString(regex)}?"
}
case anchored: Regex.Anchored =>
anchored match {
case Anchored.WordBoundary => "\\b"
case Anchored.NotWordBoundary => "\\B"
case Anchored.Beginning => "$"
case Anchored.Ending => "^"
}
case set: Regex.Set =>
def concat(candidates: Seq[CharSpecifier]): String =
candidates.foldLeft("") { (acc, r) =>
val str = r match {
case CharSpecifier.InRangeOf(from, to) => s"$from-$to"
case charSpecifier => toString(charSpecifier)
}
s"$acc$str"
}
set match {
case Set.AnyOf(candidates) => s"[${concat(candidates)}]"
case Set.NotAnyOf(candidates) => s"[^${concat(candidates)}]"
}
case Regex.Grouped(regex) => s"(${toString(regex)})"
case Regex.Concatenated(regex1, regex2) => s"${toString(regex1)}${toString(regex2)}"
case Regex.Or(regex1, regex2) => s"(?:${toString(regex1)}|${toString(regex2)})"
}
}
ADTで作成したプログラムでは、次の解釈器を使用して外部世界に会います.val regex: Regex = ???
val interpreted: ScalaRegex = regexInterpreter.interpret(regex)
3. DSL
前述したように,ADTを用いてプログラムの記述を完了するだけでは厄介なことである.したがって、ADTの生成および操作が容易な関数からなるDSLを作成することができる.
trait RegexSyntax {
implicit def literal(char: Char): Regex = Literal(char)
implicit def literal(str: String): Regex = str.foldLeft(Empty: Regex) { (acc, r) => acc ++ Literal(r) }
implicit class RegexOps(val regex: Regex) {
def concatWith(regex2: Regex): Regex = Concatenated(regex, regex2)
def oneOrMore: Regex = OneOrMore(regex)
def optional: Regex = Optional(regex)
def zeroOrMore: Regex = ZeroOrMore(regex)
def quantified(min: Int, max: Int): Regex = Quantified.QuantifiedRange(regex, min, max)
def quantifiedMin(min: Int): Regex = Quantified.QuantifiedMin(regex, min)
def quantifiedExact(quantity: Int): Regex = Quantified.QuantifiedExact(regex, quantity)
def or(regex2: Regex): Regex = Or(regex, regex2)
def ++(regex2: Regex): Regex = Concatenated(regex, regex2)
}
implicit class CharRegexOps(val char: Char) {
def ~(char2: Char): Regex = InRangeOf(char, char2)
def l: Regex = Literal(char)
}
implicit class StringRegexOps(val str: String) {
def l: Regex = literal(str)
}
}
ADTを直接使用してregexを作成する方法と、DSLを使用してregexを作成する方法の違いは、次の方法で確認できます.ex)電話番号Regexの作成
// make regex using ADT directly
val koreanPhoneNumber1: Regex =
Concatenated(
Concatenated(
Concatenated(
Concatenated(
Or(
Concatenated(Concatenated(Literal('0'),Literal('1')),Literal('0')),
Concatenated(Concatenated(Literal('0'),Literal('1')),Literal('1'))
),
Literal('-')
),
QuantifiedExact(Digit,4)
),
Literal('-')
),
QuantifiedExact(Digit,4)
)
// make regex using DSL
val koreanPhoneNumber2: Regex =
("010".l or "011") ++ '-' ++
Digit.quantifiedExact(4) ++ '-' ++
Digit.quantifiedExact(4)
次の例では、ISO-8601 datetime formatを表すregexを作成します.宣言的なインターフェースにより,使いやすく直感的なDSLが形成されているようである.// year: 0000~9999
private val year: Regex = Digit.quantifiedExact(4)
// month: 01~12
private val month: Regex =
('0'.l ++ '1' ~ '9') or ('1'.l ++ '0' ~ '2')
// date: 01~31
private val date: Regex =
('0'.l ++ '1' ~ '9') or ('1' ~ '2' ++ Digit) or ('3'.l ++ '0' ~ '1')
// hour: 01~23
private val hour: Regex = ('0' ~ '1' ++ Digit) or ('2'.l ++ '0' ~ '3')
// minute: 00~59
private val minute: Regex = '0' ~ '5' ++ Digit
// second: 00~59
private val second: Regex = '0' ~ '5' ++ Digit
// iso8601: {YYYY}-{MM}-{DD}T{hh}:{mm}:{ss}Z
val iso8601Format: Regex =
year ++ '-' ++
month ++ '-' ++
date ++ 'T' ++
hour ++ ':' ++
minute ++ ':' ++
second ++ 'Z'
Reference
この問題について(タイプのセキュリティとユーザーフレンドリーなRegex Builderの作成), 我々は、より多くの情報をここで見つけました https://velog.io/@dvmflstm/type-safe-user-friendly한-Regex-Builder-만들기テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol