Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Tame Cloud Complex with F# powered DSLs

491 views

Published on

The emergence of Cloud platforms has fundamentally changed the IT landscape. However, attempting to ride on this ever-expanding platform ecosystem wave has created a new set of challenges.

Join Yan Cui in this talk as he draws on his extensive experience with AWS over the last 7 years to illustrate, with real-world use examples, how you can use F# to build internal and external DSLs to tame the complexity from these cloud services.

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Tame Cloud Complex with F# powered DSLs

  1. 1. @theburningmonk tame |> (cloud <| complexity) |> with |> (fsharp >> powered >> DSLs)
  2. 2. image by nerovivo license : https://creativecommons.org/licenses/by-sa/2.0/
  3. 3. @theburningmonk Under-Abstraction
  4. 4. @theburningmonk Oversimplification
  5. 5. @theburningmonk Impedance Mismatch
  6. 6. @theburningmonk Amazon DynamoDB Amazon SimpleWorkflow Amazon CloudWatch CASE STUDY
  7. 7. @theburningmonk F# DSLs. Awesome!
  8. 8. @theburningmonk Amazon DynamoDB Amazon SimpleWorkflow Amazon CloudWatch CASE STUDY
  9. 9. @theburningmonk
  10. 10. @theburningmonk managed key-value store
  11. 11. @theburningmonk redundancy 9-9s guarantee
  12. 12. @theburningmonk great performance
  13. 13. @theburningmonk name your throughput
  14. 14. @theburningmonk
  15. 15. @theburningmonk can be changed on-the-fly
  16. 16. @theburningmonk
  17. 17. @theburningmonk infinitely scalable (but you still have to pay for it)
  18. 18. @theburningmonk
  19. 19. Hash Key Range Key
  20. 20. @theburningmonk Query: given a hash key filter on range key, or local secondary index
  21. 21. @theburningmonk
  22. 22. Hash Key Range Key Local Secondary Index Global Secondary Index
  23. 23. @theburningmonk Scan: FULL TABLE search (performance + cost concern)
  24. 24. @theburningmonk
  25. 25. Hash Key Range Key Local Secondary Index Global Secondary Index
  26. 26. Hash Key Range Key Local Secondary Index Who are the TOP 3 players in “Starship X” with a score of at least 1000? Global Secondary Index
  27. 27. @theburningmonk
  28. 28. @theburningmonk select GameTitle, UserId, TopScore from GameScores where GameTitle = “Starship X” and TopScore >= 1000 order desc limit 3 with (NoConsistentRead, Index(GameTitleIndex, true))
  29. 29. DynamoDB.SQL github.com/fsprojects/DynamoDb.SQL
  30. 30. @theburningmonk GOAL Disguise complexity
  31. 31. @theburningmonk GOAL SELECT UserId, TopScore FROM GameScore WHERE GameTitle CONTAINS “Zelda” ORDER DESC LIMIT 3 WITH (NoConsistentRead)
  32. 32. @theburningmonk Query AST Execution
  33. 33. F# & FParsec* *www.quanttec.com/fparsec External DSL via
  34. 34. @theburningmonk
  35. 35. @theburningmonk SELECT * FROM GameScore Abstract Syntax Tree (AST) FParsec
  36. 36. @theburningmonk SELECT * FROM GameScore keyword keyword * | attribute, attribute, … table name
  37. 37. @theburningmonk SELECT * FROM GameScore type Attributes = | Asterisk | Attributes of string[]
  38. 38. @theburningmonk SELECT * FROM GameScore type Query = { Attributes : Attributes Table : string }
  39. 39. @theburningmonk SELECT * FROM GameScore Parser for “SELECT” keyword pSelect
  40. 40. @theburningmonk SELECT * FROM GameScore pSelect let pSelect = skipStringCI "select"
  41. 41. @theburningmonk SELECT * FROM GameScore pSelect let pSelect = skipStringCI "select" matches the string “select” (Case Insensitive) and ignores it
  42. 42. @theburningmonk SELECT * FROM GameScore pFrom let pFrom = skipStringCI "from"
  43. 43. @theburningmonk SELECT * FROM GameScore Parser for a string that represents the table name pTableName
  44. 44. @theburningmonk SELECT * FROM GameScore pTableName let isTableName = isLetter <||> isDigit let pTableName = many1Satisfy isTableName
  45. 45. @theburningmonk SELECT * FROM GameScore pTableName let isTableName = isLetter <||> isDigit let pTableName = many1Satisfy isTableName
  46. 46. @theburningmonk SELECT * FROM GameScore pTableName let isTableName = isLetter <||> isDigit let pTableName = many1Satisfy isTableName
  47. 47. @theburningmonk SELECT * FROM GameScore pTableName let isTableName = isLetter <||> isDigit let pTableName = many1Satisfy isTableName
  48. 48. @theburningmonk SELECT * FROM GameScore pTableName let isTableName = isLetter <||> isDigit let pTableName = many1Satisfy isTableName parses a sequence of one or more chars that satisfies the predicate function
  49. 49. @theburningmonk SELECT * FROM GameScore pAsterisk * pAttributeName UserId, GameTitle, TopScore, …
  50. 50. @theburningmonk SELECT * FROM GameScore pAsterisk * pAttributeName UserId, GameTitle, TopScore, … let pAsterisk = stringCIReturn "*" Asterisk
  51. 51. @theburningmonk SELECT * FROM GameScore pAsterisk * pAttributeName UserId, GameTitle, TopScore, … let pAsterisk = stringCIReturn "*" Asterisk matches the specified string and return the given value
  52. 52. @theburningmonk SELECT * FROM GameScore pAsterisk * pAttributeName UserId, GameTitle, TopScore, … let isAttributeName = isLetter <||> isDigit let pAttributeName = many1Satisfy isAttributeName
  53. 53. @theburningmonk SELECT * FROM GameScore UserId, GameTitle, TopScore, … pAttributeName pCommapAsterisk * let pComma = skipStringCI ","
  54. 54. @theburningmonk SELECT * FROM GameScore UserId, GameTitle, TopScore, … pAttributeName pCommapAsterisk * let pAttributeNames = sepBy1 pAttributeName pComma
  55. 55. @theburningmonk SELECT * FROM GameScore UserId, GameTitle, TopScore, … pAttributeName pCommapAsterisk * let pAttributeNames = sepBy1 pAttributeName pComma parses one or more occurrences of pAttributeName separated by pComma
  56. 56. @theburningmonk SELECT * FROM GameScore UserId, GameTitle, TopScore, … pAttributeName pComma sepBy1 pAttributeNames pAsterisk *
  57. 57. @theburningmonk SELECT * FROM GameScore pAsterisk * pAttributeNames UserId, GameTitle, TopScore, …
  58. 58. @theburningmonk SELECT * FROM GameScore pAsterisk * pAttributeNames UserId, GameTitle, TopScore, … let pAttribute = pAsterisk <|> pAttributeNames
  59. 59. @theburningmonk SELECT * FROM GameScore pAsterisk * pAttributeNames UserId, GameTitle, TopScore, … let pAttribute = pAsterisk <|> pAttributeNames
  60. 60. @theburningmonk SELECT * FROM GameScore pAsterisk * pAttributeNames UserId, GameTitle, TopScore, … choice pAttribute
  61. 61. @theburningmonk SELECT * FROM GameScore pAttribute
  62. 62. @theburningmonk SELECT * FROM GameScore pAttribute pTableNamepFrompSelect
  63. 63. @theburningmonk SELECT * FROM GameScore pAttribute pTableNamepFrompSelect let pQuery = tuple4 pSelect pAttribute pFrom pTableName |>> (fun (_, attributes, _, table) -> { Attributes = attributes Table = table })
  64. 64. @theburningmonk SELECT * FROM GameScore pAttribute pTableNamepFrompSelect let pQuery = tuple4 pSelect pAttribute pFrom pTableName |>> (fun (_, attributes, _, table) -> { Attributes = attributes Table = table })
  65. 65. @theburningmonk SELECT * FROM GameScore pAttribute pTableNamepFrompSelect let pQuery = tuple4 pSelect pAttribute pFrom pTableName |>> (fun (_, attributes, _, table) -> { Attributes = attributes Table = table })
  66. 66. @theburningmonk SELECT * FROM GameScore pAttribute pTableNamepFrompSelect let pQuery = tuple4 pSelect pAttribute pFrom pTableName |>> (fun (_, attributes, _, table) -> { Attributes = attributes Table = table }) Query
  67. 67. @theburningmonk SELECT * FROM GameScore pAttribute pTableNamepFrompSelect tuple4 pQuery
  68. 68. @theburningmonk
  69. 69. @theburningmonk < 50 lines of code
  70. 70. @theburningmonk Amazing F# + FParsec =
  71. 71. @theburningmonk Recap
  72. 72. @theburningmonk
  73. 73. @theburningmonk select GameTitle, UserId, TopScore from GameScores where GameTitle = “Starship X” and TopScore >= 1000 order desc limit 3 with (NoConsistentRead, Index(GameTitleIndex, true))
  74. 74. @theburningmonk Amazon DynamoDB Amazon SimpleWorkflow Amazon CloudWatch CASE STUDY
  75. 75. @theburningmonk
  76. 76. @theburningmonk Decision Worker
  77. 77. @theburningmonk Decision Worker Poll
  78. 78. @theburningmonk Decision Worker Decision Task
  79. 79. @theburningmonk Decision WorkerDecide
  80. 80. @theburningmonk Activity Worker Decision Worker
  81. 81. @theburningmonk Activity Worker Decision Worker Poll
  82. 82. @theburningmonk Activity Worker Decision Worker Activity Task
  83. 83. @theburningmonk Activity Worker Decision Worker Complete
  84. 84. @theburningmonk Workers can run from anywhere
  85. 85. @theburningmonk
  86. 86. @theburningmonk input = “Yan” result = “Hello Yan!” Start Finish Activity
  87. 87. @theburningmonk
  88. 88. image by Ryan Hageman license : https://creativecommons.org/licenses/by-sa/2.0/
  89. 89. SWF-based Application
  90. 90. API
  91. 91. Heartbeats Error Handling Polling API
  92. 92. Activity Worker Decision Worker Heartbeats Error Handling Polling API
  93. 93. Activity Worker Decision Worker Heartbeats Error Handling Polling API Boilerplate
  94. 94. – Kris Jordan “Good simplicity is less with leverage, not less with less. Good simplicity is complexity disguised, not complexity denied.”
  95. 95. http://bit.ly/1pOLeKl
  96. 96. Start Finish Activity ?
  97. 97. @theburningmonk the workflow is implied by decision worker logic…
  98. 98. @theburningmonk instead..
  99. 99. @theburningmonk the workflow should drive decision worker logic
  100. 100. @theburningmonk the workflow should driveautomate decision worker logic
  101. 101. Amazon .SimpleWorkflow .Extensions github.com/fsprojects/Amazon.SimpleWorkflow.Extensions
  102. 102. @theburningmonk GOAL Remove boilerplates
  103. 103. @theburningmonk GOAL Code that matches the way you think
  104. 104. @theburningmonk Start Finish Activity
  105. 105. Start Finish Activity
  106. 106. @theburningmonk Workflows can be nested
  107. 107. input result
  108. 108. @theburningmonk Recap
  109. 109. Activity Worker Decision Worker Heartbeats Error Handling Polling API
  110. 110. @theburningmonk Amazon DynamoDB Amazon SimpleWorkflow Amazon CloudWatch CASE STUDY
  111. 111. @theburningmonk
  112. 112. @theburningmonk
  113. 113. @theburningmonk
  114. 114. @theburningmonk wanna find correlations?
  115. 115. @theburningmonk wanna find correlations? you can DIY it! ;-)
  116. 116. @theburningmonk “what latencies spiked at the same time as payment service?”
  117. 117. Amazon .CloudWatch .Selector github.com/fsprojects/Amazon.CloudWatch.Selector
  118. 118. @theburningmonk Find metrics whose 5 min average exceeded 1 second during last 12 hours
  119. 119. @theburningmonk cloudWatch.Select( unitIs “milliseconds” + average (>) 1000.0 @ last 12 hours |> intervalOf 5 minutes)
  120. 120. @theburningmonk cloudWatch.Select(“ unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes”)
  121. 121. @theburningmonk “did any cache nodes’ CPU spike yesterday?”
  122. 122. @theburningmonk cloudWatch.Select( namespaceLike “elasticache” + nameLike “cpu” + max (>) 80.0 @ last 24 hours |> intervalOf 15 minutes)
  123. 123. @theburningmonk cloudWatch.Select( namespaceLike “elasticache” + nameLike “cpu” + max (>) 80.0 @ last 24 hours |> intervalOf 15 minutes) Regex
  124. 124. @theburningmonk
  125. 125. @theburningmonk
  126. 126. @theburningmonk
  127. 127. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes
  128. 128. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes Filters
  129. 129. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes TimeFrame
  130. 130. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes Period
  131. 131. @theburningmonk type Query = { Filter : Filter TimeFrame : TimeFrame Period : Period option }
  132. 132. @theburningmonk Query Internal DSL External DSL
  133. 133. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes
  134. 134. @theburningmonk type MetricTerm = Namespace | Name type Filter = | MetricFilter of MetricTerm * (string -> bool)
  135. 135. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes
  136. 136. @theburningmonk type MetricTerm = Namespace | Name type Unit = | Unit type Filter = | MetricFilter of MetricTerm * (string -> bool) | UnitFilter of Unit * (string -> bool)
  137. 137. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes
  138. 138. @theburningmonk type MetricTerm = Namespace | Name type Unit = | Unit type StatsTerm = | Average | Min | Max | Sum | SampleCount type Filter = | MetricFilter of MetricTerm * (string -> bool) | UnitFilter of Unit * (string -> bool) | StatsFilter of StatsTerm * (float -> bool)
  139. 139. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes
  140. 140. @theburningmonk type MetricTerm = Namespace | Name type Unit = | Unit type StatsTerm = | Average | Min | Max | Sum | SampleCount type Filter = | MetricFilter of MetricTerm * (string -> bool) | UnitFilter of Unit * (string -> bool) | StatsFilter of StatsTerm * (float -> bool) | CompositeFilter of Filter * Filter
  141. 141. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes
  142. 142. @theburningmonk type TimeFrame = | Last of TimeSpan | Since of DateTime | Between of DateTime * DateTime
  143. 143. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes
  144. 144. @theburningmonk type Period = | Period of TimeSpan
  145. 145. @theburningmonk type Query = { Filter : Filter TimeFrame : TimeFrame Period : Period option }
  146. 146. @theburningmonk Active Patterns a primer on
  147. 147. @theburningmonk allow patterns to be abstracted away into named functions
  148. 148. @theburningmonk Single-Case Patterns
  149. 149. @theburningmonk let (|Float|) input = match Double.TryParse input with | true, n -> n | _ -> failwithf “not a float [%s]” input
  150. 150. @theburningmonk let (|Float|) input = match Double.TryParse input with | true, n -> n | _ -> failwithf “not a float [%s]” input
  151. 151. @theburningmonk let (|Float|) input = match Double.TryParse input with | true, n -> n | _ -> failwithf “not a float [%s]” input Float : string -> float
  152. 152. @theburningmonk match someString with | Float 42.0 -> “ftw” | Float 11.0 -> “palprime” | Float x -> sprintf “just %f” x
  153. 153. @theburningmonk let (|Float|) input = match Double.TryParse input with | true, n -> n match someString with | Float 42.0 -> “ftw” | Float 11.0 -> “palprime” | Float x -> sprintf “just %f” x
  154. 154. @theburningmonk let (|Float|) input = match Double.TryParse input with | true, n -> n match someString with | Float 42.0 -> “ftw” | Float 11.0 -> “palprime” | Float x -> sprintf “just %f” x
  155. 155. @theburningmonk match someString with | Float 42.0 -> “ftw” | Float 11.0 -> “palprime” | Float x -> sprintf “just %f” x let (|Float|) input = match Double.TryParse input with | true, n -> n
  156. 156. @theburningmonk match someString with | Float 42.0 -> “ftw” | Float 11.0 -> “palprime” | Float x -> sprintf “just %f” x let (|Float|) input = match Double.TryParse input with | true, n -> n
  157. 157. @theburningmonk match “42” with | Float 42.0 -> “ftw” | Float 11.0 -> “palprime” | Float x -> sprintf “just %f” x
  158. 158. @theburningmonk match “boo” with | Float 42.0 -> “ftw” | Float 11.0 -> “palprime” | Float x -> sprintf “just %f” x Error!!!
  159. 159. @theburningmonk Partial Patterns
  160. 160. @theburningmonk let (|Float|_|) input = match Double.TryParse input with | true, n -> Some n | _ -> None
  161. 161. @theburningmonk let (|Float|_|) input = match Double.TryParse input with | true, n -> Some n | _ -> None Float : string -> float option
  162. 162. @theburningmonk match “boo” with | Float 42.0 -> “ftw” | Float 11.0 -> “palprime” | Float x -> sprintf “just %f” x | _ -> “not a float”
  163. 163. @theburningmonk match “boo” with | Float 42.0 -> “ftw” | Float 11.0 -> “palprime” | Float x -> sprintf “just %f” x | _ -> “not a float”
  164. 164. @theburningmonk Multi-Case Patterns
  165. 165. @theburningmonk let (|Prime|NotPrime|NaN|) input = match Double.TryParse input with | true, n when isPrime n -> Prime n | true, n -> NotPrime n | _ -> NaN
  166. 166. @theburningmonk let (|Prime|NotPrime|NaN|) input = match Double.TryParse input with | true, n when isPrime n -> Prime n | true, n -> NotPrime n | _ -> NaN
  167. 167. @theburningmonk let (|Prime|NotPrime|NaN|) input = match Double.TryParse input with | true, n when isPrime n -> Prime n | true, n -> NotPrime n | _ -> NaN
  168. 168. @theburningmonk let (|Prime|NotPrime|NaN|) input = match Double.TryParse input with | true, n when isPrime n -> Prime n | true, n -> NotPrime n | _ -> NaN
  169. 169. @theburningmonk let (|Prime|NotPrime|NaN|) input = match Double.TryParse input with | true, n when isPrime n -> Prime n | true, n -> NotPrime n | _ -> NaN
  170. 170. @theburningmonk match someString with | Prime n -> … | NotPrime n -> … | NaN -> …
  171. 171. @theburningmonk match someString with | Prime n & Float 11.0 -> … | Prime n -> … | Float 42.0 | Float 8.0 -> … | NotPrime n -> … | NaN -> …
  172. 172. @theburningmonk match someString with | Prime n & Float 11.0 -> … | Prime n -> … | Float 42.0 | Float 8.0 -> … | NotPrime n -> … | NaN -> …
  173. 173. @theburningmonk match someString with | Prime (IsPalindrome) -> … | Prime _ -> … | _ -> …
  174. 174. @theburningmonk Tokenise (string -> string list)
  175. 175. @theburningmonk [ “namespaceIs”; “‘JustEat’”; “and”; “nameLike”; “‘cpu’”; “and”; … ]
  176. 176. @theburningmonk “namespaceIs” “and” … F# List = LinkedList “‘JustEat’”
  177. 177. @theburningmonk “namespaceIs” “and” … F# List = LinkedList “namespaceIs” “‘JustEat’” “and”:::: :: tail cons (::) operator to prepend “‘JustEat’”
  178. 178. @theburningmonk “namespaceIs” “and” … F# List = LinkedList “namespaceIs” “‘JustEat’” “and”:::: :: tail match aList with | “‘JustEat’” works as a pattern too!
  179. 179. @theburningmonk let (|StringCI|_|) (str : string) (input : string) = if str.ToLower() = input.ToLower() then Some () else None let (|Float|_|) input = match System.Double.TryParse input with | true, n -> Some n | _ -> None
  180. 180. @theburningmonk let (|EmptyString|_|) input = if String.IsNullOrWhiteSpace input then Some () else None let (|StartsWith|_|) char (input : string) = if input.[0] = char then Some () else None let (|EndsWith|_|) char (input : string) = if input.[input.Length-1] = char then Some () else None
  181. 181. @theburningmonk let (|QuotedString|_|) (input : string) = match input with | EmptyString -> None | str when str.Length < 2 -> None | StartsWith ''' & EndsWith ''' -> Some <| input.Substring(1, input.Length - 2) | _ -> None
  182. 182. @theburningmonk let (|NamespaceIs|_|) = function | StringCI “NamespaceIs" :: QuotedString ns :: tl -> Some (eqFilter MetricFilter Namespace ns, tl) | _ -> None
  183. 183. @theburningmonk let (|NamespaceIs|_|) = function | StringCI “NamespaceIs" :: QuotedString ns :: tl -> Some (eqFilter MetricFilter Namespace ns, tl) | _ -> None namespaceIs ‘JustEat’ and nameLike ‘cpu’ and …
  184. 184. @theburningmonk let (|NamespaceIs|_|) = function | StringCI “NamespaceIs" :: QuotedString ns :: tl -> Some (eqFilter MetricFilter Namespace ns, tl) | _ -> None “namespaceIs” “‘JustEat’” :::: …
  185. 185. @theburningmonk let (|NamespaceIs|_|) = function | StringCI “NamespaceIs" :: QuotedString ns :: tl -> Some (eqFilter MetricFilter Namespace ns, tl) | _ -> None type MetricTerm = Namespace | Name type Filter = | MetricFilter of MetricTerm * (string -> bool)
  186. 186. @theburningmonk let (|NamespaceIs|_|) = function | StringCI “NamespaceIs" :: QuotedString ns :: tl -> Some (eqFilter MetricFilter Namespace ns, tl) | _ -> None “and” :: “nameLike” :: “‘cpu’” :: “and” :: …
  187. 187. @theburningmonk let rec loop acc = function | NamespaceIs (filter, tl) | NamespaceLike (filter, tl) | NameIs (filter, tl) | NameLike (filter, tl) | UnitIs (filter, tl) | Average (filter, tl) | Sum (filter, tl) | Min (filter, tl) | Max (filter, tl) | SampleCount (filter, tl) -> match tl with | And tl -> loop (filter::acc) tl | _ -> flatten (filter::acc), tl | _ -> failwith “No filters?!?!?”
  188. 188. @theburningmonk let rec loop acc = function | NamespaceIs (filter, tl) | NamespaceLike (filter, tl) | NameIs (filter, tl) | NameLike (filter, tl) | UnitIs (filter, tl) | Average (filter, tl) | Sum (filter, tl) | Min (filter, tl) | Max (filter, tl) | SampleCount (filter, tl) -> match tl with | And tl -> loop (filter::acc) tl | _ -> flatten (filter::acc), tl | _ -> failwith “No filters?!?!?”
  189. 189. @theburningmonk let rec loop acc = function | NamespaceIs (filter, tl) | NamespaceLike (filter, tl) | NameIs (filter, tl) | NameLike (filter, tl) | UnitIs (filter, tl) | Average (filter, tl) | Sum (filter, tl) | Min (filter, tl) | Max (filter, tl) | SampleCount (filter, tl) -> match tl with | And tl -> loop (filter::acc) tl | _ -> flatten (filter::acc), tl | _ -> failwith “No filters?!?!?”
  190. 190. @theburningmonk let rec loop acc = function | NamespaceIs (filter, tl) | NamespaceLike (filter, tl) | NameIs (filter, tl) | NameLike (filter, tl) | UnitIs (filter, tl) | Average (filter, tl) | Sum (filter, tl) | Min (filter, tl) | Max (filter, tl) | SampleCount (filter, tl) -> match tl with | And tl -> loop (filter::acc) tl | _ -> flatten (filter::acc), tl | _ -> failwith “No filters?!?!?”
  191. 191. @theburningmonk let rec loop acc = function | NamespaceIs (filter, tl) | NamespaceLike (filter, tl) | NameIs (filter, tl) | NameLike (filter, tl) | UnitIs (filter, tl) | Average (filter, tl) | Sum (filter, tl) | Min (filter, tl) | Max (filter, tl) | SampleCount (filter, tl) -> match tl with | And tl -> loop (filter::acc) tl | _ -> flatten (filter::acc), tl | _ -> failwith “No filters?!?!?”
  192. 192. @theburningmonk let parse (input : string) = input |> tokenize |> parseFilter |> parseTimeFrame |> parsePeriod
  193. 193. @theburningmonk Amazing F# =
  194. 194. @theburningmonk usable from anywhere you can run F# code e.g. F# REPL, executable, .. Internal DSL
  195. 195. @theburningmonk useful for building tools e.g. CLI, … External DSL
  196. 196. @theburningmonk
  197. 197. @theburningmonk
  198. 198. @theburningmonk
  199. 199. @theburningmonk Recap
  200. 200. @theburningmonk
  201. 201. @theburningmonk
  202. 202. @theburningmonk Amazon DynamoDB Amazon SimpleWorkflow Amazon CloudWatch CASE STUDY
  203. 203. @theburningmonk theburningmonk.com github.com/theburningmonk

×