Wednesday, January 19, 2011

Monad.Zero -- doing it right

For my own benefit (see title of blog) as much as anything else. When I wrote

Rule 1 of monadic parsers is -- if you return Monad.Zero, you're probably doing it wrong... It's likely that you mean monadic return of an empty list.

it's because there's a distinction between positively returning an empty result (i.e. successfully doing nothing) and returning nothing at all (not succeeding in what you were trying to do) -- as the F# documentation for Zero puts it

Called for empty else branches of if...then expressions in computation expressions.

So, for a parser monad Zero is the function that returns a parser that takes no input and always fails:

public static Parser<TInput, TOutput> Zero<TInput, TOutput>()
{
Operation<TInput, TOutput> bound = cs =>
{
return new List<KeyValuePair<TOutput, IEnumerable<TInput>>>();
};
return bound.ToParser();
}
view raw gistfile1.cs hosted with ❤ by GitHub

for the Enumerable or Seq monad, it's the empty sequence, for Maybe its None and so forth -- basically the only thing you can return if you don't have any value to return.

And for individual leaf parsers -- the functions that define an Operation<TInput, TOutput> and return them wrapped as a Parser<TInput, TOutput>, those Operation<TInput, TOutput>s should return the same non-value when faced with a parse failure.

By contrast, when composing a parser that may try to consume some of the input, but fail, and you want to not fail the whole parse, you need a parser that successfully matches nothing (and consumes nothing) as an alternative that provides a success value -- and that is not Zero, but rather Return of the non-value of the appropriate type. So, for example, the parser combinator Many:

public static Parser<TInput, List<TOutput>> Many<TInput, TOutput>(Parser<TInput, TOutput> p)
{
var arg1 = Many1(p);
var arg2 = ParserMonad.Return<TInput, List<TOutput>>(new List<TOutput>());
return Combinators.Oppp(arg1, arg2);
}
view raw gistfile1.cs hosted with ❤ by GitHub

is "Consume as many blocks of p as you can -- or, if that fails, do nothing"; by contrast, the combinator Sat fails if the predicate doesn't match:

public static Parser<TInput, TOutput> Sat<TInput, TOutput>(Predicate<TInput> p, Func<TInput, TInput, TOutput> select)
{
return Item<TInput, TInput>(x => x).SelectMany(c =>
{
if (p(c))
return ParserMonad.Return<TInput, TInput>(c);
else
return ParserMonad.Zero<TInput, TInput>();
}, select);
}
view raw gistfile1.cs hosted with ❤ by GitHub

So in that case it says Zero and means Zero because that's a failure to satisfy the parser -- here in the equivalent the F# computation expression we don't even have to write the else branch; it's filled in for us by the compiler.

And that's really the one subtlety to be borne in mind -- otherwise you end up with an almost intractable debugging issue when your parser fails unexpectedly, partway through consuming valid input.

Tuesday, January 18, 2011

Monadic Parsers in C#

Updated 17-Jan -- because I realised I'd gotten Input and Output the wrong way around; too easy to do when you're using the same type in each case.

Updated 18-Jan -- In which we bug-fix the heck out of the previous two iterations, and pretty much completely rewrite this post. Rule 1 of monadic parsers is -- if you return Monad.Zero, you're probably doing it wrong... It's likely that you mean monadic return of an empty list.

Following on from Mike Hadlow's recent series of posts on monads in C#, and the "Aha!" moment that SelectMany actually means Bind, in the same way that Select means map, I thought I'd take it for a spin on something more than just the usual Identity, Maybe and State monads.

For a long while now there have been things like illustrative F# ports of a Haskell monadic parser -- so why not, I thought, try to emulate that in C#?

So we have a type which looks like this (abusing KeyValuePair as a 2-tuple for .net 3.5 compatibility) --

public delegate List<KeyValuePair<TOutput, IEnumerable<TInput>>> Operation<TInput, TOutput>(IEnumerable<TInput> seq);
public struct Parser<TInput, TOutput>
{
public Operation<TInput, TOutput> Op;
}
view raw gistfile1.cs hosted with ❤ by GitHub

which is a monad upon the return type; the input stream parametrization being orthogonal to this.

The monad itself (and here I've reverted to using Bind for the simple case analogous to the F# case) looks like

public static class ParserMonad
{
public static Parser<TInput, TOutput> ToParser<TInput, TOutput>(this Operation<TInput, TOutput> value)
{
return new Parser<TInput, TOutput> { Op = value };
}
public static Parser<TInput, UOutput> Bind<TInput, TOutput, UOutput>(
this Parser<TInput, TOutput> value,
Func<TOutput, Parser<TInput, UOutput>> operation)
{
Operation<TInput, UOutput> bound = cs =>
{
var r = value.Op(cs);
var rr = r.Select(kv => operation(kv.Key).Op(kv.Value));
return rr.SelectMany(x => x).ToList();
};
return bound.ToParser();
}
public static Parser<TInput, VOutput> SelectMany<TInput, TOutput, UOutput, VOutput>(this Parser<TInput, TOutput> value,
Func<TOutput, Parser<TInput, UOutput>> operation,
Func<TOutput, UOutput, VOutput> select)
{
return value.Bind(t =>
operation(t).Bind(u =>
ParserMonad.Return<TInput, VOutput>(select(t, u))
));
}
public static Parser<TInput, TOutput> Return<TInput, TOutput>(TOutput value)
{
Operation<TInput, TOutput> bound = cs =>
{
var tmp = new KeyValuePair<TOutput, IEnumerable<TInput>>(value, cs);
return new List<KeyValuePair<TOutput, IEnumerable<TInput>>> { tmp };
};
return bound.ToParser();
}
public static Parser<TInput, TOutput> Zero<TInput, TOutput>()
{
Operation<TInput, TOutput> bound = cs =>
{
return new List<KeyValuePair<TOutput, IEnumerable<TInput>>>();
};
return bound.ToParser();
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

Here ToParser is the convenience function-to-monad constructor; and Return is what lifts an output value into the monad -- creating a parser that places that output value into the output stream.

The combinators assemble all the parsers from the atomic units -- some here are more specialized than others.

public static class Combinators
{
/// Non-deterministic choice -- The (++) operator
/// is a (non-deterministic) choice operator for parsers. The parser p ++ q applies
/// both parsers p and q to the argument string, and appends their list of results.
public static Parser<TInput, TOutput> Opp<TInput, TOutput>(Parser<TInput, TOutput> p, Parser<TInput, TOutput> q)
{
Operation<TInput, TOutput> bound = cs =>
{
return p.Op(cs).Concat(q.Op(cs)).ToList();
};
return bound.ToParser();
}
/// a deterministic)choice operator (+++) that has
/// the same behaviour as (++), except that at most one result is returned:
public static Parser<TInput, TOutput> Oppp<TInput, TOutput>(Parser<TInput, TOutput> p, Parser<TInput, TOutput> q)
{
Operation<TInput, TOutput> bound = cs =>
{
var sum = Opp(p, q).Op(cs);
if (sum.Any())
{
return new List<KeyValuePair<TOutput, IEnumerable<TInput>>> { sum.First() };
}
return new List<KeyValuePair<TOutput, IEnumerable<TInput>>>();
};
return bound.ToParser();
}
/// Matches 1 or more applications of parser p
/// f# prototype is parser['a, 'b] -> parser['a list, 'b]
public static Parser<TInput, List<TOutput>> Many1<TInput, TOutput>(Parser<TInput, TOutput> p)
{
var parser = p.Bind(x =>
{
var manyp = Many(p);
var outer = manyp.Bind(xs =>
{
var tmp = new List<TOutput> { x };
var result = tmp.Concat(xs).ToList();
return ParserMonad.Return<TInput, List<TOutput>>(result);
});
return outer;
});
return parser;
}
/// Matches 0 or more applications of parser p
/// f# prototype is parser['a, 'b] -> parser['a list, 'b]
public static Parser<TInput, List<TOutput>> Many<TInput, TOutput>(Parser<TInput, TOutput> p)
{
var arg1 = Many1(p);
var arg2 = ParserMonad.Return<TInput, List<TOutput>>(new List<TOutput>());
return Combinators.Oppp(arg1, arg2);
}
/// Apply a parser
public static Parser<TInput, TOutput> Apply<TInput, TOutput>(Parser<TInput, TOutput> p)
{
var parser = p.Bind(x => ParserMonad.Return<TInput, TOutput>(x));
return parser;
}
/// A parser which successfully consumes the first element
/// of the input stream if it is is non-empty, and fails otherwise.
/// f# prototype is (unit ->) parser[char, string]
public static Parser<TInput, TOutput> Item<TInput, TOutput>(Func<TInput, TOutput> select)
{
Operation<TInput, TOutput> bound =
cs =>
{
if (cs.Any())
{
return new List<KeyValuePair<TOutput, IEnumerable<TInput>>>
{
new KeyValuePair<TOutput, IEnumerable<TInput>>(select(cs.First()), cs.Skip(1).ToList())
};
}
return new List<KeyValuePair<TOutput, IEnumerable<TInput>>>();
};
return bound.ToParser();
}
/// A parser that consumes a single token if it matches the predicate
/// or fails otherwise
/// f# prototype is (char->bool) -> parser[char, string]
public static Parser<TInput, TOutput> Sat<TInput, TOutput>(Predicate<TInput> p, Func<TInput, TInput, TOutput> select)
{
var parser = Item<TInput, TInput>(x => x).SelectMany(c =>
{
if (p(c))
{
return ParserMonad.Return<TInput, TInput>(c);
}
else
{
return ParserMonad.Zero<TInput, TInput>();
}
}, select);
return parser;
}
/// A parser for specific characters
/// f# prototype is char -> parser[char, string]
public static Parser<char, TOutput> Char<TOutput>(char c, Func<char, char, TOutput> select)
{
return Sat(s => s == c, select);
}
/// Parse a specific string
/// f# prototype is string -> parser[char list, string]
public static Parser<char, List<TOutput>> StringP<TOutput>(string input, Func<char, char, TOutput> select)
{
if (input.Any())
{
var parser = Char<TOutput>(input.ToCharArray()[0], select).Bind(c =>
{
var p2 = StringP(input.Substring(1), select);
var inner = p2.Bind(cs =>
{
var tmp = new List<TOutput> { c };
var result = tmp.Concat(cs).ToList();
return ParserMonad.Return<char, List<TOutput>>(result);
});
return inner;
});
return parser;
}
else
{
return ParserMonad.Return<char, List<TOutput>>(new List<TOutput>());
}
}
/// Parse a string of spaces.
/// f# prototype is (unit ->) parser[char list, string]
public static Parser<char, List<TOutput>> Space<TOutput>(Func<char, char, TOutput> select)
{
return Many(Sat(System.Char.IsWhiteSpace, select));
}
/// Parse a token using a parser p, throwing away any trailing space.
/// f# prototype is parser['a, string] -> parser['a, string]
public static Parser<char, TOutput> Token<TOutput>(Parser<char, TOutput> p, Func<char, char, TOutput> select)
{
var parser = p.Bind(x =>
Space(select).Bind(xs =>
{
return ParserMonad.Return<char, TOutput>(x);
}
));
return parser;
}
/// Parse a symbolic token:
/// f# prototype is string -> parser[char list, string]
public static Parser<char, List<TOutput>> Symbol<TOutput>(string symbol, Func<char, char, TOutput> select)
{
Parser<char, List<TOutput>> inner = StringP<TOutput>(symbol, select);
return Token<List<TOutput>>(inner, (x, y) => new List<TOutput> { select(x,y) } );
}
/// Parse repeated applications of a parser p, separated by applications of a parser
/// op whose result value is an operator that is assumed to associate to the left,
/// and which is used to combine the results from the p parsers.
public static Parser<TInput, TOutput> Chain1<TInput, TOutput> (
Parser<TInput, TOutput> p,
Parser<TInput, Func<TOutput, TOutput, TOutput>> op)
{
return p.Bind(x => Chain1Rest(x, p, op));
}
/// Helper method for Chain1
public static Parser<TInput, TOutput> Chain1Rest<TInput, TOutput> (
TOutput a,
Parser<TInput, TOutput> p,
Parser<TInput, Func<TOutput, TOutput, TOutput>> op)
{
var parser = op.Bind(f =>
p.Bind(b =>
Chain1Rest(f(a, b), p, op)
));
var stop = ParserMonad.Return<TInput, TOutput>(a);
return Oppp(parser, stop);
}
//F# Monadic Parser - Calculator Example :)))
// Grammar
//expr ::= expr addop term | term
//term ::= term mulop factor | factor
//factor ::= digit | ( expr )
//digit ::= 0 | 1 | : : : | 9
//addop ::= + | -
//mulop ::= * | /
public static Parser<char, Func<double, double, double>> AddOp()
{
var plus = Symbol<char>("+", (x, y) => x).Bind(x =>
ParserMonad.Return<char, Func<double, double, double>>(
(a, b) => a + b));
var minus = Symbol<char>("-", (x, y) => x).Bind(x =>
ParserMonad.Return<char, Func<double, double, double>>(
(a, b) => a - b));
return Oppp(plus, minus);
}
public static Parser<char, Func<double, double, double>> MulOp()
{
var plus = Symbol<char>("*", (x, y) => x).Bind(x =>
ParserMonad.Return<char, Func<double, double, double>>(
(a, b) => a * b));
var minus = Symbol<char>("/", (x, y) => x).Bind(x =>
ParserMonad.Return<char, Func<double, double, double>>(
(a, b) => a / b));
return Oppp(plus, minus);
}
public static Parser<char, double> Digit()
{
var sat = Sat<char, char>(System.Char.IsDigit, (x, y) => x);
return Token<char> (sat, (x,y) => x).Bind<char, char, double>
(x => ParserMonad.Return<char, double>((double)(x - '0')));
}
public static Parser<char, double> Expr()
{
return Chain1<char, double>(Term(), AddOp());
}
public static Parser<char, double> Term()
{
return Chain1<char, double>(Factor(), MulOp());
}
public static Parser<char, double> Factor()
{
var paren = Symbol("(", (x, y) => x).
Bind(x => Expr().
Bind(n => Symbol(")", (x2, y2) => x2).
Bind(z => ParserMonad.Return<char, double>(n))));
return Oppp(Digit(), paren);
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

And we can top this off with an example

public static void Main()
{
var restToken = Combinators.Sat<string, int>(x =>
{
return x.Length > 2;
},
(x,y) => {
return Math.Max(x.Length, y.Length);
});
var tail = Combinators.Many(restToken);
var data = new List<string> { "the", "quick", "brown", "fox" };
var results = Combinators.Apply(tail).Op(data);
if (results.Any())
{
results.ForEach(x => Console.WriteLine(
"key={0}; value={1}",
"[" + string.Join("; ", x.Key.Select(n=>n.ToString()).ToArray()) + "]",
"[" + string.Join("; ", x.Value.ToArray()) + "]"));
}
else
{
Console.WriteLine("empty");
}
var calc = "5 * (3 + 4)";
var summed = Combinators.Apply(Combinators.Expr()).Op(calc.ToCharArray());
if (summed.Any())
{
summed.ForEach(x => Console.WriteLine(
"key={0}; value={1}",
"[" + x.Key + "]",
"[" + string.Join("; ", x.Value.Select(z => z.ToString()).ToArray()) + "]"));
}
else
{
Console.WriteLine("empty");
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

which produces a successfully parsed output

key=[3; 5; 5; 3]; value=[]
key=[35]; value=[]

So what lessons do I draw from this exercise?

The big one is that old saying about needing to be twice as clever to debug code as to write it; and code that works with continuation passing can get pretty darn clever. Or, as Eric Lippert put it (in the context of writing asynchronous code in this style in C#):

Remember what I was saying about the pros and cons of CPS?

  • PRO: Arbitrarily complex and interesting control flows can be built out of simple parts – check.
  • CON: The reification of control flow via continuations is hard to read and hard to reason about – check.
  • CON: The code that represents the mechanisms of control flow completely overwhelms the meaning of the code – check.
  • CON: The transformation of ordinary code control flow into CPS is the kind of thing that compilers are good at, and almost no one else is – check.

The parts are indeed individually simple enough; but all the rest is sadly true. We can see how clever, what the actual power-to-weight ratio is, or how much what actually gets executed differs from what it superficially looks like we've written, by replacing the initialization in ToParser with

{ Op = x => {
max = Math.Max(max, new System.Diagnostics.StackTrace().GetFrames().Length);
return value(x);
} }
view raw gistfile1.cs hosted with ❤ by GitHub

for a static integer value max in the class. Running the test example, we see that the stack depths achieved by these simple parsers are 64 and 95 respectively -- causes and effects are remote from one another (with the side effect that the only code not covered in this belongs to the subtract and divide operator parsers, and the failure handlers in the main program.

Coupled with this is the difficulty of doing anything meaningful in the way of fine-grained TDD in the bootstrap phase -- the individual pieces are not parsers, are comparatively trivial, and even those return functions operating on functions; it's in combination that they do their magic, and there's a lot of code needed just to read one token out of a stream.

Having the F# equivalent to work with -- to provide guidance of what types all the individual pieces were, and then in essence manually de-sugaring the computation expression notation -- was invaluable. But if you have the F# version, there is little need for the transposition except as a five-finger exercise like this.

On the positive side, though, once you have the toolkit like this and it handles even the simple cases, that probably means that it is robust; and then it can pretty much be taken as a black box, for those times when you absolutely have to do it in C# rather than F#.

Sunday, January 16, 2011

More Garden Rubbish

Insanely mild weather today meant that when I slipped out in bare feet and T-shirt to check the greenhouse, I ended up doing a bit more pottering around in the garden to catch up with some of the tidying from the autumn tasks without feeling the need to wear more.

I did put on a shirt and sandals for the catch-up pruning of the apple trees and digging over the beds where the annual flowers and the broccoli, which didn't survive the harsh weather in Nov-Dec, had been.

Tuesday, January 11, 2011

Using F# 2.0 Powerpack ArgParser from C# -- (ii)

Having resolved the issue of why the "--" separator wasn't triggering (PowerShell was swallowing it before it got to my code) that was stumping me in the previous post, I put together a little fluent wrapper to make this a little easier to consume from C#. The code that isolates the F# types looks like this:

namespace Tinesware.ArgParser
{
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.FSharp.Core;
using Microsoft.FSharp.Text;
/// <summary>
/// Assembles the various command line options and then calls the parser
/// </summary>
public class Options
{
/// <summary>
/// Option values
/// </summary>
private List<ArgInfo> options = new List<ArgInfo>();
/// <summary>
/// Callbacks for boolean values
/// </summary>
private List<Flag> flags = new List<Flag>();
/// <summary>
/// Initializes a new instance of the Options class.
/// </summary>
public Options()
{
}
/// <summary>
/// An option with no argument
/// </summary>
/// <param name="option">The option string</param>
/// <param name="callback">The callback for this option</param>
/// <param name="usage">Usage comment</param>
/// <returns>This object (fluent interface)</returns>
public Options Unit(string option, Action callback, string usage)
{
var type = ArgType.Unit(FuncConvert.ToFSharpFunc<Unit>(x => callback()));
this.options.Add(new ArgInfo(option, type, usage));
return this;
}
/// <summary>
/// An option with a string argument
/// </summary>
/// <param name="option">The option string</param>
/// <param name="callback">The callback for this option</param>
/// <param name="usage">Usage comment</param>
/// <returns>This object (fluent interface)</returns>
public Options String(string option, Action<string> callback, string usage)
{
var type = ArgType.String(FuncConvert.ToFSharpFunc<string>(callback));
this.options.Add(new ArgInfo(option, type, usage));
return this;
}
/// <summary>
/// An option with an integer argument
/// </summary>
/// <param name="option">The option string</param>
/// <param name="callback">The callback for this option</param>
/// <param name="usage">Usage comment</param>
/// <returns>This object (fluent interface)</returns>
public Options Int(string option, Action<int> callback, string usage)
{
var type = ArgType.Int(FuncConvert.ToFSharpFunc<int>(callback));
this.options.Add(new ArgInfo(option, type, usage));
return this;
}
/// <summary>
/// An option with a double precision argument
/// </summary>
/// <param name="option">The option string</param>
/// <param name="callback">The callback for this option</param>
/// <param name="usage">Usage comment</param>
/// <returns>This object (fluent interface)</returns>
public Options Float(string option, Action<double> callback, string usage)
{
var type = ArgType.Float(FuncConvert.ToFSharpFunc<double>(callback));
this.options.Add(new ArgInfo(option, type, usage));
return this;
}
/// <summary>
/// An end-of-arguments argument, usually "--"
/// </summary>
/// <param name="option">The option string, usually "--"</param>
/// <param name="callback">The callback for the remaining arguments</param>
/// <param name="usage">Usage comment</param>
/// <returns>This object (fluent interface)</returns>
public Options Rest(string option, Action<string> callback, string usage)
{
var type = ArgType.Rest(FuncConvert.ToFSharpFunc<string>(callback));
this.options.Add(new ArgInfo(option, type, usage));
return this;
}
/// <summary>
/// An argument indicating true
/// </summary>
/// <param name="option">The option string</param>
/// <param name="callback">The callback for the remaining arguments</param>
/// <param name="usage">Usage comment</param>
/// <returns>This object (fluent interface)</returns>
public Options Set(string option, Action<bool> callback, string usage)
{
var flag = new Flag
{
Reference = new FSharpRef<bool>(false),
Callback = callback
};
var type = ArgType.Set(flag.Reference);
this.options.Add(new ArgInfo(option, type, usage));
this.flags.Add(flag);
return this;
}
/// <summary>
/// An argument indicating false
/// </summary>
/// <param name="option">The option string</param>
/// <param name="callback">The callback for the remaining arguments</param>
/// <param name="usage">Usage comment</param>
/// <returns>This object (fluent interface)</returns>
public Options Clear(string option, Action<bool> callback, string usage)
{
var flag = new Flag
{
Reference = new FSharpRef<bool>(true),
Callback = callback
};
var type = ArgType.Set(flag.Reference);
this.options.Add(new ArgInfo(option, type, usage));
this.flags.Add(flag);
return this;
}
/// <summary>
/// Parse the environment command line with no default handler or usage comment
/// </summary>
public void Parse()
{
this.Parse(null, null);
}
/// <summary>
/// Parse the environment command line with no default handler
/// </summary>
/// <param name="usage">usage comment</param>
public void Parse(string usage)
{
this.Parse(null, usage);
}
/// <summary>
/// Parse the environment command line with no usage comment
/// </summary>
/// <param name="defaultHandler">default handler for unmatched arguments</param>
public void Parse(Action<string> defaultHandler)
{
this.Parse(defaultHandler, null);
}
/// <summary>
/// Parse the environment command line
/// </summary>
/// <param name="defaultHandler">default handler for unmatched arguments</param>
/// <param name="usage">usage comment</param>
public void Parse(Action<string> defaultHandler, string usage)
{
var temp = usage ?? string.Empty;
var theUsage = string.IsNullOrEmpty(temp.Trim()) ?
FSharpOption<string>.None :
new FSharpOption<string>(usage);
var theDefault = defaultHandler == null ?
FSharpOption<FSharpFunc<string, Unit>>.None :
new FSharpOption<FSharpFunc<string, Unit>>(
FuncConvert.ToFSharpFunc<string>(defaultHandler));
ArgParser.Parse(this.options, theDefault, theUsage);
this.flags.ForEach(x => x.Callback(x.Reference.Value));
}
/// <summary>
/// Report the command usage
/// </summary>
public void Usage()
{
this.Usage(null);
}
/// <summary>
/// Report the command usage
/// </summary>
/// <param name="remark">Initial comment</param>
public void Usage(string remark)
{
var temp = remark ?? string.Empty;
var theUsage = string.IsNullOrEmpty(temp.Trim()) ?
FSharpOption<string>.None :
new FSharpOption<string>(remark);
ArgParser.Usage(this.options, theUsage);
}
/// <summary>
/// Binds a ref bool and a callback
/// </summary>
private struct Flag
{
/// <summary>
/// A reference cell with a boolean value
/// </summary>
public FSharpRef<bool> Reference;
/// <summary>
/// A post-parse callback
/// </summary>
public Action<bool> Callback;
}
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

The calling code, for the same example as before looks like

namespace fsgetopt
{
class Program
{
static void Main(string[] args)
{
Action<string> compile = (s => Console.WriteLine("Compiling {0}...", s));
var outputName = "a.out";
var verbose = false;
var warningLevel = 0;
var rest = new List<string>();
var options = new Tinesware.ArgParser.Options().
String("-o",
x => outputName = x,
"Name of the output").
Set("-v",
x => verbose = x,
"Display additional information").
Int("--warn",
x => warningLevel = x,
"Set warning level").
Rest("--",
x => rest.Add(x),
"Stop parsing command line");
options.Parse(compile, "Usage options are:");
Console.WriteLine("outputName = {0}", outputName);
Console.WriteLine("Verbose = {0}", verbose.Value);
Console.WriteLine("Warning level = {0}", warningLevel);
Console.WriteLine("rest = {0}", rest.Count);
}
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

which is much clearer.

Now edited for FxCop.

Monday, January 10, 2011

Garden Rubbish and Meaningless Numbers

You can tell quite how early -- late November -- the big freeze set in, as for the last couple of weekends I was doing things like raking leaves off the lawn, as well as other winter chores like pruning roses and clearing other dead foliage from expected dieback (enough to fill the green bin). The cold was also enough that inside the greenhouse, even under a further bubble-wrap tent with a heater set to frost-free, some of the pelargoniums got scorched -- not something I've had happen before in a winterised greenhouse.

In the weekend clearout, I got rid of the last tomato plants from the tent, harvesting one last tomato. Sunday I baked a crumble and almost used up the last of the apples -- leaving two Charles Ross picked and packed remained in a box of fruit that had mostly spontaneously self-destructed; and a large windfall Bramley (we ate the former raw and baked the latter for dessert today). Many of the picked-as-ripe Bramleys also self-destructed from within in the last couple of weeks; the windfalls were just as durable as the cosseted ones.

And some numbers -- at 08:39-08:40 on 21-Oct-10, the digital clock and odometer coincided; as again, cheating at 09:50 BST on 2-Nov; not cheating at 17:43-40 on 6-Jan-11; 1000 miles on 4-Nov. The bike read 1033.9 miles at the end of the year (and also on the 6 month mark of having the bike computer, 7-Jan).

Using F# 2.0 Powerpack ArgParser from C#

This is an update of the technique mentioned in an antique post by Robert Pickering which was referenced by Laurent Le Brun last summer. I've taken Laurent's example of F# usage and ported it to C# in the most direct fashion:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.FSharp.Core;
using Microsoft.FSharp.Text;
namespace fsgetopt
{
class Program
{
static void Main(string[] args)
{
Action<string> compile = (s => Console.WriteLine("Compiling {0}...", s));
var outputName = "a.out";
var verbose = new FSharpRef<bool>(false);
var warningLevel = 0;
ArgInfo[] specs =
{
new ArgInfo("-o",
ArgType.String (FuncConvert.ToFSharpFunc<string> (x => outputName=x)),
"Name of the output"),
new ArgInfo("-v",
ArgType.Set (verbose),
"Display additional information"),
new ArgInfo("--warn",
ArgType.Int (FuncConvert.ToFSharpFunc<int> (x => warningLevel=x)),
"Set warning level"),
new ArgInfo("--",
ArgType.Rest (FuncConvert.ToFSharpFunc<string> (x => Console.WriteLine("rest has {0}", x))),
"Stop parsing command line"),
};
var after = FuncConvert.ToFSharpFunc<string>(compile);
var opt1 = new FSharpOption<FSharpFunc<string, Unit>>(after);
var opt2 = new FSharpOption<string>("Usage options are:");
ArgParser.Parse(specs, opt1, opt2);
Console.WriteLine("outputName = {0}", outputName);
Console.WriteLine("Verbose = {0}", verbose.Value);
Console.WriteLine("Warning level = {0}", warningLevel);
}
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

It is possible to streamline this to hide most of the messy type names inside utilities.

One thing I've not managed to do is get the lambda associated with the ArgType.Rest to fire, even after setting the default handler opt1 to FSharpOption<T>.None. That -- the default handler -- gets fired every time an otherwise unmatched argument is encountered; the ArgType.Rest handler looks from the code like it should "just work" so I'm a bit baffled ATM.

LATER: Mystery solved

Of course, I was testing this at a PowerShell prompt -- and the shell was swallowing the unquoted "--" string : there was no "rest" to operate on.