So, shortly after adding the more in depth examples in my last post, I started playing around with the TryParse method in C# to see how "nice" I could make the example code:
public class User { public int Age { get; set; } public DateTime SignupDate { get; set; } public Double Weight { get; set; } static string Post(string key) { return key; } static User Build() { var age = 0; var signupDate = DateTime.MinValue; var weight = 0.0; int.TryParse(Post("age"), out age); double.TryParse(Post("weight"), out weight); DateTime.TryParse(Post("signupDate"), out signupDate); return new User { Age = age, SignupDate = signupDate, Weight = weight }; } }
The first attempt I made involved a generic ParseOrDefault function and a corresponding TryFunc delegate:
delegate bool TryFunc<t>(string raw, out T value); static T ParseOrDefault<t>(TryFunc<t> method, string raw) { var local = default(T); method(raw, out local); return local; }
Sadly, using that function in the most natural way possible caused type inferencing errors, so the end result of that little bit of work was this code:
var user = new User { Age = ParseOrDefault<int>(int.TryParse, Post("age")), SignupDate = ParseOrDefault<datetime>(DateTime.TryParse, Post("signupDate")), Weight = ParseOrDefault<double>(Double.TryParse, Post("weight")) };
It's not terrible, but the redundant type specifiers really chafe. The code could be much prettier with a smarter C# compiler.
As a fun little exercise, I also spent time making the F# from the previous example something more appealing. The original F# looked like this:
type User = { Age : int; SignupDate : DateTime; Weight : Double; } let post key = key let u = { Age = Int32.TryParse(post "age") |> snd; SignupDate = DateTime.TryParse(post "signupDate") |> snd; Weight = Double.TryParse(post "weight") |> snd; }
I started down the same path, with the F# equivalent of ParseOrDefault from above:
let parseOrDefault f v = f v |> snd
parseOrDefault takes a function (named f) and a value as parameters. It pipes the result of f(v) to the built in second function, which returns the second value in a two-value tuple.
Using that function lets me change my F# to something like this:
let u = { Age = post "age" |> parseOrDefault Int32.TryParse; SignupDate = post "signupDate" |> parseOrDefault DateTime.TryParse; Weight = post "weight" |> parseOrDefault Double.TryParse; }
It's actually a little bit more text than the original, but it seems a bit more straightforward to me. "Send the result of the post function to my parseOrDefault function, which should use Int32.TryParse to do its thing."
The level of repetition in that last bit of code annoys me, though. Fortunately for me, creating new functions in F# is such an easy task that I can do this instead:
let u = let parsePosted f key = parseOrDefault f (post key) { Age = parsePosted Int32.TryParse "age"; SignupDate = parsePosted DateTime.TryParse "signupDate"; Weight = parsePosted Double.TryParse "weight"; }
Doing the equivalent in C# is just not worth it, at least in this case. A generic ParsePosted function would have to be a static member somewhere, since you can't do this in C#:
Func <tryFunc<t>,string,T> ParsePosted = (method, key) => ParseOrDefault<t>(method, Post(key)); var user = new User { Age = ParseOrDefault<int,string>(int.TryParse, Post("age")), SignupDate = ParseOrDefault<dateTime,string>(DateTime.TryParse, Post("signupDate")), Weight = ParseOrDefault<double,string>(Double.TryParse, Post("weight")) };
Local, one-off functions are possible in C#, but I can't find any way to create a generic one. That really sucks at times like this when we're dealing with types that aren't anywhere in the same inheritance chain.
There's another interesting aspect to the F# version of the parseOrDefault method. F# automatically generalizes it to the equivalent of this in C#:
delegate bool TryFunc<t, K>(K raw, out T value); static T ParseOrDefault<t,K>(TryFunc<t> method, K raw) { var local = default(T); method(raw, out local); return local; }
This version of the function might come in handy if I ever need the TryGetValue function of a dictionary with some-type-other-than-string as a key. The downside in C# is that it gets terribly verbose to use that kind of function, due to the limits in type inferencing:
return new User { Age = ParseOrDefault<int,string>(int.TryParse, Post("age")), SignupDate = ParseOrDefault<dateTime,string>(DateTime.TryParse, Post("signupDate")), Weight = ParseOrDefault<double,string>(Double.TryParse, Post("weight")) };
As I get deeper into F#, I'm beginning to realize just how empowering it is for function creation and composition to be such a cheap (from a development time perspective) operation.
Comments (2)
FWIW, I *think* the C# 4 compiler will fix the type inference bug you ran into with using TryParse.
Ah, that would rock.