3

I would like to create an abstract class which takes a type parameter and the constructor of that class should be passed another Action eg.

abstract class Action<Tc> {
    public function __construct(private ?Action<*> $onSuccess = null) {}
}

How can I express a type parameter wildcard ie. "?" (Java) or "_" (Scala) in Hack?

Tino
  • 325
  • 1
  • 8
  • I'm no expert in Hack, but it seems to me that your setup is equivalent to having `Action` inherit from a monomorphic class, say `ActionBase`, which exposes the interface used by all `Action<*>`. If you are designing that code, you could refactor your action class that way. – didierc Jul 15 '14 at 15:19

2 Answers2

4

Hack doesn't have wildcard type parameters right now, so the closest you can get is actually specifying a dummy type parameter that you don't actually need, e.g.,

abstract class Action<Tc, Ta> {
  public function __construct(private ?Action<Ta> $onSuccess = null) {}
  // ...
}

Depending on how exactly you use the $onSuccess member variable, you may want it to be some specific subclass of Action<T> to be determined later, and so you may want something like this:

abstract class Action<Tc, Ta, To as Action<Ta>> {
  public function __construct(private ?To $onSuccess = null) {}
  // ...
}

However, I question whether the "dummy" types above above are really a dummy -- the vast, vast majority of use cases of Action<T> are going to care what exactly the T is, otherwise how exactly would you use the Action<T>? (There are certainly rare cases where you don't care about the T at a callsite, but they are, well, rare and so I encourage you to consider whether that is actually your case as you build out this functionality.)

Josh Watzman
  • 7,060
  • 1
  • 18
  • 26
  • the behaviour makes total sense given your comment regarding desugaring on Github [link](https://github.com/facebook/hhvm/issues/3183). However Action won't work as private ?Action $onSuccess = null is missing the first type argument required for Action. Which brings us back to wildcards. You are right, in my case a type constraint ie. 'as' will solve the issue eg. abstract class Action ... private Action $onSuccess = null. I guess higher kinded type support is non-trivial to add. I wish I had time to hack around in the type checker code and offer a PR... :( – Tino Jul 15 '14 at 05:40
0

Not sure about a wildcard, but could this achieve what you want?

<?hh
    abstract class Action<T1 as Action, T2> {
        public function __construct(private ?T1 $onSuccess = null, private ?T2 $bla = null) {}
    }

    class ActionA<T1 as Action, T2> extends Action<T1, T2> {}
    class ActionB<T1 as Action, T2> extends Action<T1, T2> {}
    class ActionC<T1 as Action, T2> extends Action<T1, T2> {}

    $action = new ActionA(new ActionB(new ActionC(null)));

    var_dump($action);

When I run this against HHVM 3.1.0, I get:

object(ActionA)#1 (2) {
  ["onSuccess":"Action":private]=>
  object(ActionB)#2 (2) {
    ["onSuccess":"Action":private]=>
    object(ActionC)#3 (2) {
      ["onSuccess":"Action":private]=>
      NULL
      ["bla":"Action":private]=>
      NULL
    }
    ["bla":"Action":private]=>
    NULL
  }
  ["bla":"Action":private]=>
  NULL
}

And the 3.1.0 type checker also returns "No errors!".

However, the T1 as Action statement on the abstract class doesn't appear to be enforcing. For instance, I can change the instantiation line to:

$action = new ActionA(new ActionB(new ActionC(new DateTime())));

And it hums along fine, with the typechecker returning no errors still. And this is after taking the class definitions out into their own file with <?hh // strict.

So not really your answer, but perhaps close? The behavior above might suggest Hack has some issues with this sort of pattern?

ndavison
  • 185
  • 7
  • 1
    `T1 as Action` omits the type parameter to `Action`, and so won't work in strict, and generally isn't a good idea. The `T1 as Action` is "enforcing" only in the typechecker, which means only as far as the typechecker can see -- HHVM has erasure semantics right now for generics and so can't enforce at runtime. I suspect your code is at toplevel or you've forgotten the hhi files, so we can't see that `DateTime` isn't an `Action`. – Josh Watzman Jul 14 '14 at 17:16