-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Allow use of property-based row classes in ML.NET #616
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
src/Microsoft.ML.Api/ApiUtils.cs
Outdated
|
||
il.Emit(OpCodes.Ldarg_3); // push arg3 | ||
il.Emit(OpCodes.Ldarg_1); // push arg1 | ||
il.Emit(OpCodes.Call, propertyInfo.GetGetMethod()); // push [stack top].[propertyInfo] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Call [](start = 28, length = 4)
Should it be just call or callvirt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For F# records "call" was fine, but you're right that for C# get/set we may need callvirt
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could just check the property, right? If the property is virtual or abstract, then callvirt
. If it isn't virtual, then just call
?
In reply to: 206686593 [](ancestors = 206686593)
src/Microsoft.ML.Api/ApiUtils.cs
Outdated
|
||
il.Emit(OpCodes.Ldarg_1); // push arg1 | ||
il.Emit(OpCodes.Ldarg_2); // push arg2 | ||
il.Emit(OpCodes.Call, propertyInfo.GetSetMethod()); // [stack top-1].[propertyInfo] <- [stack top] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Call [](start = 28, length = 4)
Same question regarding Call or Callvirt
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
likewise, thanks
src/Microsoft.ML.Api/TypedCursor.cs
Outdated
"Can't bind the IDataView column '{0}' of type '{1}' to field '{2}' of type '{3}'.", | ||
col.ColumnName, realColType, col.FieldInfo.Name, col.FieldInfo.FieldType.FullName); | ||
"Can't bind the IDataView column '{0}' of type '{1}' to field or property '{2}' of type '{3}'.", | ||
col.ColumnName, realColType, col.MemberInfo.Name, col.OutputType.FullName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OutputType [](start = 78, length = 10)
This look something suspicious. In case of IsComputed = true you will return something else rather than FieldType or PropertyType. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, will change this
Thank you for your work @dsyme!
Basically to make sure you can read data into IDataView properly and if you decide to map IDataView on some class it get properly mapped. I know this is mostly to unblock F#, but I think it would be nice to have tests where you cover private getter/setter, class which inherit property from base class, class which override, readonly property, private property, and I guess lot of other cases, just to knew what allowed and what not. But if you need to be unblocked by this PR to continue working on F# support, I think tests coverage easily can be separate issue. |
src/Microsoft.ML/Data/TextLoader.cs
Outdated
userType | ||
.GetProperties(BindingFlags.Public | BindingFlags.Instance) | ||
.Where(x => x.CanRead && x.CanWrite && x.GetIndexParameters().Length == 0) | ||
.ToArray(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't need to ToArray
here, when you are just going to concat it with fieldInfos
and then ToArray
that as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll remove it, thanks
src/Microsoft.ML/Data/TextLoader.cs
Outdated
if (mappingAttr == null) | ||
throw Contracts.Except($"{field.Name} is missing ColumnAttribute"); | ||
throw Contracts.Except($"field or property {memberInfo.Name} is missing ColumnAttribute"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
f [](start = 45, length = 1)
It's a totally nitpicking, but we prefer to start our sentences with Capital letter. #Resolved
break; | ||
|
||
default: | ||
throw Contracts.ExceptNotSupp("expected a FieldInfo or a PropInfo"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PropInfo [](start = 77, length = 8)
is it worth to be shorten?
Also can you start sentence with capital letter? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
if (fieldInfo == null) | ||
if (memberInfo == null) | ||
throw Contracts.ExceptParam(nameof(userSchemaDefinition), "No field with name '{0}' found in type '{1}'", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No field
=> No field or property
src/Microsoft.ML.Api/ApiUtils.cs
Outdated
return (Delegate)methInfoProp.Invoke(null, new object[] { propertyInfo, assignmentOpCodeProp }); | ||
|
||
default: | ||
throw Contracts.ExceptNotSupp("expected a FieldInfo or a PropInfo"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PropInfo
=> PropertyInfo
OK, all updated base on code review comments so far, thanks! @Ivanidzo4ka I've added some initial testing here, is this the sort of thing you were after? https://github.com/dotnet/machinelearning/pull/616/files#diff-de10d1dc0f5785ee3a7f0053c465fbdcR632 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is looking in the right direction. Good work, @dsyme.
src/Microsoft.ML.Api/ApiUtils.cs
Outdated
var mb = new DynamicMethod("Peek", null, args, typeof(TOwn), true); | ||
var il = mb.GetILGenerator(); | ||
var minfo = propertyInfo.GetGetMethod(); | ||
var opcode = minfo.IsVirtual ? OpCodes.Callvirt : OpCodes.Call; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to handle IsAbstract
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not 100% sure, I thought IsAbstract implies IsVirtual, but the MSDN docs don't say that so I'll add it
src/Microsoft.ML/Data/TextLoader.cs
Outdated
for (int index = 0; index < fields.Length; index++) | ||
var userType = typeof(TInput); | ||
|
||
var fieldInfos = userType.GetFields(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we be checking for public and instance fields here? Just like we are in GetProperties.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this looks good, thanks for the help, @dsyme.
@TomFinley @Zruty0 - any concerns on merging this?
Public is the default - but yes, I think we should be checking for instance fields (I assume static fields cause failures at the moment) #Closed |
|
||
default: | ||
throw Contracts.ExceptNotSupp("Expected a FieldInfo or a PropertyInfo"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I understand, this clause is only present because the compiler complains that there must be a default clause, so we actually will never trigger it.
If that's the case, I suggest putting Contracts.Assert(false)
before throwing, to signify that this is a perceived-never-to-execute code (as opposed to something we don't support not but may in the future). #Closed
break; | ||
|
||
default: | ||
throw Contracts.ExceptNotSupp("Expected a FieldInfo or a PropertyInfo"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throw [](start = 20, length = 5)
Same here: if the code is never expected to execute, make it obvious to reader by putting Contracts.Assert(false) ahead of throw. #Closed
src/Microsoft.ML.Api/ApiUtils.cs
Outdated
|
||
il.Emit(OpCodes.Ldarg_3); // push arg3 | ||
il.Emit(OpCodes.Ldarg_1); // push arg1 | ||
il.Emit(opcode, minfo); // push [stack top].[propertyInfo] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
push [stack top].[propertyInfo] [](start = 55, length = 31)
Please reconcile the comment with the code #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not quite sure what you mean here? The comment is roughly accurate, based on the similar code for fields #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant that the line 111 now emits a 'call' opcode, but the comment still says push [stack top].[propertyInfo]
. Oh, but what you mean is that the result of call will be that the property value will end up on top of the stack.
I suppose it's true, but I would expect the comment to look like // call [methodInfo]
or something.
In reply to: 206991828 [](ancestors = 206991828)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, done
src/Microsoft.ML.Api/ApiUtils.cs
Outdated
return (Delegate)methInfoProp.Invoke(null, new object[] { propertyInfo }); | ||
|
||
default: | ||
throw Contracts.ExceptNotSupp("Expected a FieldInfo or a PropertyInfo"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throw [](start = 20, length = 5)
Contracts.Assert(false) before throwing #Closed
I think we should be consistent with public vs instance. If we do so for properties, do so for fields as well. In reply to: 409618178 [](ancestors = 409618178) |
In general, we try to merge the completely tested code (unless we're blocking other work, in which case we can use discretion). In reply to: 409402126 [](ancestors = 409402126) |
In my view, we should require both getter and setter to be public. Either that, or we have to split In reply to: 409383628 [](ancestors = 409383628) |
@Zruty0 All updated - the tests have now been completed #Closed |
I will also add one more F# for another way to define F# properties #Closed |
Done |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dsyme thanks a lot for the contribution! Looks good to me now. |
This is WIP to address
It builds on #600 and you can see the added diff between #600 and this PR here
Copying comment from here: