This time we will wrap up the OO leg of our F# journey, by looking into using reflection. Before we start, I just want to point out that some of the examples that I will use here are either modified versions of some examples, or are actual examples by the fabulous Chris Sells, from his amazing “Programming F# 3.0 book”, which I can thoroughly recommend. In the start of that book it states that you are free to use small portions of the code without permission, I considered what I have taken to be a very small portion.
Chris has quite nicely shared all the code from the book on his blog, and it contains some real gems, I would urge you to check it out.
General Reflection
NOTE : This is examples in this section and from Chris Sells blog, which I link to at the top of this post
As F# is a member of the .NET family, we are of course to use many of the standard reflection APIs that you have undoubtedly used in your day to day C# lives. However F# also provides extra functions/types and helpers that may be used to deal with F# types. We will look at this F# specific stuff later on within this post.
typeof vs typedefof
F# comes with 2 typexxof operators, typeof<…>
and typedefof<..>
which are subtly different
typeof<..>
gives you a runtime representation of a static type typedefof<..>
gives a type definition of a static type.
To understand these differences lets see a small example:
printfn "typeof<List<int>> = %A" typeof<List<int>>
printfn "typedefof<List<int>> = %A" typedefof<List<int>>
Which when run will give the following results
Defining New Attributes
You have already some examples of attributes usage in F# in some of the previous posts we have done, for example you may have seen these in use
- [
<EntryPoint>
] - [
<AbstractClass>
]
These obviously assist the F# compiler, but attributes can be used to do more than this, they can also provide meta data to your own application, and you can reflect on them at run time to see if a type supports certain things/contains certain meta data / control how object are serialized, there are in fact a lot of use cases for attributes. So it should come as no surprise that you should learn how to make your own attributes in F#.
Thankfully this is trivial, we will just build on our new formed OO knowledge and inherit from attribute, and create a new custom attribute.
[<AttributeUsage(AttributeTargets.Class)>]
type ClassDescriptionAttribute(desc) =
inherit Attribute()
member this.Description = desc
As you can see this is a new attribute which may only target classes, and will be used to describe a class.
Reflecting On Types
As previously stated, F# is a member of the .NET family so you are free to use any of the types/methods/properties within the standard CLR System.Reflection
namespace. One thing you will likely have to do quit often is reflect on a particular type, so lets see an example of that:
let describeType (ty : Type) =
let bindingFlags =
BindingFlags.Public ||| BindingFlags.NonPublic |||
BindingFlags.Instance ||| BindingFlags.Static |||
BindingFlags.DeclaredOnly
let methods =
ty.GetMethods(bindingFlags)
|> Array.fold (fun desc meth -> desc + sprintf "%s\r\n" meth.Name) ""
let props =
ty.GetProperties(bindingFlags)
|> Array.fold (fun desc prop -> desc + sprintf "%s\r\n" prop.Name) ""
let fields =
ty.GetFields(bindingFlags)
|> Array.fold (fun desc field -> desc + sprintf "%s\r\n" field.Name) ""
printfn "Name: %s" ty.Name
printfn "Methods: \n\t%s\n" methods
printfn "Properties: \n\t%s\n" props
printfn "Fields: \n\t%s" fields
Which you can use like this
let s = new StringWriter()
do describeType(s.GetType())
Which when run gives the following results:
Inspecting Attributes
Inspecting attributes is also very easy (well thanks to Chris Sells blog that). Say we had these types:
[<AttributeUsage(AttributeTargets.Class)>]
type ClassDescriptionAttribute(desc) =
inherit Attribute()
member this.Description = desc
[<AttributeUsage(AttributeTargets.Method)>]
type MethodDescriptionAttribute(desc) =
inherit Attribute()
member this.Description = desc
type Widget =
| RedWidget
| GreenWidget
| BlueWidget
[<ClassDescription("Represents a stack of Widgets.")>]
type WidgetStack() =
let mutable m_widgets : Widget list = []
[<MethodDescription("Pushes a new Widget onto the stack.")>]
member this.Push(x) = m_widgets <- x :: m_widgets
We can easily inspect the attributes and get their values using the following code (again thanks Chris Sells):
let printDocumentation(ty : Type) =
let objHasType ty obj = (obj.GetType() = ty)
let classDescription : string option =
ty.GetCustomAttributes(false)
|> Seq.tryFind(objHasType typeof<ClassDescriptionAttribute>)
|> Option.map(fun attr -> (attr :?> ClassDescriptionAttribute))
|> Option.map(fun cda -> cda.Description)
let methodDescriptions : seq<string * string option> =
ty.GetMethods()
|> Seq.map(fun mi -> mi, mi.GetCustomAttributes(false))
|> Seq.map(fun (methodInfo, methodAttributes) ->
let attributeDescription =
methodAttributes
|> Seq.tryFind(objHasType typeof<MethodDescriptionAttribute>)
|> Option.map(fun atr -> (atr :?> MethodDescriptionAttribute))
|> Option.map(fun mda -> mda.Description)
methodInfo.Name, attributeDescription)
let getDescription = function
| Some(desc) -> desc
| None -> "(no description provided)"
printfn "Info for class: %s" ty.Name
printfn "Class Description:\n\t%s" (getDescription classDescription)
printfn "Method Descriptions:"
methodDescriptions
|> Seq.iter(fun (methName, desc) ->
printfn "\t%15s - %s" methName (getDescription desc))
Which when run with this demo code, will produce the output shown below
Properties, Get A Value, Set A Value
To get and set a property value is also trivial, we can just get a property using the standard System.Reflection namespace and then use the GetValue(..)
and SetValue(..)
methods exposed by the CLR.
I kind of feel a bit guilty using so many of Chris’s bit of code, but it is just so good, and highly enlightening. For example what is shown below, is very cool in that it shows how to create a new property get/set operator which behind the scenes uses reflection:
type Book(title, author) =
let mutable m_currentPage : int option = None
member this.Title = title
member this.Author = author
member this.CurrentPage with get () = m_currentPage
and set x = m_currentPage <- x
override this.ToString() =
match m_currentPage with
| Some(pg) -> sprintf "%s by %s, opened to page %d" title author pg
| None -> sprintf "%s by %s, not currently opened" title author
let (?) (thingey : obj) (propName: string) : 'a =
let propInfo = thingey.GetType().GetProperty(propName)
propInfo.GetValue(thingey, null) :?> 'a
let (?<-) (thingey : obj) (propName : string) (newValue : 'a) =
let propInfo = thingey.GetType().GetProperty(propName)
propInfo.SetValue(thingey, newValue, null)
Which we can use as follows, take note of the “?” operators in action (which are really just functions, thus is the awesomeness of F#):
let book = new Book("Foundation", "Asimov")
printfn "%A" book.CurrentPage
book?CurrentPage <- Some(14)
printfn "%A" book.CurrentPage
let currentPage : int option = book?CurrentPage
printfn "%A" currentPage
book?CurrentPage <- Some(24)
printfn "%A" book.CurrentPage
let currentPage : int option = book?CurrentPage
printfn "%A" currentPage
Which when run gives us the following results:
F# Specific Reflection
What we have discussed so far has been how to leverage the existing .NET reflection namespace, which is totally fine, but what does F# have to offer us by way of reflection APIs.
F# actually offers a few modules to help with reflection of F# types. Lets have a brief look at these modules
Reflection.FSharpType
Contains operations associated with constructing and analyzing F# types such as records, unions and tuples. You can read more about this module here: http://msdn.microsoft.com/en-us/library/ee370530.aspx
Here is what is available within this module:
Here are some examples of this module
open Microsoft.FSharp.Reflection
type ContactCard =
{ Name : string;
Phone : string; }
type Suit =
| Hearts
| Clubs
| Diamonds
| Spades
type Rank =
| Value of int
| Ace
| King
| Queen
| Jack
....
....
let aTuple = (12,13)
let aFunction() =
printfn "Im function"
let aRecord = { Name = "Alf" ; Phone = "(206) 555-0157" ; }
let aDiscrimatingUnion1 = Value(3)
let aDiscrimatingUnion2 = Rank.Ace
printfn "aTuple IsTuple = %A" (FSharpType.IsTuple(aTuple.GetType()))
printfn "aFunction IsTuple = %A" (FSharpType.IsTuple(aFunction.GetType()))
printfn "aTuple IsFunction = %A" (FSharpType.IsFunction(aTuple.GetType()))
printfn "aFunction IsFunction = %A" (FSharpType.IsFunction(aFunction.GetType()))
printfn "aRecord IsRecord = %A" (FSharpType.IsRecord(aRecord.GetType()))
printfn "aDiscrimatingUnion1 IsUnion = %A" (FSharpType.IsUnion(aDiscrimatingUnion1.GetType()))
printfn "aDiscrimatingUnion2 IsUnion = %A" (FSharpType.IsUnion(aDiscrimatingUnion2.GetType()))
Which when run will give the following output
Reflection.FSharpValue
Contains operations associated with constructing and analyzing values associated with F# types such as records, unions and tuples. You can read more about this module here: http://msdn.microsoft.com/en-us/library/ee353505.aspx
Here is what is available within this module:
Here are some examples of this module
open Microsoft.FSharp.Reflection
open System.Reflection
type ContactCard =
{ Name : string;
Phone : string; }
type Suit =
| Hearts
| Clubs
| Diamonds
| Spades
type Rank =
| Value of int
| Ace
| King
| Queen
| Jack
....
....
let aRecord = { Name = "Alf" ; Phone = "(206) 555-0157" ; }
let aDiscrimatingUnion1 = Value(3)
let aDiscrimatingUnion2 = Rank.Ace
printfn "GetRecordFields(aRecord, BindingFlags.Public) = %A"
(FSharpValue.GetRecordFields(aRecord, BindingFlags.Public))
printfn "GetUnionFields(aDiscrimatingUnion1, aDiscrimatingUnion2.GetType()) = %A"
(FSharpValue.GetUnionFields(aDiscrimatingUnion1, aDiscrimatingUnion2.GetType()))
printfn "GetUnionFields(aDiscrimatingUnion2, aDiscrimatingUnion2.GetType()) = %A"
(FSharpValue.GetUnionFields(aDiscrimatingUnion2, aDiscrimatingUnion2.GetType()))
Which when run gives the following results
Reflection.UnionCaseInfo
Represents a case of a discriminated union type. You can read more about this module here: http://msdn.microsoft.com/en-us/library/ee370473.aspx
Here is what is available within this module: