Closed ta0kira closed 2 years ago
Also see #9 regarding tracing.
It might be cleaner to make function types a separate ValueType
constructor so that they can't be used in parameter substitution. This will also avoid having to update the formalization of the type system to treat function types as type instances. (Also, the idea of a function type in a union is dubious.)
I'd also like to avoid lambdas for a few reasons:
At worst, a private function could be created and bound to self
.
Parsing of infix functions actually provides most of what's needed, as far as parsing function-passing. Additionally, ValueOperation
would need a new constructor for ()
without a function name.
There will also be an issue of passing call<#x>
vs. call
with unsubstituted param #x
. For example, should [<#x> #x defines Equals<#x> (#x) -> (#x)]
be a valid function type?
Also, what about currying params or args? The former could be done with call<Foo,_>
, but the latter sounds like a syntactic mess.
Separately, what would call<?>
mean (with type-inference)? That's currently valid for infix.
I've mostly forgotten about this, which means it hasn't been a limitation yet. I'll reopen this if/when it becomes a priority. Rather than doing the "usual" function object and lambda thing, there might be a better solution with anonymous structures.
I can't tell if allowing function references (bound or not) would require specifying variances for function params. Probably not, since you wouldn't need the param to check conversion of the function type.
For example:
call<#x> (#x) -> (#x)
f <- call<Foo>
g <- f // assume g needs (Bar) -> (Baz)
The param #x
is irrelevant after Foo
gets bound; it just turns (#x) -> (#x)
into (Foo) -> (Foo)
.
I think what makes this different from type-level params is that the latter represents usage in a collection of functions that need to be kept as a collection using the containing type.
Thinking about this a bit more, it seems like anonymous structures is the way to go, since it's like an OOP analog to lambdas.
A few issues that might come up with anonymous structs:
What will the type be? The intersection of all of its refines
s? What type is #self
? (That might be easy, since #self
just inherits all refines
and defines
, plus the current category.)
All functions will need to be @value
, which means no defines
. How will the compiler resolve function calls? In particular, something like self.foo()
; what type is self
here? What about internal functions not from refines
? (Note that dispatching of the latter won't be a problem because compileFunctionCall
already bypasses TypeValue::Call
for calls to self
.)
How will params and values be captured?
// Just use <> as a way to capture #x and #y?
// Inline use of {} to initialize members?
\ callWithFoo(anonymous<#x,#y>{ value } {
refines Foo<#x>
@value Value value
foo () {
\ Bar:baz<#y>()
}
})
What if the contract for an inherited function requires making a copy of the object? Should #self{ ... }
be allowed? (anonymous<#x,#y>{ ... }
would imply a separate type, if that's the syntax we're going to use.)
Do param filters need to be reiterated? If so, they will need to be checked when the params are captured.
How will the C++ class
es be named? They will need to avoid name clashes if a single category uses more than one anonymous
. Maybe something like Anonymous_100_19
, appending line and column numbers. Will they live in the .cpp
for the category that uses them? (Probably, since we won't be generating anything in the usual Category_Foo.hpp
header.) We also need to avoid link-time clashes if separate .0rx
s happen to use anonymous
on the same line/column when defining public categories.
Coincidentally, the compiler already supports @value
-scoped params, from the recently-removed (#158) internal-params feature. This should make it easy to refer to params that aren't in parent
when compiling procedures.
The parsed representation will need some functionality of both AnyCategory c
and DefinedCategory c
. This implies that it should be parsed as a (AnyCategory c, DefinedCategory c)
, adding one new constructor to each.
Regarding parsing, I just realized that the (AnyCategory c, DefinedCategory c)
will be parsed within the respective parent DefinedCategory c
.
It might therefore make sense to do this:
DefinedCategory
constructor for the "defined" part of anonymous
.AnyCategory
constructor for the "declared" part of anonymous
.Expression
constructor to init a specific anonymous
struct, which will contain an "init" section as well as one each of AnyCategory c
and DefinedCategory c
.DefinedCategory
in generateCategoryDefinition
, recursively extract the (AnyCategory c, DefinedCategory c)
(recursive because an anonymous
could contain another anonymous
) and generate the respective code within the same .cpp
. (Keep in mind that the order will matter in the .cpp
.)Another consideration is anonymous @type
s that will only be passed as params:
\ callWithFoo<anonymous {
refines Foo
defines Factory<Foo>
new () {
return #self{ }
}
foo () {
// ...
}
}>()
This syntax is quite confusing and awkward. As far as the definition itself, the main syntactic difference vs. a @value
is the missing initializer {}
after anonymous
.
On the topic of capturing params, how would we deal with params included in filters that haven't been explicitly captured?
concrete Foo<#x> {
@type foo<#y,#z>
#y requires Bar<#z>
#z requires Bar<#x>
() -> ()
}
define Foo {
foo () {
// #y depends on #z, which depends on #x.
\ call(anonymous<#y> ... )
}
}
I think anonymous structures will be too much of a mess, both in terms of compiler implementation and in terms of code to use it in Zeolite programs.
It might be useful to support automatic encapsulation of a named function in a @value interface
(the way Java does), but I think that could also be a mess, e.g., when dealing with function params.
I'm going to close this again. I'll reopen it if I actually end up in a situation where this is going to save a lot of effort.
Possible syntax:
As a value type:
Creating a lambda value:
Reference to a named function:
The syntax will be easy; designing closure semantics will be the difficult part.