Thursday, May 22, 2008
LanguageFear
Martin didn't quite specified why we need a DSL:
- Is it for business people to specify their requirements?
- Is it just 'readable' code, for programmers ?
I assume that in both cases we are trying to solve a customer's problem, by building an easy to maintain.
Let's say we use XML. Well that is a bad idea, and Martin knows it: XML is not the answer. It is not even the question. And to write XML is a difficult thing to sell, to a business-man.
Let's say that we go the ANTLR way. Which means from all the languages which are out there, none fits our needs, so we have to build our own. Ok, let's say we've build one. But now we need for our users an editor. We might need good error messages, syntax highlight, the possibility to reuse blocks of code from our programming language. Later we will need to extend our language, without hurting the users. And on the other side, the code generated will be difficult to maintain or to build abstractions on it. That's because code generation is just a raw, automatic, copy-paste machine: it doesn't build any higher-order abstractions. And the direction seems to be wrong: instead of helping our customer, we are baby-sitting our language/editor/infrastructure.
Or we go with embedded DSLs: some languages are better then others. And some languages can be easily integrated with others. Some have heavier syntax making more difficult to create a clean DSL, some are more lightweight, making a perfect match for DSLs.
Or we can go with a 'building material', a 'programmable programming language', or something similar.
Why do people fear of learning of a new language, but they invest uncountable hours in building sand-castles. Even Martin agrees: be a polyglot.
Friday, May 02, 2008
Validation Combinators, a F# prototype
Some strings can be validated as 'standalone' some need to be validate against each other. This validation doesn't belong to the domain for several reasons.
Regrettably, the existing frameworks don't suite our needs.
Trying to decompose the problem, we need:
- a way to get object out of the bag, by name
- a validation method/function which can validate one or more objects
- if the validation fails, we want to collect a 'failed validation message'
- we want to compose these validations: and, or, not, xor, etc...
Let's put that in code:
1 #light
2 open System
3 open System.Collections.Generic
4 open System.Text
5
6 type get_by_name = String -> String
7 type accumulate_message = String -> unit
8 type validator = get_by_name -> accumulate_message -> bool
10 let _and_ (x : validator) (y : validator) = (fun (get_value_from_bag : get_by_name) (accumulate_validation_message : accumulate_message) ->
11 let ok_x = lazy x get_value_from_bag accumulate_validation_message
12 let ok_y = lazy y get_value_from_bag accumulate_validation_message
13 Lazy.force ok_x && Lazy.force ok_y)
14
15 let _or_ (x : validator) (y : validator) = (fun (get_value_from_bag : get_by_name) (accumulate_validation_message : accumulate_message) ->
16 let ok_x = lazy x get_value_from_bag accumulate_validation_message
17 let ok_y = lazy y get_value_from_bag accumulate_validation_message
18 Lazy.force ok_x || Lazy.force ok_y)
19
20 let _not_ (x : validator) = (fun (get_value_from_bag : get_by_name) (accumulate_validation_message : accumulate_message) ->
21 let ok_x = x get_value_from_bag accumulate_validation_message
22 not ok_x)
23
24 let _xor_ (x : validator) (y : validator) = ((_not_ x) |> _and_ y ) |> _or_ (y |> _and_ (_not_ x) )
a lazy value: the value is computed only once, delayed, on demand. In the xor combinator we have used another DSL-friendly feature: partial-function application: ie. if we have a function f(x, y) -> z we could write this as: y |> f x.
Ok, let's see how it works. We define a validator:
27 let is_starting_with (start : String) (name : String) = (fun (get_value_from_bag : get_by_name) (accumulate_validation_message : accumulate_message) ->
28 let value = get_value_from_bag name
29 let ok = value.StartsWith(start)
30 if not ok then
31 let msg = string.Format("{0} '{1}' doesn't start with {2}", name, value, start)
32 accumulate_validation_message msg
33 ok)
Lets define a map of name -> values, and a message accumulator:
36 let map = new Dictionary
37 map.Add("customer","ali")
38 map.Add("buyer", "baba")
39 map.Add("vendor", "nono")
40
41 let get_value (key : String) : String = map.Item(key)
42
43 let acc = new StringBuilder()
44 let accumulate (msg: String) : unit = acc.AppendLine(msg) |> ignore
Lets create a more complex validator:
46 let x = ("customer" |> is_starting_with "a") |> _and_ ("buyer" |> is_starting_with "b") |> _and_ ("vendor" |> is_starting_with "v")
And we can evaluate it:
48 printfn "evaluation: %b messages: %s" (x get_value accumulate) (acc.ToString())
49
50 System.Console.ReadKey()
ps. Look at a better example in a Good Video: "Composing Contracts: An Adventure in Financial Engineering".
pps. here is the code.