Open woojamon opened 1 year ago
let y = { member this.MyMember() = 42 }
What type does y
have here?
let y = { member this.MyMember() = 42 }
What type does
y
have here?
Whatever type the compiler dynamically generates, with whatever name could be useful in debugging.
Object_<file-name>_<line-number>_<column-number>
perhaps. The idea is that the compiler would write the interface instead of the developer.
Developer writes an independent object expression whose opening {
is on line 3, column 9 in MyFile.fs:
// MyFile.fs
module MyModule
let y = { member this.MyMember() = 42 }
Compiler translates this to:
// MyFile.fs
module MyModule
type private Object_MyFile_4_9 = abstract member MyMember : unit -> int
let y = { new Object_MyFile_4_9 with member this.MyMember() = 42 }
Then if something blows up during runtime the developer can see the name Object_MyFile_4_9
and know where to start looking.
Anonymous records seem to be a better base feature to extend upon. A type like this {| member MyMember: unit -> int |}
with value {| member _.MyMember() = 42 |}
@Happypig375 That could certainly also work, assuming it could still be passed to a function with member constraints ’a when ‘a : (member MyMember: unit -> int)
.
Also, for the particular use case of ad-hoc mocking up of an object graph, I think it’s important that the implementation allows for composition of these things, whatever they end up being.
let mockClient = {| member _.MyMember() = {| member _.ItsMember() = 42 |} |}
let getValue<‘a, ‘b
when ‘a : (member MyMember: unit -> ‘b)
and ‘b : (member ItsMember: unit -> int)
> (client: ‘a) =
client.MyMember().ItsMember()
printfn “%A” (getValue mockClient)
// 42
let realClient = SomeThirdPartyLibrary.RealClient()
printfn “%A” (getValue realClient)
// … whatever this happens to be at runtime.
@Happypig375 yeah, anonymous object is the closest construct, but they are immutable, and this will not make it possible to mock mutating methods. And also what will it worth to allow self-identifier and method members is another question.
@konst-sh That's a good point about mutation, especially if its my code that's trying to mutate whatever object is passed into it, whether it be the my mock object or the third-party object.
If its the third-party library that's doing the mutation, though, I think I would stop short of testing that, as in my mind such tests (tests that ensure the third-party type behaves as expected) are the responsibility of the library developer, or if I want to test their stuff then I would have justification for writing an integration test.
Even still, I admit there would be some friction with mutation here...
By the way, just to clarify, did you mean "anonymous records" instead of "anonymous objects"?
@woojamon yes, I meant anonymous records. I imagine we could generalize current object expression semantics to append some extension methods as well. Then to create object with minimal set of members we will just need to create object expression which extends Object type
I propose that F# allow for independent object expressions, i.e. object expressions not requiring a base type or interface.
The existing way of approaching this problem in F# is to create an interface or class for the object expression to depend on.
The current way is particularly verbose if I have an object graph that I want to simulate but not take a dependency on. For example, here is some code I'm using to simulate an object graph for unit testing an adapter around an Azure ArmClient. (Note that the interfaces are just boilerplate to be able to make the object expressions.)
And there are many, many more interfaces that would be created for simulating other paths through the ArmClient object graph.
But with independent object expressions all the boilerplate interfaces go away:
The compiler would still create a new anonymous type and everything just like usual, for compile-time type safety.
Pros and Cons
The advantages of making this adjustment to F# are that F# can be even less verbose.
The disadvantages of making this adjustment to F# are that it potentially opens the door for some wierd or unexpected things with object expressions.
Extra information
Estimated cost (XS, S, M, L, XL, XXL): I'm not a language writer but I would think S-M.
Related suggestions: none that I can find. (Most object expression improvements seem to end up here https://github.com/fsharp/fslang-suggestions/issues/1253, but that feature actually requires the dependent base type or interface.)
Affidavit (please submit!)
Please tick this by placing a cross in the box:
Please tick all that apply:
For Readers
If you would like to see this issue implemented, please click the :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.