4. Simple Domain type Point = { X : float; Y : float } type
Size = { Width : float; Height : float } type Frame = { Position :
Point Size : Size }
5. Simple Domain type Shape = | Oval of Frame | Rectangle of
Frame
6. Moar Features! type Shape = | Oval of Frame | Rectangle of
Frame | Path of Point list | Union of Shape list | Subtract of
Shape list
7. Common Properties type Element = { Id : Guid Name : string
Color : Color Shape : Shape } and Shape = | Oval of Frame |
Rectangle of Frame | Path of Point list | Union of Element list |
Subtract of Element list
8. Document Model type Document = { Elements : Element list
}
9. module DrawingDomain = type Color = { R : float; G : float;
B : float; A : float } type Point = { X : float; Y : float } type
Size = { Width : float; Height : float } type Frame = { Position :
Point Size : Size } type Shape = | Oval of Frame | Rectangle of
Frame | Path of Point list | Union of Shape list | Subtract of
Shape list type Element = { Id : Guid Name : string Color : Color
Shape : Shape } type Document = { Elements : Element list }
10. CRUD Operations
11. New Document let newDocument = { Elements = [] }
12. Add Oval let addOval doc frame = let gray = rgb 0.5 0.5 0.5
let oval = { Id = Guid.NewGuid () Name = "Oval" Color = gray Shape
= Oval frame } { doc with Elements = oval :: doc.Elements }
13. Add Oval let d1 = newDocument let d2 = addOval d1 {
Position = { X = 0.0; Y = 0.0 } Size = { Width = 200.0; Height =
200.0; } } val d1 : Document = {Elements = [];} val d2 : Document =
{Elements = [{Id = 9993a910-c487-4b6f-9025-279ce941518f; Name =
"Oval"; Color = {R = 0.5; G = 0.5; B = 0.5; A = 1.0;}; Shape = Oval
{Position = {X = 0.0; Y = 0.0;}; Size = {Width = 200.0; Height =
200.0;};};}];}
14. Remove Elements let removeElement doc id = ...
15. Mapping val newData : int list = [0; 1000; 2000; 3000;
4000] let data = [0; 1; 2; 3; 4] let times1000 x = x * 1000 let
newData = data |> List.map times1000
16. Map the Document let rec mapElements f elms = let
mapChildren e = match e.Shape with | Union children -> { e with
Shape = Union (mapElements f children) } | Subtract children ->
{ e with Shape = Subtract (mapElements f children) } | _ -> e
let mapElement = mapChildren >> f elms |> List.choose
mapElement and mapDocument f doc = { doc with Elements =
mapElements f doc.Elements }
17. Remove Element et removeElement doc id = let keep e = if
e.Id = id then None else Some e mapDocument keep doc
18. Change the Color let setColor doc ids newColor = let
selected e = Set.contains e.Id ids let set e = if selected e then
Some { e with Color = newColor } else Some e mapDocument set
doc
19. Mutant Free Zone
20. GUIs
21. OOP GUIs FrankName Code Behind, View Model, View
Controllers, Reactive, Binding, whatever 1. User types something 2.
Text property changes 3. Some middleman to help you sleep at night
4. Mutate the Model (change properties) Model
22. Mutant Love Zone
23. F# is OOP type ShapeMutant () = let mutable color = rgb 0.5
0.5 0.5 member this.Color with get () = color and set v = color {
if (e.Name == "Text") view.Text = model.Name; } ; } }
28. public class ElementNameEditor { TextEdit view; Element
model; void Initialize () { // Display the current data view.Text =
model.Name; // Handle user changes view.TextChanged += (s, e) =>
model.Name = view.Text; // Refresh when the doc changes
model.PropertyChanged += (s, e) => { if (e.Name == "Text")
view.Text = model.Name; } ; } } OOP Name Editor Direct Reference
MAGIC
29. Two Changes
30. 1. Instead of a Direct Reference, use an Indirect
Reference
31. 2. Instead of MAGIC, call a function and handle the update
event
32. Functional Name Editor type ElementNameEditor (view :
TextEdit, docc : DocumentController, id) = member this.Initialize
() = // Handle user changes view.TextChanged.Add (fun _ -> let
newName = view.Text let setName e = if e.Id = id then Some { e with
Name = newName } else Some e let newDoc = mapDocument setName
docc.Data docc.Update newDoc) // Display the current data let
refresh doc = let model = getElement id doc view.Text let newName =
view.Text let setName e = if e.Id = id then Some { e with Name =
newName } else Some e let newDoc = mapDocument setName docc.Data
docc.Update newDoc) // Display the current data let refresh doc =
let model = getElement id doc view.Text let newName = view.Text let
setName e = if e.Id = id then Some { e with Name = newName } else
Some e let newDoc = mapDocument setName docc.Data docc.Update
newDoc) // Display the current data let refresh doc = let model =
getElement id doc view.Text let newName = view.Text let setName e =
if e.Id = id then Some { e with Name = newName } else Some e let
newDoc = mapDocument setName docc.Data docc.Update newDoc) //
Display the current data let refresh doc = let model = getElement
id doc view.Text let newName = view.Text let setName e = if e.Id =
id then Some { e with Name = newName } else Some e let newDoc =
mapDocument setName docc.Data docc.Update newDoc) // Display the
current data let refresh doc = let model = getElement id doc
view.Text SetName (view.Text); void SetName (string newName) { var
oldName = model.Name; UndoManager.RegisterUndo (() => SetName
(oldName)); UndoManager.SetAction ("Rename"); model.Name = newName;
} view.TextChanged += (s, e) => model.Name = view.Text;
43. For every command you write, you have to write its
inverse
46. F# Undo docc.Update newDoc docc.Update newDoc "Rename" No
Need to Write an Inverse Function!
47. type DocumentControllerWithUndo () = let updated = Event ()
let mutable historyIndex = 0 let mutable history = [(newDocument,
"New")] member this.Data = history.[historyIndex] member
this.Update newData message = history