Skip to content

Instantly share code, notes, and snippets.

@jackfoxy
Created September 18, 2020 21:20
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jackfoxy/10185bbc33e9b388d8bbb0d7ca73832a to your computer and use it in GitHub Desktop.
Save jackfoxy/10185bbc33e9b388d8bbb0d7ca73832a to your computer and use it in GitHub Desktop.
A demo of decision tables using F# types and pattern matching
(*
Decision Table Demo
MIT License, do with this what you will
A demo of decision tables using F# types and pattern matching, inspired by https://www.hillelwayne.com/post/decision-table-patterns/
The compiler service flags problematic (incomplete) decision tables.
Requirements:
Some F# development environment using F# Compiler Services, https://fsharp.github.io/FSharp.Compiler.Service/, in order to generate intellisense help regarding the completeness of pattern matches.
Visual Studio Community Edition, https://visualstudio.microsoft.com/vs/community/
Rider, https://www.jetbrains.com/rider/
VS Code with Ionide plugin, http://ionide.io/
VIM, https://github.com/ionide/Ionide-vim
Developed prior to F# 5.0 ... shouldn't make much difference.
*)
module Domain =
type Action =
| Merge
| FixTests
| FixReviewComments
| FixTestsFirst
type PR =
{
PassedReview : bool
PassedTests : bool
}
type InfiniteState = int
type DoSomething =
| DoThis
| DoThat
open Domain
let decisionTable1 (pr : PR) =
(*
A complete decision table, no warnings.
*)
match pr with
| { PassedReview = true; PassedTests = true } -> Action.Merge
| { PassedReview = true; PassedTests = false } -> Action.FixTests
| { PassedReview = false; PassedTests = true } -> Action.FixReviewComments
| { PassedReview = false; PassedTests = false } -> Action.FixTestsFirst
let decisionTable1Alternate1 passedReview passedTests =
(*
...or if you prefer your patterns less verbose
*)
match passedReview, passedTests with
| true, true -> Action.Merge
| true, false -> Action.FixTests
| false, true -> Action.FixReviewComments
| false, false -> Action.FixTestsFirst
let decisionTable1Alternate2 passedReview passedTests =
(*
Multiple patterns result in same decision.
*)
match passedReview, passedTests with
| true, true -> Action.Merge
| false, true -> Action.FixReviewComments
| true, false
| false, false -> Action.FixTests
let decisionTable1Warning1 (pr : PR) =
(*
Note green squiggly under the pr in match statement indicates a warning.
Mouse-over to read message
FS00025: Incomplete pattern matches on this expression. For example, the value
'{PassedReview=_;PassedTests=false}' may indicate a case not covered by the pattern(s).
Comment: notice the generated example is not quite correct in that 'PassedReview' is assigned the wild card.
*)
match pr with
| { PassedReview = true; PassedTests = true } -> Action.Merge
| { PassedReview = true; PassedTests = false } -> Action.FixTests
| { PassedReview = false; PassedTests = true } -> Action.FixReviewComments
let decisionTable1Warning2 (pr : PR) =
(*
This time the green squiggly is under our last pattern.
FS00026: This rule will never be matched.
We included the same pattern a second time and the compiler warns us.
*)
match pr with
| { PassedReview = true; PassedTests = true } -> Action.Merge
| { PassedReview = true; PassedTests = false } -> Action.FixTests
| { PassedReview = false; PassedTests = true } -> Action.FixReviewComments
| { PassedReview = true; PassedTests = false } -> Action.FixTestsFirst
let decisionTable2 (state : InfiniteState) =
(*
When using guards in the pattern, the compiler is not smart enough.
FS00025: Incomplete pattern matches on this expression.
*)
match state with
| s when s % 2 = 0 -> DoSomething.DoThis
| s when s % 2 <> 0 -> DoSomething.DoThat
(*
We get around this limitation using F# Active Patterns, https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/active-patterns
We could just as easily define up to 7 ranges.
*)
let (|Even|Odd|) n =
if n % 2 = 0 then
Even
else
Odd
let decisionTable2Better (state : InfiniteState) =
(*
No warnings.
*)
match state with
| Even -> DoSomething.DoThis
| Odd -> DoSomething.DoThat
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment