-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
[WIP] Internals of trait implementations and generic constraints #39
Comments
Additional responsibility: Associated types or static members will also be part of metadata, in case we support those in traits. trait Foo {
type Bar;
} This means that the implementation will have to define or alias a type named |
I think you need more details on how you'll emit calls here. From what you've shown so far, you'll require reflection to call the implementation of Quack. |
My current thought is that we do it like in F#'s SRTP: inline methods with .NET-unsupported constraints. So reflection will be invoked only on interop. Will make it clearer it in the post |
Even with the edit, I don't understand what the body of that static quack method will look like. It looks to me like you need reflection. |
Assume we have a method, which constraints
Within Fresh (be that the same assembly or different one), we invoke this function:
Then function
where
That's it. We inline it before emitting IL. Example of how it's accomplished by F#: type Foo() =
member _.Quack() = System.Console.WriteLine("Hello, world")
let inline quack< ^a when ^a : (member Quack : unit -> unit)> (c : ^a) =
(^a : (member Quack : unit -> unit) c)
let someOther () =
quack (Foo ()) If we look at public static void someOther()
{
Foo foo = new Foo();
Console.WriteLine("Hello, world");
} So, no invokation of Yes its IL body will be reflection, but reflection will only be invoked when interop. When it's not interop, we should restore the initial code (from metadata? or somewhere else?) and inline it. |
I'm somewhat concerned by the need to inline. That means that you'll either need to emit the implementation into the ref assembly (bloating the assembly, and potentially causing the need for other things in the ref assembly) or not have ref assemblies at all. How does F# deal with inline methods that access private type data? Ie private fields. |
Yeah, F#ers don't use SRTP as often as we would use traits. Agree, that's some question yet to solve. Maybe we should balance between inlining and reflection? Not sure.
From what I know, it does not allow (example). |
The private data is going to be an issue, since presumably fresh won't disallow this? |
Yeah, it absolutely is, if we use the inlining strategy. Now I'm thinking more about finding some balance between inlining and reflection. For instance, we could reveal otherwise private members by reflection. Though it doesn't sound good in a sense, that we get an incentive to prefer public members to private (because public members unexpectedly can give a better perf). Hmmmm.... |
Another idea, based on basically using the internals of lambdas for external implementations. E. g. we have type trait IIndex<TIndex, T> {
[TIndex]: T
} Now assume we have
now in
which gets compiled into var list = new List<string>();
return getIthElement(0, new IIndexForList<string>(list));
...
[CompilerGenerated]
public struct IIndexForList<T> : IIndex<int, T>
{
private List<T> field;
public IIndexForList(List<T> arg) => field = arg;
public T Item(int index) => field[index];
} |
Problem
The CLR currently does not support implementing interfaces for unowned types (see roles and extensions).
The basic idea is to use attributes instead of core IL features like interface implementation and generic constraints.
Generic constraints
Here I consider only constraints to traits.
For example, assume we want to write a function which adds two generic types. For that, I implement trait
CanAdd<T, T, T>
, which has operator+
defined. Now I want to create functionadd
whose type argument is constrained to this trait:Then internally (or from C#) it looks like:
Implementing traits for types we don't own
The attribute:
Assume we are defining a trait. Then, we want to implement it for types we do not have control over. Then, each our trait should have a list associated with it. Each element of this list is pair - (type, implementation of the trait for this type).
Assume we have a trait, its implementation, and use:
Then in .NET it will look like
Implementing traits we don't own for types we own
The attribute:
Assume this time that we own
Person
, but notCanQuack<T>
:So in .NET it looks like
Interop
So far my current thought is that within Fresh we inline methods, where we use complicated generic constraints (similar to F#'s SRTP with
inline
).However, we cannot force compilers of other .NET langs do the same. So when a Fresh-written method with trait constraints is invoked from, say, C#, it will somehow need to execute correctly without inlining.
To do it, from the .NET perspective these methods will have looser constraints, so that any type argument which passes in Fresh, would pass when invoking this method in C#.
Then, we will need to use reflection to determine the right method to execute (by storing a table of traits/types correspondance, though we have to refine these details later). If there's no such method (or, if there is, but the provided type does not implement the trait), then we throw an exception in runtime.
In the long run we can implement static analyzers for C# and F# which could prevent some cases of providing a bad type to a Fresh-written method.
The text was updated successfully, but these errors were encountered: