Open mindplay-dk opened 6 years ago
Just to through in a question regarding this:
class Box<T>
{
public function __construct(T $content)
{
// ...
}
public function<T> doSomething()
{
}
}
So you can see here that the method doSomething
declared T
, which is also a type parameter of the class. My initial reaction to this is that T
on the method is overriding T
on the class, so the T
in Box<T>
effectively dissappears.
Alternatively, this could throw an error because there is a conflict. Personally I don't mind which, but its worth choosing one for the RFC.
I would check how other languages handle this, but my inclination is to error here. It seems error-prone to allow it.
I just tested it in Java and it works by overriding the type parameter of the class.
From Java docs:
We use T for type, whenever there isn't anything more specific about the type to distinguish it. This is often the case in generic methods. If there are multiple type parameters, we might use letters that neighbor T in the alphabet, such as S. If a generic method appears inside a generic class, it's a good idea to avoid using the same names for the type parameters of the method and class, to avoid confusion. The same applies to nested generic classes.
I share of the same opinion as @morrisonlevi about reporting a compile-time error.
I was wondering, how are we supposed to handle the following cases?
interface Evaluator<N instanceof Node, V> {
public function supports(N node) : bool;
public function evaluate(N node) : V;
}
class Context {
private $evaluators;
public function __construct(Evaluator ...$evaluators) {
$this->evaluators = $evaluators;
}
public function <V> evaluate(Node $node) : V {
$evaluator = $this->getEvaluator($node);
$result = $evaluator->evaluate($node);
return $result;
}
}
In Java, we'd cast the result to ensure the return type. In PHP, I believe we need some mechanism to check 1) whether a type matches another and 1) if a value matches a type.
$reflectionTypeA = $relectionParameterA->getType();
$reflectionTypeB = $relectionParameterB->getType();
// 1)
$reflectionTypeA->supports("foo");
// 2)
$reflectionTypeA->isSubtypeOf($reflectionTypeB);
Using reflection for introspection looks ok, but seems a lot of code for just providing better feedback about the unexpected return type.
@marcospassos You would define Context in one of the following ways:
class Context {
private $evaluators;
public function __construct(Evaluator<Node, string> ...$evaluators) {
$this->evaluators = $evaluators;
}
public function evaluate(Node $node) : string {
$evaluator = $this->getEvaluator($node);
$result = $evaluator->evaluate($node);
return $result;
}
}
// or
class Context<V> {
private $evaluators;
public function __construct(Evaluator<Node, V> ...$evaluators) {
$this->evaluators = $evaluators;
}
public function evaluate(Node $node) : V {
$evaluator = $this->getEvaluator($node);
$result = $evaluator->evaluate($node);
return $result;
}
}
This way you know what the return value should be. But even in your example where you define the method type parameter V
the caller of $context->evaluate<string>()
is already aware of what the return value should be.
@orolyn if you know the result type in beforehand, this is the way to go, but this is not often the case. In a AST evaluation, the return type of BooleanEvaluato::evaluate() : bool
is known, but of MemberAccessEvaluator::evaluate() : mixed
is not.
The following snippet is based on another example from the JDK:
interface Temporal {
<R> R query(TemporalQuery<R> query);
}
interface Query<R> {
R queryFrom(Temporal temporal);
}
class CustomQuery<R extends Temporal> implements Query {
R queryFrom(Temporal temporal) {
return (R) temporal.methodThatReturnsATemporal();
}
}
LocalDate date = temporal.query(customQuery);
Back in PHP, the interpreter would throw a TypeError
giving no chance for providing better feedback or a more sophisticated logic to filter out evaluators whose return type does not match the parameter type. As it stands, I can only see the usefulness of generics methods as generic factory methods:
class Factory {
public function <T> create() : Foo<T> {
return new Foo<T>(...);
}
}
@marcospassos I may have misunderstood your question a little in regards to where you'd be making that check. But I think I see where you're coming from now.
Is it here you're referring to?
public function evaluate(Node $node) : V {
$evaluator = $this->getEvaluator($node);
$result = $evaluator->evaluate($node);
// want to make sure $result is V
return $result;
}
If so, then you have a few options including:
public function <V> evaluate(Node $node) : V {
$evaluator = $this->getEvaluator($node);
$result = $evaluator->evaluate($node);
// gettype() returns values like: `"integer"` and `"double"` etc, so we can't use it.
switch (true) {
case is_int($result) && V !== 'int':
case is_float($result) && V !== 'float':
case is_string($result) && V !== 'string':
case is_array($result) && V !== 'array':
case is_object($result) && (!$result instanceof V):
throw new InvalidEvaluationResultException($evaluator, $node);
}
return $result;
}
try {
$result = $context->evaluate<string>($node);
} catch (InvalidEvaluationResultException $e) {
...
}
This does make the assumption that V
can be read as a string, however $result instanceof V
is certainly possible. Its a bit much, and perhaps a new built in function similar to is_a
such as is_of_type($result, V)
should be included for cases like this.
That's the point, boilerplate code everywhere.
This does make the assumption that V can be read as a string, however $result instanceof V is certainly possible. Its a bit much, and perhaps a new built in function similar to is_a such as is_of_type($result, V) should be included for cases like this.
Btw, how type parameters conflicting with constants are resolved?
Btw, how type parameters conflicting with constants are resolved?
They aren't. The proposed usages such as is_of_type($result, V)
are invalid. Type parameters can only be used in places where type names are expected. Add ::class
to put it into a type name usage.
@morrisonlevi
Does that mean where T
is string
then T::class
might return "string"
?
Yes. In case you are worried about it, note that string::class
is permitted today.
Nope, thats awesome.
To summarize, it means that either V::class === Foo::class
or $foo instanceof V::class
will be supported, right? It makes me wonder about how it will work with generic types. If V
is Foo<Bar>
it implies that V::class === 'Foo<Bar>'
should evaluates to true
, right?
The class is Foo
, the type is Foo<Bar>
So, how one can check if an object is an instance of a given type?
We haven’t figured that out, I think we should add a ::type
class constant
It looks like a good idea. For any non-generic subject, subject::class
will be always equal to subject::type
.
In that sense, does $subject instanceof V::type
should work?
Yeah, theoretically. We haven’t discussed instanceof at all as far as I can remember. I’m not sure how difficult changing it is. cc @morrisonlevi
@marcospassos I detail some of this in #27
A handy feature in Java is binding arguments types to method parameters. It sounds especially useful for a dynamically-typed language like PHP.
interface Bar<T> {
public function getValue() : T;
}
class StringBar implements Bar<string> {
private string $value;
public function __construct(String $value) {
$this->value = $valuel
}
public function getValue() : string {
return $this->value;
}
}
function <T> foo(Bar<T> $bar) : T {
return $bar->getValue();
}
// Inferred "foobar"
echo foo(new Bar<string>("foobar"));
// Alternatively
echo foo<string>(new Bar<string>("foobar"));
// Type error
echo foo<int>(new Bar<string>("foobar"));
Will this feature be supported?
@marcospassos We will be inferring types, but we will not be handling generic methods or functions for the first version.
The "Parameterized Methods" section talks about extending/overriding parameterized methods.
The example may be too elaborate and distracting? The point of the example and two last paragraphs is a bit unclear.
Consider using a better example and improving the description.