In F#, we can create interface instance by object expression, but while I'm trying to use attribute ReflectedDefinition on the instance method, then I cannot get the quotations. The method info is declared in the interface type, not the instance type.
Here is my test code:
module Test
open System
open System.Reflection
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
open Microsoft.FSharp.Quotations.DerivedPatterns
open Microsoft.FSharp.Quotations.ExprShape
type IMyInterface =
abstract Foo : int -> int
let createMyInterface () =
{ new IMyInterface with
[<ReflectedDefinition>]
member this.Foo a = a + 1 }
let expr =
let a = createMyInterface()
<# a.Foo(42) #>
let rec iterExpr (expr:Expr) =
match expr with
| Call(objectExpr, info, paramExprs) ->
printfn "info: %A" info
printfn "reflected type: %A" info.ReflectedType
match info with
| MethodWithReflectedDefinition methodExpr ->
printfn "%A" methodExpr
| _ -> failwith "No reflected definition"
| ShapeVar _ -> failwithf "TODO: %A" expr
| ShapeLambda _ -> failwithf "TODO: %A" expr
| ShapeCombination _ -> failwithf "TODO: %A" expr
let test() =
iterExpr expr
[<EntryPoint>]
let main argv =
test()
0 // return an integer exit code
If I run it, I got exception:
C:\Users\Xiang\Documents\Inbox\TTTT\bin\Debug>TTTT
info: Int32 Foo(Int32)
reflected type: Test+IMyInterface
Unhandled Exception: System.Exception: No reflected definition
at Microsoft.FSharp.Core.Operators.FailWith[T](String message)
at Test.iterExpr(FSharpExpr expr) in C:\Users\Xiang\Documents\Inbox\TTTT\Program.fs:line 30
at Test.test() in C:\Users\Xiang\Documents\Inbox\TTTT\Program.fs:line 37
at Test.main(String[] argv) in C:\Users\Xiang\Documents\Inbox\TTTT\Program.fs:line 41
And I also checked the generated assembly with dotPeek, it is implemented as a derived class:
[CompilationMapping(SourceConstructFlags.ObjectType)]
[Serializable]
public interface IMyInterface
{
int Foo([In] int obj0);
}
[CompilationMapping(SourceConstructFlags.Closure)]
[Serializable]
[SpecialName]
[StructLayout(LayoutKind.Auto, CharSet = CharSet.Auto)]
internal sealed class createMyInterface\u004014 : Test.IMyInterface
{
public createMyInterface\u004014()
{
base.\u002Ector();
Test.createMyInterface\u004014 createMyInterface14 = this;
}
[ReflectedDefinition]
int Test.IMyInterface.Test\u002DIMyInterface\u002DFoo([In] int obj0)
{
return obj0 + 1;
}
}
So, the problem is, when I call the Foo method in quotation, the Call pattern get MethodInfo which is declared at interface type, which has no definition. So how could I get the actually implementation MethodInfo? and then I can get the quotation of the implementation?
Here's your problem in a nutshell:
You're calling a virtual method through an instance of the type where the method is defined.
You want the quotation to contain a call to the method as defined on the derived class.
This won't work, and isn't limited to interfaces or object expressions:
type A() =
abstract M : unit -> unit
default this.M() = printfn "abstract"
type T() =
inherit A() with
[<ReflectedDefinition>]
override this.M() = printfn "override"
let expr =
let a : A = upcast T()
<# a.M() #>
Fundamentally, the whole point of an object expression is to provide an anonymous implementation of a non-sealed class, so what you're asking for doesn't make sense to me - the compiler only knows that the object is some instance implementing that interface but can't know the concrete type of the instance and therefore can't know which (of potentially many) concrete method to use.
Related
With reference to Is there an equivalent of C#'s nameof(..) in F#?
how can the nameof function used or extended for the following case?
let nameof (q:Expr<_>) =
match q with
| Patterns.Let(_, _, DerivedPatterns.Lambdas(_, Patterns.Call(_, mi, _))) -> mi.Name
| Patterns.PropertyGet(_, mi, _) -> mi.Name
| DerivedPatterns.Lambdas(_, Patterns.Call(_, mi, _)) -> mi.Name
| _ -> failwith "Unexpected format"
let any<'R> : 'R = failwith "!"
let s = _nameof <# System.Char.IsControl #> //OK
type A<'a>() =
static member MethodWith2Pars(guid:Guid, str:string) = ""
static member MethodWith2Pars(guid:Guid, ba:byte[]) = ""
let s1 = nameof <# A<_>.MethodWith2Pars #> //Error FS0503 A member or object constructor 'MethodWith2Pars' taking 1 arguments is not accessible from this code location. All accessible versions of method 'MethodWith2Pars' take 2 arguments
let s2 = nameof <# A<_>.MethodWith2Pars : Guid * string -> string #> //Same error
The compiler gives the following error:
Error FS0503 A member or object constructor 'MethodWith2Pars' taking 1 arguments is not accessible from this code location. All accessible versions of method 'MethodWith2Pars' take 2 arguments
The answer you linked is a bit outdated. F# 5.0 (released just recently) offers the true nameof feature. See the announcement: https://devblogs.microsoft.com/dotnet/announcing-f-4-7/#nameof
This feature also existed in preview since F# 4.7: https://devblogs.microsoft.com/dotnet/announcing-f-4-7/#nameof
You can write your code like this:
open System
type A() =
static member MethodWith2Pars(guid:Guid, str:string) = ""
static member MethodWith2Pars(guid:Guid, ba:byte[]) = ""
let s1 = nameof (A.MethodWith2Pars : Guid * byte[] -> string)
let s2 = nameof (A.MethodWith2Pars : Guid * string -> string)
The type annotation is needed due to overloading. Not sure why there is a generic type parameter on the class declaration, but it's not used anywhere so I just removed it.
In C# you can get the name of a method by using
nameof(ISomeClass.SomeMethod)
Is this doable in F#? When trying to dot into ISomeClass to get the SomeMethod, it merely says "SomeMethod is not a static method"
You can create a method based around F# quotations that retrieve an interface method in a type-safe manner.
open FSharp.Quotations
open FSharp.Quotations.Patterns
let getMethodName (e: Expr<'T -> 'U>) =
match e with
| Lambda (_, Call (_, mi, _)) -> mi.Name
| _ -> failwith "%A is not a valid getMethodName expression, expected Lamba(_ Call(_, _, _))"
type ISomeInterface =
interface
abstract SomeMethod: unit -> unit
end
[<EntryPoint>]
let main argv =
let name = <# fun (i : ISomeInterface) -> i.SomeMethod () #> |> getMethodName
printfn "%s" name
0
in System.Activities.WorkflowApplication there is a delegate property:
public Action<WorkflowApplicationCompletedEventArgs> Completed { get; set; }
In my program so far, I have a variable that is an instance of this class
I want to define an F# function to set that:
let f (e: WorkflowApplicationCompletedEventArgs) =
// body
myInst.Completed <- f
but this produces the error:
Error 102 This expression was expected to have type
Action but here has type
'a -> unit
how do I complete function "f" to satisfy the compiler?
If you pass an anonymous function fun a -> ... to a method or a constructor that expects a System.Action<...> or a System.Func<...>, then it is automatically converted; in any other case, you need to convert it explicitly like #Funk indicated.
let f = System.Action<WorkflowApplicationCompletedEventArgs>(fun e ->
// body
)
myInst.Completed <- f
// Another solution:
let f (e: WorkflowApplicationCompletedEventArgs) =
// body
myInst.Completed <- System.Action<_>(f)
I have an .Net library that already has implemented .Item methods, e.g.
namespace Library2
type A() =
member m.Item with get(a: string) = printfn "get a string"
member m.Item with get(a: int) = printfn "simple slice"
In the code that uses this library, I want to add one extra method of the same name (therefore it is optional extensions):
#r #"Library2.dll"
open Library2
type A with
member m.Item with get(a: bool) =
printfn "get a bool"
The last line of the following example does not compile:
let a = new A()
a.["good"]
a.[10]
a.[true]
The F# doc says:
Extension methods cannot be virtual or abstract methods. They can
overload other methods of the same name, but the compiler gives
preference to non-extension methods in the case of an ambiguous call.
This means that I cannot extend .ToString/.GetHashCode with the same type signature, but here I use a different type signature. Why cannot the new method get extended?
I think, the problem is caused by the fact extension methods are implemented as the following (C#):
public static class MyModule
{
public static void Item(this A a, bool b)
{
// whatever
}
}
The compiler is looking for .Item(...) method, finds it in the original Library2.A class, and fails to search for any extension methods.
Note that if all .Item(...) overloads are extension methods, everything works fine:
module Library2 =
type A() =
member m.dummy = ()
open Library2
type A with
member m.Item with get(a: string) = printfn "get a string"
member m.Item with get(a: int) = printfn "simple slice"
member m.Item with get(a: bool) = printfn "get a bool"
This seems to be a bug in the compiler. The extension method is there and can be called when you abstain from the nice syntactic sugar that comes with indexers, i.e. this works:
Library:
namespace TestLibrary
type A() =
member m.Item with get(a: string) = "string"
member m.Item with get(a: int) = "int"
Main:
open TestLibrary
type A with
member m.Item with get(a: bool) = "bool"
[<EntryPoint>]
let main argv =
let a = new A()
printfn "%s" (a.get_Item "a")
printfn "%s" (a.get_Item 1)
printfn "%s" (a.get_Item true)
System.Console.ReadLine() |> ignore
0
My first intuition was that an indexer cannot have unit as return type, but that didn't turn out to be the problem.
Strange, I created a similar thing in LinqPad and it worked as you expected.
module ModuleA =
type A() =
member m.Item with get(a: string) = printfn "get a string"
member m.Item with get(a: int) = printfn "simple slice"
module ModuleB =
open ModuleA
type A with
member m.Item with get(a: bool) = printfn "get a bool"
open ModuleB
let a = new ModuleA.A()
a.["good"]
a.[10]
a.[true]
// get a string
// simple slice
// get a bool
I'm playing around with quotations and I can't see an expression pattern for type definitions. Is there really not one, or am I missing something?
<## type MyType (name:string) =
member x.Name = name ##>
Gives "Unexpected keyword 'type' in quotation literal."
You can't. You can only quote code, that is to say, any valid F# expression. Type definitions are not considered as code, but definitions.
What you might want to do is put ReflectedDefinition attribute on a type members:
type MyType (name : string) =
[<ReflectedDefinition>] member x.Name = name
If you want to retrieve the AST of members that have ReflectedDefinition you can use Expr.TryGetReflectedDefinition function.
E.g, this sample code prints ASTs of all reflected definition members of MyType:
open Microsoft.FSharp.Quotations
open System.Reflection
type MyType (name : string) =
[<ReflectedDefinition>] member x.Name = name
let mis = typeof<MyType>.GetMembers()
for mi in mis do
try
match Expr.TryGetReflectedDefinition(mi :?> MethodBase) with
| Some(e) -> printfn "%A" e
| None -> ()
with _ -> ()
()