forked from swlaschin/13-ways-of-looking-at-a-turtle
-
Notifications
You must be signed in to change notification settings - Fork 0
/
07-DependencyInjection_Functions-2.fsx
169 lines (131 loc) · 5.24 KB
/
07-DependencyInjection_Functions-2.fsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
(* ======================================
07-DependencyInjection_Functions-2.fsx
Part of "Thirteen ways of looking at a turtle"
Related blog post: http://fsharpforfunandprofit.com/posts/13-ways-of-looking-at-a-turtle/
======================================
Way #7: Dependency injection using functions (v2: pass in a single function)
In this design, an API layer communicates via one or more functions that are passed in as parameters to the API call.
These functions are typically partially applied so that the call site is decoupled from the "injection"
No interface is passed to the constructor.
====================================== *)
#load "Common.fsx"
#load "FPTurtleLib.fsx"
#load "TurtleApiHelpers.fsx"
open System
open Common
open FPTurtleLib
open TurtleApiHelpers // helpers for API validation, etc
// ======================================
// Turtle Api -- Pass in a single function
// ======================================
module TurtleApi_PassInSingleFunction =
open Result
type TurtleCommand =
| Move of Distance
| Turn of Angle
| PenUp
| PenDown
| SetColor of PenColor
// No functions in constructor
type TurtleApi() =
let mutable state = Turtle.initialTurtleState
/// Update the mutable state value
let updateState newState =
state <- newState
/// Execute the command string, and return a Result
/// Exec : commandStr:string -> Result<unit,ErrorMessage>
member this.Exec turtleFn (commandStr:string) =
let tokens = commandStr.Split(' ') |> List.ofArray |> List.map trimString
// return Success of unit, or Failure
match tokens with
| [ "Move"; distanceStr ] -> result {
let! distance = validateDistance distanceStr
let command = Move distance // create a Command object
let newState = turtleFn command state
updateState newState
}
| [ "Turn"; angleStr ] -> result {
let! angle = validateAngle angleStr
let command = Turn angle // create a Command object
let newState = turtleFn command state
updateState newState
}
| [ "Pen"; "Up" ] -> result {
let command = PenUp
let newState = turtleFn command state
updateState newState
}
| [ "Pen"; "Down" ] -> result {
let command = PenDown
let newState = turtleFn command state
updateState newState
}
| [ "SetColor"; colorStr ] -> result {
let! color = validateColor colorStr
let command = SetColor color
let newState = turtleFn command state
updateState newState
}
| _ ->
Failure (InvalidCommand commandStr)
// -----------------------------
// Turtle Implementations for "Pass in a single function" design
// -----------------------------
module TurtleImplementation_PassInSingleFunction =
open FPTurtleLib
open TurtleApi_PassInSingleFunction
let log = printfn "%s"
let move = Turtle.move log
let turn = Turtle.turn log
let penUp = Turtle.penUp log
let penDown = Turtle.penDown log
let setColor = Turtle.setColor log
let normalSize() =
let turtleFn = function
| Move dist -> move dist
| Turn angle -> turn angle
| PenUp -> penUp
| PenDown -> penDown
| SetColor color -> setColor color
// partially apply the function to the API
let api = TurtleApi()
api.Exec turtleFn
// the return value is a function:
// string -> Result<unit,ErrorMessage>
let halfSize() =
let turtleFn = function
| Move dist -> move (dist/2.0)
| Turn angle -> turn angle
| PenUp -> penUp
| PenDown -> penDown
| SetColor color -> setColor color
// partially apply the function to the API
let api = TurtleApi()
api.Exec turtleFn
// the return value is a function:
// string -> Result<unit,ErrorMessage>
// -----------------------------
// Turtle API Client for "Pass in a single function" design
// -----------------------------
module TurtleApiClient_PassInSingleFunction =
open Result
// the API type is just a function
type ApiFunction = string -> Result<unit,ErrorMessage>
let drawTriangle(api:ApiFunction) =
result {
do! api "Move 100"
do! api "Turn 120"
do! api "Move 100"
do! api "Turn 120"
do! api "Move 100"
do! api "Turn 120"
} |> ignore
// -----------------------------
// Turtle Api Tests for "Pass in a single function" design
// -----------------------------
do
let api = TurtleImplementation_PassInSingleFunction.normalSize()
TurtleApiClient_PassInSingleFunction.drawTriangle(api)
do
let api = TurtleImplementation_PassInSingleFunction.halfSize()
TurtleApiClient_PassInSingleFunction.drawTriangle(api)