Closed sanjiva closed 3 years ago
We will make #426 just cover the return type coming from a typedesc parameter.
Then we can cover defaulting the typedesc parameter to the contextually expected type here.
This builds on #426 by allowed the parameter to be defaulted using the following syntax:
function query(string q, typedesc rowType = <>) returns stream<rowType, sql:Error> = external;
The <>
is from how a cast sets the contextually expected type; the idea is that a cast that doesn't narrow the type gives you contextually expected type.
It's not quite as simple as using the contextually expected type. Consider
stream<Customer,sql:Error> stm = client->query(“SELECT ...”);
In this case, we want to default the rowType parameter to Customer. So the contextually expected type gives the return type and from this return type we determine the default for the rowType.
This needs the rework of contextually expected type from #392.
I think we can deal with this independently of #392.
What we need to do is a simple form of unification.
Suppose we have a declaration of a function f with parameter t of type typedesc\
In the above case, our function will be declared as:
function query(string q, typedesc<record{}> rowType = <>)
returns stream<rowType, sql:Error> = external;
and we have a call:
stream<Customer,sql:Error> stm = query(“SELECT ...”);
So in this case. we are unifying stream\<rowType, sql:Error> and stream\<Customer,sql:Error> treating rowType as a variable, constrained to be a subtype of record{}. This is a super simple kind of unification. To unify these two, we recursively unify rowType with Customer, which we can do with a substitution of { rowType -> Customer }, and sql:Error with sql:Error, which succeeds without any substitutions. Thus the result of the unification is a substitution { rowType -> Customer }. We check that Customer is a subtype of record {}. Then we call call the query
function with rowType parameter being a typedesc describing Customer.
A couple of questions:
1) Should we allow inferring the typedesc param value when the contextually expected type is a union? If so, how would we go on about supporting it? The concern here is since the order in which the member types of a union doesn't matter, how do we go on about inferring a consistent value for the typedesc param? e.g.,
function getValue(typedesc<anydata> td = <>) returns Person|td = external;
// usage
Employee|Student|map<string> val = getValue();
2) Is having multiple typedesc inferences ok? Something like the following for example,
function getTuple(typedesc<int|string> td1 = <>, typedesc<record {}> td2 = <>, typedesc<float|boolean> td3 = <>) returns [td1, td2, td3] = external;
// usage
[int, Person, float] tup = getTuple();
On point 1, I think we need to support:
function getValue(typedesc<record{}> td = <>) returns td? = external;
map<string>? val = getValue();
Similarly for error. So if we are trying to infer t and we have a type T|t, I would suggest we require that the possible basic types for T and t are disjoint (e.g. in this case nil and mapping). (We do something similar with the applicable contextually expected type already.)
On point 2, I think it makes sense but I can't think of a case where we need it. If it's no extra work to implement, then go for it, otherwise I wouldn't bother at this point.
WDYT?
IIUC, then the type inferred for t
would be the set of types not in the intersection of T
and the contextually expected type right? For example, consider something like the following where E1
and E2
are error types and Foo
and Bar
are unrelated types.
function getValue(typedesc<any|error> t = <>) returns t|E1|E2 = external;
// usage
Foo|Bar|error val = getValue();
Then, the type inferred for t
would be Foo|Bar
? And it's the same inferred type for t
in the following case as well?
function getValue(typedesc<any|error> t = <>) returns t|error = external;
// usage
Foo|Bar|E1|E2 val = getValue();
And regarding the support for multiple inferences, it's already supported in the current implementation I'm working on since it was straight forward with the work done for #426. But for unions, with the above complications, I think we'll have to allow just one inference.
@pubudu91 Sorry I didn't reply earlier.
I allowed only one <>
for now.
Your 2nd example isn't right because I cannot assign Foo|Bar|error
to Foo|Bar|E1E2
.
In your first example, I would expect t to be inferred as Foo|Bar
, but I need to fix the spec to make that so. My point about disjoint was that if you have
function(typedesc<T> t = <>) returns t|R = external;
then T and R should have disjoint basic types. So this:
function getValue(typedesc<any|error> t = <>) returns t|E1|E2 = external;
wouldn't be allowed (assuming E1 and E2 are subtypes of error), but this would:
function getValue(typedesc<any> t = <>) returns t|E1|E2 = external;
Does that make sense?
Ah yes, that makes sense. Thanks! Misunderstood what you said about disjoint sets earlier.
There are several place where a function needs to "data bind" its result. Two examples:
We do the 1st one currently in a rather hacky way:
(I'm ignoring some type checking rules here - that line does not compile.)
What we need is a way to say that the return type is the contextually expected type and to pass that typedesc into the function as a parameter so the code can try to return that. If there's a possibility that it might not be able to then the programmer can union the return type with error or possibly even panic.
Current http:get is as follows:
What I want is something like this:
Then, I can just use it like this:
This is using the @TypeParam thing we're using in LangLib as a poor man's parametric typing system. (I'm probably not using it properly.)
[Summarized from an email discussion with James.]