My solution to this was to define a type which encompassed all the fields I might be interested in, and then used a pattern match in the function to pick out the correct field. This presupposes you know all the fields you will want to get beforehand, but works for my problem, which included taking account of the differences in units

1
2
3
4
5
6
7
8
9
type cell = {age:float<seconds>; radius:float<um>; pressure:float<Pa>}
type descriptor = Size | Age | Pressure

let f (c:cell) (d:descriptor) =
    match d with
    | Size -> float c.radius
    | Age -> float c.age
    | Pressure -> float c.pressure
By on 1/24/2014 8:55 AM ()

Hi,
reflection is probably the best alternative (meaning that it will internaly use .NET reflection), but you can simplify the code by using F# reflection library and also use it together with F# quotations to get a surprising degree of compile-time checking.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
 
#light

open System
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Typed
open Microsoft.FSharp.Quotations.Raw

type SampleRec = { 
  Str : string
  Num : int }
  
// This function takes a quotation with a "hole" (which is represented as a function)
// The type 'a is a type of the record and the type 'b is a type of the field, so we can
// use this type information and return a function 'a -> 'b which takes a record value
// and reads the field (you can of course remove the "unbox" function and it would
// return just 'obj' type, which may be sometimes more useful)
let getValueReader (prop:Expr<'a> -> Expr<'b>) = 
  let rcTy = typeof<'a>
  // fill in the hole with some dummy quotation - we just want to get 
  // the representation of quotation as "Expr" and not as a function
  let expr = prop (Typed.of_raw (MkHole rcTy))
  // Analyze the qutotation...
  match expr.Raw with
  | RecdGet (ty, nm, expr) ->
      // It represents an access to a record value, so we can use F# Reflection
      // to get a function "rdr" that reads a specified field of the record
      let rdr = Reflection.Value.GetRecordFieldReader (ty, nm)
      ((box >> rdr >> unbox) : 'a -> 'b)
  | _ -> 
      // Not an access to a record field - fail
      failwith "Invalid expression - not reading record field!"
  
// Create a 'reader' from a quotation
// Note that these two are typed (SampleRec -> string / int)
let rdS = getValueReader <@ (_ : SampleRec).Str @>   
let rdN = getValueReader <@ (_ : SampleRec).Num @>      

let rc = { Str = "Hello world!"; Num = 42 }
let v1 = rdS rc
let v2 = rdN rc

printfn "Extracted: %s, %d" v1 v2
Console.ReadLine() |> ignore

BTW: This is a really interesting question, so thanks for it! I'm really interested in hearing more about your problem and whether this solution will be helpful for you.

T.

By on 5/23/2008 3:40 AM ()

Hi Tomas,

Thanks a lot for your detailed solution and sorry for my late reply, it is great to learn some of these cool F# features.

Thanks also for your interest. My idea was to translate a program in my domain specific language (for modelling biochemical systems) into an F# program which, when executed, would produce some target mathematical model (e.g. differential equations or Petri nets). My hope was that the DSL program would be well-typed iff the target F# program was well-typed.

While my DSL shares some features with F# that could be elegantly addressed by such a translation, there are also some features which do not map neatly into F# (e.g. mechanisms for returning stuff from modules), and the requirement in F# to declare records and do so in a "non-compositional" manner also posed difficulties.

On a more practical level, I wonder for future reference if there is an easy way to programatically control the F# type checker and compiler. This would be necessary in order to translate F# type errors back into error messages appropriate for the DSL.

In any case, I have decided that the safest and most robust approach is to write a translator for my DSL in the traditional way. Sigh.

Best regards,
Michael.

By on 5/25/2008 11:07 PM ()

Hi Michael,
that looks like an interesting project. Having some way for adding additioal checking to custom DSLs in F# (e.g. by extending the compiler) seems like very useful idea to me and I encountered situation where this would be useful too.

Another possibility is using F# quotations - this lets you express the DSL in F# language (with te usual type cheking and usual function composiion etc.), while you can add some additional checking when processing the quotation. For example you could write:

1
2
3
let expr = <@ for_all numbers (fun x -> x > 0) (* .. any F# code here .. *) @>
verifyExpr expr
runExpr expr

The verifyExpr function could then analyze the quotation (some sort of F# AST) and ensure that all your additional rules hold. runExpr would then execute the DSL in some way (unfortunatelly it is not yet possible to compile it back to executable code and just run it). But I agree that having some mechanism for checking DLSs directly in F# would make things a lot easier. Using quotations for this may not be completely appropriate.

BTW: You can find some quotation samples here: [link:tomasp.net]

By on 5/26/2008 5:00 PM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper