Using and Abusing the F# Dynamic Lookup Operator

Lately, I’ve been playing with such things as MongoDB using F# to rapidly prototype ideas.  With that, I’ve tried to rid myself of magic strings by using the F# dynamic lookup operator.  I’ll cover exactly what I’m doing in the next post when using MongoDB, but in this post I’d like to explore a little of what you could do with a little noticed dynamic lookup operator in F#.

The Dynamic Lookup Operator

Much like C# 4.0 has the ability to do dynamic lookup, F# also has the same capability, although in a different capacity.  The language has support for a dynamic lookup get operator ( ? ) and set operator ( ?<- ), but note that I said support and not actual implementation.  The actual implementation is up to you and how you want to use it. 

The Get Operator

Let’s start off by implementing the get operator to get a public property (not super useful yet).

let (?) (this : 'Source) (prop : string) : 'Result =
  let p = this.GetType().GetProperty(prop)
  p.GetValue(this, null) :?> 'Result

We can write an example that verifies the behavior such as comparing a normal invocation and our “dynamic” invocation, as in this example of comparing getting the length of a string.

[<Fact>]
let ``Dynamic lookup should equal normal``() =
  areEqual "foo".Length "foo"?Length

But, we’re not limited to that.  For example, we could instead decide that we want to get a private member instead of a public one.  We could change up our example from above just slightly.

let (?) (this : 'Source) (prop : string) : 'Result =
  let flags = BindingFlags.GetProperty |||
              BindingFlags.NonPublic
  let p = this.GetType().GetProperty(prop, flags)
  p.GetValue(this, null) :?> 'Result

We can verify our behavior of this idea with a little example, for example invoking a private property in a custom class such as the following:

type PrivateProperty(propValue : string) =
  member private __.PropValue
    with get() = propValue
    
[<Fact>]
let ``Can invoke private property``() =
  let expected = "foo"
  let p = PrivateProperty(expected)
  
  let actual : string = p?PropValue
  
  areEqual expected actual

So, as you can see, this is quite useful so far, but we’ve only scratched the surface.  We’ve just dealt with properties so far, but what about methods?  The same applies here as well.  In this instance, let’s take a method that could have any number of arguments and invoke it just as if we would normally.

open Microsoft.FSharp.Reflection
open System.Reflection

let (?) (this : 'Source) (member' : string) (args : 'Args) : 'Result =
  let argArray =
    if box args = null then null
    elif FSharpType.IsTuple (args.GetType()) then
      FSharpValue.GetTupleFields args
    else [|args|]

  let flags = BindingFlags.GetProperty ||| BindingFlags.InvokeMethod
  this.GetType().InvokeMember(member', flags, null, this, argArray) :?> 'Result

What we needed to do here is take not only the member but the arguments as well.  If the arguments object is null (or unit), then we pass a null objet array, else if our type is a tuple, then we create an array from our tuple, and finally if it’s a single argument, then we make it into an array.  Finally we invoke our member with the flags to say it is either a property getter or a method and invoke with our arguments.  Let’s write an example of how to use this.

[<Fact>]
let ``Can invoke method with dynamic lookup``() =
  isTrue ("dynamic"?Length() > 0)
  isTrue ("dynamic"?EndsWith("ic"))
  areEqual "dyn" ("dynamic"?Substring(0,3))

Above are three tests which show no arguments, a single argument, and finally tupled arguments.  But what about setters?

The Set Operator

Just as we have the ( ? ) operator reserved for the get operator, we have the ( ?<- ) operator reserved for the set.  This allows us to be able to set any number of things with the same ease as the get.  For example, we could set a public property:

let (?<-) (this : 'Source) (property : string) (value : 'Value) =
  this.GetType().GetProperty(property).SetValue(this, value, null)

This simply gets the property and then sets the appropriate value.  And then we can write a test as an example of how it should work.

type MutablePropertyClass(value : string) =
  let mutable v = value
  
  member __.Value 
    with get() = v
    and  set(value) = v <- value
    
[<Fact>]    
let ``Dynamic setter should set property``() =    
  let m = MutablePropertyClass("foo")
  
  m?Value <- "dynamic"
  
  areEqual "dynamic" m.Value

In this example, we have a simple class with a property with a getter and setter and then we set our property using our special operator and then compare the values.  Once again, this is flexible enough where we could set private properties much as we retrieved them above.  Now, let’s put them together for a few quick examples.

The Dictionary Example

One idea that we could go with is instead of using string keys for dealing with key value pairs, we could use our dynamic getter and setter instead.  Nothing like a bit of syntactic sugar to brighten your day to lessen the noise.  Our goal for this is to go from:

let d = Dictionary<string,obj>()
d.["StaticKey"] <- "Meh"

To something like the following:

let d = Dictionary<string,obj>()
d?DynamicKey <- "Totally!"

How could we pull this one off?  Well, we could implement the dynamic lookup getter and setter to do nothing more than look in the dictionary as follows:

open System.Collections

let (?) (this : #IDictionary) key =
  this.[key]

let (?<-) (this : #IDictionary) key value =
  this.[key] <- value

Now we can verify our behavior with our test that we should have written first.

let ``Dictionary should get and set dynamically``() = 
  let d = Dictionary<string,obj>()
  
  d?DynamicKey <- "hello"
  
  areEqual d.["DynamicKey"] d?DynamicKey

A solution like this could work well for situations like MongoDB, which we’ll get into with our next post.  But, could go a step further and to say anything with the Item property could be ours for the taking.

The Item Indexer Property Example

In the .NET libraries, we have a few classes which expose an indexer property like an array, a Dictionary, and even a string.  It would be nice to have a way to both get and set this value through the use of our dynamic lookup operator, but how could we pull this off?  In some previous posts, I talked about generic restrictions in the F# language, and one in particular stands out, the member restriction operator.  This operators lets us constrain a given object parameter so that it must implement the following methods and properties.

Remember, our overall goal is to support calling this:

open System.Collections.Generic

let d = Dictionary<string,string>()
d?DynamicKey <- "Totally!"

type IndexedObject() = 
  let d = Dictionary<string,string>()

  member self.Item
    with get(key) =
      d.[key]
    and set key value =
      d.[key] <- value
      
let i = IndexedObject()
h?IndexedKey <- "IndexedValue"

So, what we have is both a Dictionary<TKey,TValue> and a custom indexed object.  Since they do not follow an inheritance chain which exposes an Item property, we cannot use the example from above with the IDictionary.  Instead, we’ll use member restrictions for both the method get_Item and set_Item.

let inline (?) this key =
  ( ^a : (member get_Item : ^b -> ^c) (this,key))
 
let inline (?<-) this key value =
  ( ^a : (member set_Item : ^b * ^c -> ^d) (this,key,value))

What you see is that we’re calling both the get_Item and set_Item properties, which are the raw method names for the Item property.  Now we’re able to verify our test using our IndexedObject class from above:

let ``Can use dynamic indexed getter and setter``() =
  let i = IndexedObject()
  
  i?SomeKey <- "Some value"
  
  areEqual i.["SomeKey"] i?SomeKey

A big word of caution when using this approach is that you’ll get a nice warning from the F# compiler that doing this may cause unverified code as F# treats the Item property specially.  For example, all value types must be boxed before putting them into the collection.

let h = Hashtable()
h?MyKey <- 3 // Don't do this

h?MyKey <- box 3 // Do this

Very powerful way of doing things but also you must be careful with this approach if you use it at all.

Conclusion

So, as you can see, F#, just like C# has the facilities for dynamic lookup, although in F# you are left to implement it yourself in any which way you like.  One great thing about these operator is that it doesn’t require .NET 4.0 to implement and use them.  But with any power comes of course some responsibility for it, so use it wisely.

This entry was posted in F#. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://undermyhat.org/ Abel Braaksma

    @Ryan (5yrs after your comment!), the same issue occurs in F# in VS2010 and VS2012. You should change the first line of the first example as follows:
    let inline (?) (this : ^Source) (prop : string) : ^Result = …….

    Then it’ll work as you’d expect.

  • http://wizardsofsmart.net/ Ryan Riley

    This is really cool stuff. The dictionary and indexer stuff worked for me, but the property and method invocation failed with:

    stdin(381,1): error FS0030: Value restriction. The value ‘it’ has been inferred to have generic type
    val it : ‘_a
    Either define ‘it’ as a simple data term, make it a function with explicit arguments or, if you do not intend for it to
    be generic, add a type annotation.

    When trying to create the IndexedObject type, I got:
    stdin(403,25): error FS0436: The property ‘Item’ has the same name as another property in this type, but one takes indexer arguments and the other does not. You may be missing an indexer argument to one of your properties.
    > type IndexedObject() =
    – let mine = Dictionary()
    – member self.Item
    – with get(key) =
    – mine.[key]
    – and set(key, value) =
    – mine.[key] <- value;;

    with get(key) =
    ———^^^

    stdin(408,10): error FS0436: The property ‘Item’ has the same name as another property in this type, but one takes indexer arguments and the other does not. You may be missing an indexer argument to one of your properties.

    I’m using the VS2008 fsi. Could that be the problem?

    Thanks,
    Ryan

  • http://wizardsofsmart.net/ Ryan Riley

    This is terrific stuff, Matt. Thanks for sharing! Btw, is there a way to execute xUnit tests from an fsi session?

    Thanks,
    Ryan

  • http://codebetter.com/members/Matthew.Podwysocki/default.aspx Matthew.Podwysocki

    @Cesar,

    You’re right, the inlining has everything to do with it, to allow for the member restrictions. It’s something I forget once in a while until the compiler complains

    Matt

  • http://codebetter.com/members/Paks/default.aspx Cesar Mendoza

    I think it should be stressed that the “inline” keyword is very important when using member restrictions. Otherwise we would get a compiler error or warnings about the code being less generic. That surprised me the first time I tried to use member restrictions.