I'm trying to use AutoMapper from F#, but I'm having trouble setting it up due to AutoMapper's heavy use of LINQ Expressions.
Specifically, the AutoMapper type IMappingExpression<'source, 'dest> has a method with this signature:
ForMember(destMember: Expression<Func<'dest, obj>>, memberOpts: Action<IMemberConfigurationExpression<'source>>)
This is typically used in C# like this:
Mapper.CreateMap<Post, PostsViewModel.PostSummary>()
.ForMember(x => x.Slug, o => o.MapFrom(m => SlugConverter.TitleToSlug(m.Title)))
.ForMember(x => x.Author, o => o.Ignore())
.ForMember(x => x.PublishedAt, o => o.MapFrom(m => m.PublishAt));
I made an F# wrapper that arranges things so that type inference can work. This wrapper allows me to translate the C# example above into something like this:
Mapper.CreateMap<Post, Posts.PostSummary>()
|> mapMember <# fun x -> x.Slug #> <# fun m -> SlugConverter.TitleToSlug(m.Title) #>
|> ignoreMember <# fun x -> x.Author #>
|> mapMember <# fun x -> x.PublishedAt #> <# fun m -> m.PublishAt #>
|> ignore
This code compiles, and it seems pretty clean as far as syntax and usage. However, at runtime AutoMapper tells me this:
AutoMapper.AutoMapperConfigurationException: Custom configuration for members is only supported for top-level individual members on a type.
I presume this is caused by the fact that I have to convert Expr<'a -> 'b> into Expression<Func<'a, obj>>. I convert the 'b to obj with a cast, which means my lambda expression is no longer simply a property access. I get the same error if I box the property value in the original quotation, and don't do any splicing at all inside forMember (see below). However, if I don't box the property value, I end up with Expression<Func<'a, 'b>> which does not match the parameter type that ForMember expects, Expression<Func<'a, obj>>.
I think that this would work if AutoMapper's ForMember was completely generic, but forcing the return type of the member access expression to be obj means that I can only use it in F# for properties that are already directly of type obj and not a subclass. I can always resort to using the overload of ForMember that takes the member name as a string, but I thought I'd check to see if anyone has a brilliant work-around before I give up on compile-time typo-checking.
I'm using this code (plus the LINQ part of F# PowerPack) to convert an F# quotation into a LINQ expression:
namespace Microsoft.FSharp.Quotations
module Expr =
open System
open System.Linq.Expressions
open Microsoft.FSharp.Linq.QuotationEvaluation
// http://stackoverflow.com/questions/10647198/how-to-convert-expra-b-to-expressionfunca-obj
let ToFuncExpression (expr:Expr<'a -> 'b>) =
let call = expr.ToLinqExpression() :?> MethodCallExpression
let lambda = call.Arguments.[0] :?> LambdaExpression
Expression.Lambda<Func<'a, 'b>>(lambda.Body, lambda.Parameters)
This is the actual F# wrapper for AutoMapper:
namespace AutoMapper
/// Functions for working with AutoMapper using F# quotations,
/// in a manner that is compatible with F# type-inference.
module AutoMap =
open System
open Microsoft.FSharp.Quotations
let forMember (destMember: Expr<'dest -> 'mbr>) (memberOpts: IMemberConfigurationExpression<'source> -> unit) (map: IMappingExpression<'source, 'dest>) =
map.ForMember(Expr.ToFuncExpression <# fun dest -> ((%destMember) dest) :> obj #>, memberOpts)
let mapMember destMember (sourceMap:Expr<'source -> 'mapped>) =
forMember destMember (fun o -> o.MapFrom(Expr.ToFuncExpression sourceMap))
let ignoreMember destMember =
forMember destMember (fun o -> o.Ignore())
Update:
I was able to use Tomas's sample code to write this function, which produces an expression that AutoMapper is satisfied with for the first argument to IMappingExpression.ForMember.
let toAutoMapperGet (expr:Expr<'a -> 'b>) =
match expr with
| Patterns.Lambda(v, body) ->
// Build LINQ style lambda expression
let bodyExpr = Expression.Convert(translateSimpleExpr body, typeof<obj>)
let paramExpr = Expression.Parameter(v.Type, v.Name)
Expression.Lambda<Func<'a, obj>>(bodyExpr, paramExpr)
| _ -> failwith "not supported"
I still need the PowerPack LINQ support to implement my mapMember function, but they both work now.
If anyone is interested, they can find the full code here.
Now that F# is happy to generate a Expression<Func<...>> directly from a fun expression, this is relatively easy to solve. The biggest problem now is that the F# compiler seems to get confused by the overloading of the ForMember method, and is unable to infer correctly what you want. This can be circumvented by defining an extension method with a different name:
type AutoMapper.IMappingExpression<'TSource, 'TDestination> with
// The overloads in AutoMapper's ForMember method seem to confuse
// F#'s type inference, forcing you to supply explicit type annotations
// for pretty much everything to get it to compile. By simply supplying
// a different name,
member this.ForMemberFs<'TMember>
(destGetter:Expression<Func<'TDestination, 'TMember>>,
sourceGetter:Action<IMemberConfigurationExpression<'TSource, 'TDestination, 'TMember>>) =
this.ForMember(destGetter, sourceGetter)
You can then use the ForMemberFs method more or less as the original ForMember is intended to work, e.g.:
this.CreateMap<Post, Posts.PostSummary>()
.ForMemberFs
((fun d -> d.Slug),
(fun opts -> opts.MapFrom(fun m -> SlugConverter.TitleToSlug(m.Title)))
I'm not quite sure how to fix the generated expression tree (that's doable by post-processing it, but it is pain to find out what AutoMapper expects). However, there are two alternatives:
As a first option - the expressions that you need to translate are fairly simple. They are mostly just method calls, property getters and uses of a variable. This means that it should be possible to write your own quotation to expression trees translator that produces exactly the code you want (then you can also add your own handling of obj, perhaps by calling Expression.Convert to build the expression tree). I wrote a simple quotation tranlsator as a sample, which should handle most of the stuff in your sample.
As a second option - if AutoMapper provides an option to specify just a property name - you could just use quotations of the form <# x.FooBar #>. These should be quite easy to deconstruct using the Patterns.PropertyGet pattern. The API should maybe look like this:
Mapper.CreateMap<Post, Posts.PostSummary>(fun post summary mapper ->
mapper |> mapMember <# post.Slug #> // not sure what the second argument should be?
|> ignoreMember <# post.Author #> )
Or, in fact, you could use this style of API even in the first case, because you don't need to write lambda expressions repeatedly for every single mapping, so maybe it is a bit nicer :-)
Related
I am trying to develop F# type provider.
It provides some DTOs (with the structure described in some external document) and a set of methods for processing them. The processing algorithm is based on reflection, and I want to have a single quotation representing it.
Generally, this algorithm must pass all method call arguments to the already written function serialize: obj -> MySerializationFormat, storing all results in a list, so I getting a value of MySerializationFormat list.
Code sample below shows, how I tried to do that for first time:
let serialize (value: obj) = ...
let processingCode: Expr list -> Expr =
fun args ->
let serializeArgExpr (arg: Expr) = <# serialize %%arg} #>
let argsExprs = List.map serializeArgExpr args
let serializedArgList =
List.foldBack (fun head tail -> <# (%head) :: (%tail)#>) argsExprs <# [] #>
// futher processing
At that point I faced with exception: In function serializeArgExpr the actual type of value in arg: Expr may vary, it can be some primitive type (e.g string, int, float), or some provided type. The problem is %% operator treats that arg as an expression of the obj type. Type check is performed on that line in Microsoft.FSharp.Quotations.Patterns module, in function fillHolesInRawExpr.
So, as the actual type of my term not matched the treated type for "hole" in the quotation, it throws invalidArg.
I have tried several technics to avoid these exceptions with casting operations in my quotation, but they don't work. Then I found Expr.Coerce(source, target) function, which looks like solving my problem. I have changed the code of serializeArgExpr to something like that:
let serializeArgExpr (arg: Expr) =
let value' = Expr.Coerce(value, typeof<obj>)
<# serialize %%value' } #>
Then faced a new strange exception:
The design-time type (point to a code line that uses my processingCode) utilized by a type provider was not found in the target reference assembly set
For me, it seems that my problem is to cast the type of value in any input Expr to an obj type. Thank you for diving in and trying to help.
given
[
1,"test2"
3,"test"
]
|> dict
// turn it into keyvaluepair sequence
|> Seq.map id
|> fun x -> x.ToDictionary<_,_,_>((fun x -> x.Key), fun x -> x.Value)
which fails to compile if I don't explicitly use the <_,_,_> after ToDictionary.
Intellisense works just fine, but compilation fails with the error: Lookup on object of indeterminate type based on information prior to this program point
So, it seems, Intellisense knows how to resolve the method call.
This seems to be a clue
|> fun x -> x.ToDictionary<_,_>((fun x -> x.Key), fun x -> x.Value)
fails with
Type constraint mismatch.
The type 'b -> 'c is not compatible with type IEqualityComparer<'a>
The type 'b -> 'c' is not compatible with the type 'IEqualityComparer<'a>'
(using external F# compiler)
x.ToDictionary((fun x -> x.Key), id)
works as expected as does
let vMap (item:KeyValuePair<_,_>) = item.Value
x.ToDictionary((fun x -> x.Key), vMap)
I've replicated the behavior in FSI and LinqPad.
As a big fan of and avid reader of Eric Lippert I really want to know
what overload resolution, (or possibly extension methods from different places) are conflicting here that the compiler is confused by?
Even though the types are known ahead, the compiler's getting confused between the overload which takes an element selector and a comparer. The lambda compiles to FSharpFunc rather than the standard delegate types in C# like Action or Func, and issues do come up translating from one to the other. To make it work, you can :
Supply a type annotation for the offending Func
fun x -> x.ToDictionary((fun pair -> pair.Key), (fun (pair : KeyValuePair<_, _>) -> pair.Value)) //compiles
or name the argument as a hint
fun x -> x.ToDictionary((fun pair -> pair.Key), elementSelector = (fun (pair) -> pair.Value))
or force it to pick the 3 argument version:
x.ToLookup((fun pair -> pair.Key), (fun (pair) -> pair.Value), EqualityComparer.Default)
Aside
In your example,
let vMap (item:KeyValuePair<_,_>) = item.Value
x.ToDictionary((fun x -> x.Key), vMap)
you would explicitly need to annotate vMap because the compiler cannot find out what type the property exists on without another pass. For example,
List.map (fun x -> x.Length) ["one"; "two"] // this fails to compile
This is one of the reasons why the pipe operator is so useful, because it allows you to avoid type annotations:
["one"; "two"] |> List.map (fun x -> x.Length) // works
List.map (fun (x:string) -> x.Length) ["one"; "two"] //also works
The short answer:
The extension method ToDictionary is defined like this:
static member ToDictionary<'TSource,_,_>(source,_,_)
but is called like this:
source.ToDictionary<'TSource,_,_>(_,_)
The long answer:
This is the F# type signature of the function you are calling from msdn.
static member ToDictionary<'TSource, 'TKey, 'TElement> :
source:IEnumerable<'TSource> *
keySelector:Func<'TSource, 'TKey> *
elementSelector:Func<'TSource, 'TElement> -> Dictionary<'TKey, 'TElement>
But I only specified two regular parameters: keySelector and elementSelector. How come this has a source parameter?!
The source parameter is actually not put in the parenthesis, but is passed in by saying x.ToDictionary, where x is the source parameter. This is actually an example of a type extension. These kinds of methods are very natural in a functional programming language like F#, but more uncommon in an object oriented language like C#, so if you're coming from the C# world, it will be pretty confusing. Anyway, if we look at the C# header, it is a little easier to understand what is going on:
public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector
)
So the method is defined with a "this" prefix on a first parameter even though it is technically static. It basically allows you to add methods to already defined classes without re-compiling or extending them. This is called prototyping. It's kinda rare if you're a C# programmer, but languages like python and javascript force you to be aware of this. Take this example from https://docs.python.org/3/tutorial/classes.html:
class Dog:
tricks = [] # mistaken use of a class variable
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks # unexpectedly shared by all dogs
['roll over', 'play dead']
The method add_trick is defined with self as a first parameter, but the function is called as d.add_trick('roll over'). F# actually does this naturally as well, but in a way that mimics the way the function is called. When you declare:
member x.doSomething() = ...
or
member this.doSomething() = ...
Here, you are adding function doSomething to the prototype (or class definition) of "x"/"this". Thus, in your example, you actually have three type parameters, and three regular parameters, but one of them is not used in the call. All you have left is to declare the key selector function, and the element selector function, which you did. That's why it looks weird.
I am encountering this Function type that I need to pass to a JQueryAnimationOptions object. I usually would pass a lambda to callbacks but these seem to be incompatible. I looked up every sample I could find in the FunScript repo. and couldn't find any workaround.
It also said the the Function is actually an interface (for what?) when used as a return statement Error: Invalid use of interface type.
So how to pass a callback argument with this Function type?
the code:
[<FunScript.JS>]
module Main
open FunScript
open FunScript.TypeScript
let sayHelloFrom (name:string) =
Globals.window.alert("Hello, " + name)
let jQuery (selector:string) = Globals.jQuery.Invoke selector
let main() =
let options = createEmpty<JQueryAnimationOptions>()
options.duration <- 3000
options.complete <- (fun _ -> sayHelloFrom("F#"))
let properties = createEmpty<Object>()
properties.Item("opacity") <- 1
let mainContent = jQuery "#mainContent"
mainContent.animate(properties, options) |> ignore
mainContent.click(fun e -> sayHelloFrom("F#") :> obj)
This works more or less as you would expect when passing lambdas between F# and C#. In F#, functions can be curried, while in C# (and JavaScript) cannot. So when you need to send a lambda from F# to C# you need to convert it first. In F# this is done by wrapping the lambda like this:
open System.Linq
open System.Collections.Generic
let ar = [|1;2;3|]
let f = fun (x: int) (y: int) -> x + y
let acc = ar.Aggregate( System.Func<int,int,int>(f) )
Actually, the F# compiler can deduce the types most of the times, so you only need to write: System.Func<_,_,_>(f). Furthermore, when passing a F# lambda to a method expecting a C# lambda, the compiler makes the wrapping automatically for you. Then the previous example becomes:
let ar = [|1;2;3|]
let acc = ar.Aggregate( fun x y -> x + y )
(Of course, in this case it would be better to use the idiomatic Array.reduce. This is just a contrived example.)
This works exactly the same when interacting with JS using FunScript. The only thing you need to be aware of is how F# lambdas get translated to JS. To allow currying, a lambda with two or more parameters like fun x y -> x + y becomes:
function (x) {
return function (y) {
return x + y;
}
}
Which may be a problem because the native JS will expect the following signature: function (x, y). In that case, you would have to wrap the lambda with System.Func<_,_,_>() as when interacting with C# (remember this is done automatically if you pass the lambda to a method).
However, lambdas with just one parameter don't suppose any problem: fun x -> x*x becomes function (x) { return x*x; }. In that case you don't need to wrap them (it doesn't hurt to do it anyway) and it's enough just to use unbox to appease the F# compiler when necessary. Just please be aware the FunScript compiler ignores unbox in the final JS code so there'll be no type check at all at runtime.
I hope the explanation is clear. Please add a comment if it isn't and I'll edit the answer.
Nevermind , I found the solution, I had to unbox the lambda:
options.complete <- unbox<Function> (fun _ -> sayHelloFrom("F#"))
let f (O: obj) =
match O with
| :? (obj -> list<obj>) -> "win"
| :? list<obj> -> "list!"
| _ -> "fail"
Console.WriteLine(f(fun x -> ["lol"]))
Console.WriteLine(f(["lol"]))
prints "fail" twice, as I suppose it should, because I am giving i a function obj -> list<String>, which is not a obj -> list<obj>. Is there any way to make them match though? I could upcast each list into a list<obj> before making an anonymous function out of it, or I could upcast everything to obj before putting it into the list.
Either of those works and makes it match, but i thought this was the problem that covariance/contravariance was meant to have already solved? Correct me if i'm mistaken
Unfortunately, you can't solve this using any built-in pattern matching.
The only way to find out whether an obj value is some F# function is to use F# Reflection and call the FSharpType.IsFunction method on the type. You can check for the case in your example like this:
open System
open Microsoft.FSharp.Reflection
let f (o : obj) =
let ty = o.GetType()
if FSharpType.IsFunction(ty) then
let tyFrom, tyTo = FSharpType.GetFunctionElements(ty)
if tyTo.IsGenericType && tyTo.GetGenericTypeDefinition() = typedefof<list<_>> then
printfn "win"
else
printfn "wrong function"
else
printfn "not a function"
Console.WriteLine(f(fun x -> "lol")) // wrong function
Console.WriteLine(f(fun x -> ["lol"])) // win
Console.WriteLine(f(["lol"])) // not a function
You could encapsulate the behavior in an F# active pattern to make the syntax a bit nicer (and use pattern matching on types). However, another problem is that this doesn't give you a function that you could use to actually invoke the function dynamically. I don't think there is a built-in library function for this, so you'll probably need to use .NET reflection to call the Invoke method dynamically.
EDIT: There has been similar related questions on SO. The general problem is that you're matching against some (any) instantiation of a specific generic type, so the same issue arises with lists etc. See for example:
F# and pattern matching on generics...
Pattern matching against generic type...
How to cast an object to a list...
I'm having this method which takes a Expr as parameter:
member x.HasSeq (expr:Expr<'a -> 'b seq>) =
let casted = <# fun z -> (%expr) z :?> ICollection<'b> #>
ManyNavPropertyInfo(cfg.HasMany <| toLinq casted)
What I want is to cast the 'b seq to an ICollection<'b>, which seems to work as it should, however when it reaches the line where it's going to convert the Expr to LINQ (need to do this since cfg.HasMany excepts a System.Expression<Func<'a,ICollection<'b>>>) it simply throws an exception saying:
InvalidOperationException:
The expression 'z =>
UnboxGeneric(ToFSharpFunc(z =>
z.Books).Invoke(z))' is not a valid
property expression. The expression
should represent a property: C#: 't =>
t.MyProperty' VB.Net: 'Function(t)
t.MyProperty'.
The function I use for converting Expr to LINQ:
let toLinq (exp : Expr<'a -> 'b>) =
let linq = exp.ToLinqExpression()
let call = linq :?> MethodCallExpression
let lambda = call.Arguments.[0] :?> LambdaExpression
Expression.Lambda<Func<'a, 'b>>(lambda.Body, lambda.Parameters)
I've used the toLinq function before without problems - I think it's because I cast b seq to ICollection<'b> which leaves UnboxGeneric in the Expr and when passing the Expr to toLinq it simply dosent know what to do with the UnboxGeneric - but of course thats just a theory, and I dont know what to do at all, in order to resolve it.
Your reasoning is correct - the problem is that the HasMany method recognizes only specific C# expression trees and the expression tree that your F# code generates is different.
My guess is that EF only handles a case when the expression tree is a plain access to a property of the right type - in the C# syntax something like: x => x.Foo (without any casting etc.). I think that the best option would be to modify your code to also expect a function 'a -> ICollection<'b>.
If you have some way for building a correct expression tree - e.g. if user specifies x => x.Foo, you want to return x => x.FooInternal, then you can use patterns & functions for working with F# quotations to rebuild the expression tree:
let hasSeq (e:Expr<'a -> seq<'b>>) =
match e with
| Patterns.Lambda(v, Patterns.PropertyGet(Some instance, propInfo, [])) ->
printfn "Get property %s of %A" propInfo.Name instance
// TODO: Use 'Expr.Lambda' & 'Expr.PropGet' to construct
// an expression tree in the expected format
| _ -> failwith "Not a lambda!"
... but keep in mind that the result needs to match the structure expected by HasMany. I guess that replacing the actual property specified by the user with some other property (e.g. some internal version that has the right type) is pretty much the only thing you can do.