(Animal Quiz kata: http://tonyxzt.blogspot.it/2014/02/animal-quiz-problem-in-f.html)
The first version was command line based, and was developed with extensive use of unit tests and interaction tests (using Rhino framework).
That version is still incorporated in this new version (git: https://github.com/tonyx/fsharpanimalquizgtk) except that it is possible to run in a gui gtk# wideget adding the paremeter "gui" to the command line.
For instance, under mono, I will run it as
> mono fsharpAnimalQuizKata.exe gui
and the following window will appear:
The interaction with the user is still text based, as in the console based version: the user dialogues with the "engine" using the textbox and the label of the textbox, instead of the standard input/standard output.
Now the knowledge tree can be visualised, hidden, expanded by using the three buttons available.
The knowledge base starts from as single node with the animal "elephant" in it.
An example of interaction to make the engine add a new node with a "cat" is:
After this dialogue, the knowledge tree can be expanded and we can see the following:
Now the loop starts again, and in a new dialogue we can feed the engine to make it store a new node "ant", which is small and is an insect, so expanding the knowledge tree we can see the following:
Essentially the logic of using gtk# in F# is the same as using it in C#.
I've found that translating existing C# based tutorials of gtk# in F# requires:
- declaring as mutable any objects that will be reassigned.
- when event handling in C# has the form:
// adding
// adding handler
btn.Clicked += on_btn_clicked;
// declaration of the handler
void on_btn_clicked (object obj, EventArgs args) {
// method body
}
An equivalent in F# is
// adding handler
do btn.Clicked.AddHandler(fun i e -> this.on_btn_clicked(i,e))
// declaration of the handler
member this.on_btn_clicked(i,e: EventArgs) =
// method body
Using Monodevelop/Xamarin I can create an empty F# gtk# template project that starts with an empty window. In my experience starting from that, and consulting any C# gtk# tutorial to add buttons, menus etc... is good enough to play with gtk# gui.
(I'm sure they it should work also in .net and Windows, but I have not tried yet.)
To "wrap" a knowledge tree to a gtk# TreeStore view I used this function
let rec treeToTreeStore tree (store: TreeStore) iter prefix =
match tree with
| AnimalName name ->
do store.AppendValues(iter,[|prefix + name|]) |> ignore
| SubTree {Question=quest; YesBranch=yBranch; NoBranch=nBranch } ->
let innerIter = store.AppendValues(iter,[|prefix + quest|])
do treeToTreeStore yBranch store innerIter "YES: "
do treeToTreeStore nBranch store innerIter "NO: "
Just as a reminder. A knowledge tree type is defined as follows:
type KnowledgeTree = AnimalName of string | SubTree of Tree
and Tree = {Question: string; YesBranch: KnowledgeTree; NoBranch: KnowledgeTree}
which means that a knowledge tree is a leaf node constituted by a string (the name of the animal) or is a yes/no question and two sub trees (one leading to the animals for whom the answer is "yes" and another to the animals for whom the answer is "no").
I found reasonable to make extensive use of automated testing of the console based version by mocking the standard input and standard output, and avoiding adding tests related to the gui.
Mocking standard output and standard input requires only making those kind of abstractions
type OutStream =
abstract Write: string -> unit
type InStream =
abstract Input: unit -> string
that will be implemented in the real application as follows:
type ConsoleOutput() =
interface OutStream with
member this.Write(x) = printf "%s\n" x
type ConsoleInput() =
interface InStream with
member this.Input() = Console.ReadLine()
whereas mocks can be used to simulate input/output interaction (by simulating that the user write some input values, and adding expectations about values that the engine will write)
[<Test>]
let ``with a one leaf node knowledge tree, engine asks if it is the animal on that node, the user says yes, and so the engine answer with "yeah!"``() =
// setup
let mockrepo = new MockRepository()
let inStream = mockrepo.StrictMock<InStream>()
let outStream = mockrepo.StrictMock<OutStream>()
// setup expectations
Expect.Call(outStream.Write("think about an animal")) |> ignore
Expect.Call(inStream.Input()).Return("whatever") |> ignore
Expect.Call(outStream.Write("is it a cat?")) |> ignore
Expect.Call(inStream.Input()).Return("yes") |> ignore
Expect.Call(outStream.Write("yeah!")) |> ignore
Expect.Call(inStream.Input()).Return("anything") |> ignore
mockrepo.ReplayAll()
// arrange
let tree = AnimalName "cat"
let initState = Welcome
let numOfLoops = 3
// act
runUntilIndex outStream inStream tree initState numOfLoops |> ignore
// verify expectations
mockrepo.VerifyAll()
I guess it would be more complicated to make a similar mechanism of mocking the gui as well, with less value, because the gui is just a thin layer upon the engine which is already extensively tested.
Special mention to Visual Studio Code and Ionide as editor with some autocompletion features that are missing in Monodevelop/Xamarin.
Future work could be related to
-saving/loading trees, and
-editing tree nodes in the tree view.
Toni.
2 comments:
While you can just translate the C# form of event handling, F# provides first-class event objects (like in the Rx framework) out of the box, which helps reduce the amount of boilerplate in UI coding. More idiomatic than
btn.Clicked.AddHandler(fun i e -> this.on_btn_clicked(i,e))
where most of the time, the arguments are never looked at, is
btn.Clicked
|> Event.add (fun e -> ...)
or even
btn.Clicked
|> Event.add (fun _ -> ...)
in the usual case where the event object is never inspected. Writing a closure here allows us to pull in the context that actually gets used, even if the closure body is just a call to another function using a list of closed-over arguments, like
let label1 = ...
btn.Clicked
|> Event.add (fun _ -> OnClick label1)
That is more coincise. Thanks.
Post a Comment