Sunday, October 3, 2010

F# Computation Expressions: Basic Data Retrieval Mechanics

One of the uses often mentioned for F# computation expressions is that of simplifying the syntax of database retrieval. To investigate how the mechanics of this could work, I decided to implement a simple relational retrieval system. I tried to pare it down to the utmost basics, and I’m happy to say I got further than I expected; the sample problem has far more lines of code than does the computation expression itself!

The test database itself consists of two record types. The first is a simple key/value pair. The second is a linking record type which consists of two keys. This keeps things simple for the test, but the method would remain the same even for more complex record structures.

The computation expression doesn’t really do much other than see that work is properly partitioned into the classic collection and decision phases. Even less work is done by the operators (from, where, and select), which don’t have any function other than documentation. My test code uses a simple database of flags and colors, where the tables are implemented as lists.

Disclaimer: this oversimplified code is designed to illustrate the basic mechanics of this type of workflow. It is *not* to be considered ideal, practical or canonical! To solve this type of problem in production code, please use the F# Linq extensions; they are consistent, composable, and standard. (And as always, the code and information here are presented "as-is" and without warranty or implied fitness of any kind; use at your own risk.)

Credit: I think I first saw this idea from a web video, but I can’t recall where (possibly Channel9?). If anyone knows and can point me to it, I’ll be happy to revise this post to give credit to the source.

// Micro relational database.
/// ID,Value table.
type Rec = 
    Key : int;
    Value : string
  static member Make (k,v) =
    { Key=k; Value=v }
/// ID to ID relational link table.
type Link = 
    KeyFrom : int;
    KeyTo : int;
  static member Make (kf,kt) =
    { KeyFrom=kf; KeyTo=kt }
/// Micro query workflow.
/// Note the austere quality of the functions.
/// The real work is done by the collect in
/// the first bind and the decision in the second.
/// The workflow just sees to it that things 
/// get routed properly.
type MicroQuery () =
  // This de-sugars the "from" operator.
  member this.Bind (s,f) =
    Seq.collect f s 
  // This de-sugars the "where" operator.
  member this.Bind (b,f) =
    if b then f() else Seq.empty 
  // This de-sugars the "select" operator.
  member this.Return v =
    Seq.singleton v
// Since all the logic is contained
// in the workflow, these are not really
// necessary except as a form of
// self-documentation.
let inline from s = s
let inline where b = b
let inline select v = v
// For test data, I define some countries and
// the colors of their flags.  
// For consistency, I used the names 
// and spellings used in the USA.)
// If I left out your country, consider it 
// an exercise to the reader to add it!
let countries =
  [(0,"Australia");   (1,"Brazil");(2,"Germany");    
   (3,"Italy");       (4,"Mexico");(5,"Netherlands");
   (6,"South Africa");(7,"Spain"); (8,"USA")]
  |> Rec.Make  
let colors = 
   (3,"Green");(4,"Red"); (5,"White")]
 |> Rec.Make 
let flags =
  |> Link.Make 
/// This query will return the colors of
/// a country's flag.
let flagColors countryIn = MicroQuery () {
  let! country = from countries
  do! where country.Value=countryIn
  let! flag = from flags
  do! where country.Key=flag.KeyFrom
  let! color = from colors
  do! where color.Key=flag.KeyTo
  return select color.Value }
/// Test it:
let flagColorsBrazil = 
  flagColors "Brazil" 
  |> Seq.toList 
printfn "Your breakpoint here."

Friday, October 1, 2010

More Novice F# Computation Expression Bind/Return Mechanics

Today’s post is more F# “beginner” stuff I’m doing to help myself learn to think about workflows (or computation expressions as they are also called). As I’ve said before, I’ve gotten to the stage where I can usually puzzle out how to make a workflow do what I need it to do. However, my goal is to internalize the mechanics of workflows to the point where I can proactively recognize situations where they might be useful.

Paradoxically then, here is a particularly useless example. It does little more than simply perform a set of serial conversions. But it does illustrate the Bind/Return calling chain in a way that makes plain the “de-sugaring” mechanics. So I thought I would post it here. (As always, the code and information here are presented "as-is" and without warranty or implied fitness of any kind; use at your own risk.)

type Binder () =
  // Bind:0
  member this.Bind (v:int,f:string->float) =
    // v From Caller
    let down = v.ToString()
    let func = f down // Calls Bind:1
    let up = (float) func
    up // To Caller:
  // Bind:1
  member this.Bind (v:string,f:float->string) =
    // v From Bind:0
    let down = (float) v
    let func = f down // Calls Bind:2
    let up = (float) func
    up // To Bind:0
  // Bind:2
  member this.Bind (v:float,f:byte->double) =
    // v From Bind:1
    let down = (byte) v
    let func = f down // Calls Bind:3
    let up = func.ToString()
    up // To Bind:1
  // Bind:3
  member this.Bind (v:byte,f:byte list->int) =
    // v From Bind:2
    let down = [v;v]
    let func = f down // Calls Return
    let up = (double) func
    up // To Bind:2
  // Return
  member this.Return (v:byte list) = 
    // v From Bind:3
    let up = (int) (List.sum v)
    up // To Bind:3
let f = Binder () {
  let! x = 32 // Bind:0
  let! x =// Bind:1
  let! x =// Bind:2
  let! x =// Bind:3
  return x    // Return
printfn "Your breakpoint here."