Calculator

This sample, which has been ported from Phooey, generates a calculator.
First, to generate calculator's layout, we need to be able to generate a row of buttons.

UI<Source<char>> ButtonRow(string buttonNames)
{
    return (from c in buttonNames
                 select from click in UIM.Button(c.ToString())
                             select click.Const(c)).Concat().FromLeft();
}
This method converts each character c in a string into a button which outputs c on each click.
Resulting character sources are then joined by Concat into a single source, which value is the caption of
the last pressed button. Then, method FromLeft is applied to arrange the buttons in a row.
Now, the generated rows also need to be joined together:

UI<Source<char>> ButtonTable(string buttonNames)
{
    return (from names in buttonNames.Split(' ')
                 select ButtonRow(names)).Concat();
}	
This is essentially the same process, except that it combines more complex actions,
produced by ButtonRow. Also, note that there is no layout order specified, so the actual
layout of the resulting rows will be determined by enclosing UI action.
With buttons out of the way, let's create an action for displaying the result:

UI<Source<A>> Result<A>(Source<A> source)
{
    return from _ in UIM.Output(source)
                group source by "Result";
}
After the layout is complete, we need to take care of calculator's actual functionality.
Here is the method that transforms calculator's state given the pressed button's caption.

Pair<int, Func<int, int>> Compute(char c, Pair<int, Func<int, int>> pair)
{					
    var i = pair.First;
    var f = pair.Second;
    if (char.IsDigit(c))			
        return new Pair<int, Func<int, int>>(i * 10 + int.Parse(c.ToString()), f);
    else if (c == '=')
        return new Pair<int, Func<int, int>>(f(i), n => f(i));
    else			
        return new Pair<int, Func<int, int>>(0, Operation(c, f(i)));
}
Calculator's state is made of the displayed value and a continuation.
Pressing a digit acts on the displayed part of the state, pressing the = button applies the contiuation to the
to the displayed value, and pressing the other buttons combines the continuation with an operation,
generated by the following method:

Func<int, int> Operation(char name, int i)
{
    switch (name)
    {
        case '+': return n => i + n;
        case '-': return n => i - n;
        case '*': return n => i * n;
        case '/': return n => i / n;				
        default: return n => n;
    }			
}
The default branch here corresponds to the calculator's reset.
Now, everything is ready to create the calculator:

UI<Source<int>> Create()
{
    return from chars in ButtonTable("123+ 456- 789* C0=/")
                let state = chars.Fold(Compute, new Pair<int, Func<int, int>>(0, i => i))
                let i = state.Map(p => p.First)
                from _ in Result(i)
                orderby LayoutDirection.FromLeft
                group i by "Calculator";
}
Method Fold here produces a new source which value is aggregated from values of chars using
the Compute method.

Here it is, running on Mono this time:
calculator.jpg

Last edited Oct 30, 2008 at 7:18 PM by Yuuki, version 8

Comments

No comments yet.