阅读(2471) (0)

Refining

2017-09-28 19:05:16 更新

Refining类型基本上确定一种类型的值也是另一种类型。

假设您想要使用给定类型并将该特定类型细化为另一种兼容类型。Hack允许通过在控制流情况下使用三个构造:

  • 检查 null
  • 类型查询(例如,通过is_float()
  • 运用 instanceof

不可否认的

请记住,可空类型允许变量的值为其类型或null。有时您只想使用null该类型的非部分内容。您可以使用空检查来细化可空类型。

<?hh

namespace Hack\UserDocumentation\Types\Refining\Examples\Nullable;

function foo(?int $x): int {
  $a = 4;
  if ($x !== null) { // refine $x to just an int by verifying it is not null
    return $x + $a; // guaranteed that $x is not null now
  }
  return $a;
}

var_dump(foo(5));

Output

int(9)

混合为原始

请记住,它mixed代表任何可注释类型(可空类型除外)。mixed可以通过使用内置类型的查询功能来将其细化为更具体的原始类型is_int(), is_float(), is_string()等等

<?hh

namespace Hack\UserDocumentation\Types\Refining\Examples\Mixed;

function foo(mixed $x): int {
  $a = 4;
  if (is_int($x)) { // refine $x to int by checking to see if $x is an int
    return $x + $a;
  } else if (is_bool($x)) {
    return (int) $x + $a; // know it is a bool, so can do safe cast
  }
  return $a;
}

var_dump(foo(true));

Output

int(5)

对象实例检查

有时你想知道一个对象是一个父类的子对象还是实现一个特定的接口。您可以使用instanceof支票来帮助做出这一决定。

<?hh

namespace Hack\UserDocumentation\Types\Refining\Examples\Obj;

interface I {
  public function foo(): string;
}

class Base implements I {
  public function foo(): string {
    return "Base";
  }
}

class Child extends Base {
  // The __Override attribute is discussed in the section on attributes
  // TODO: LINK HERE WHEN READY!
  <<__Override>>
  public function foo(): string {
    return "Child";
  }
}

function bar(Base $b): Child {
  if ($b instanceof Child) { // refine $b to Child, a subclass of Base
    echo $b->foo(); // "Child"
    return $b;
  }
  echo $b->foo(); // "Base"
  return new Child();
}

function baz(I $i): Child {
  // guarantee that the interface will be a Child
  invariant($i instanceof Child, "Not Child");
  echo $i->foo(); // "Child"
  return $i;
}

function refine_object(): void {
  $c = new Child();
  bar($c);
  bar(new Base());
  baz($c);
}

refine_object();

Output

ChildBaseChild

instanceof Gotcha

看看这个例子。

<?hh

namespace Hack\UserDocumentation\Types\Refining\Examples\Unresolved;

interface I {
  public function i_method(): bool;
}

abstract class Base {
  abstract public function foo(): string;
}

class Child1 extends Base implements I {
  <<__Override>>
  public function foo(): string {
    return "Child1";
  }
  public function i_method(): bool {
    return true;
  }
}

class Child2 extends Base {
  <<__Override>>
  public function foo(): string {
    return "Child2";
  }
}

function bar(Base $b): void {
  if ($b instanceof I) { // refine $b to interface I, but makes $b unresolved
    echo $b->i_method();
  }
  // This is a type error!
  // Given the instanceof check above, we have now made $b unresolved, a union
  // between a type of I and Base. So we can only call methods common to both.
  // which in this case there are none.
  echo $b->foo();
}

function unresolved(): void {
  $c = new Child1();
  bar($c);
}

unresolved();

Output

1Child1

即使bar()是在通过了Base,和所有儿童Base实行foo(),一旦instanceof支票上完成$b,并检查返回true时,typechecker必须假设$b现在是一个未解决的类型两者的BaseI。而且由于不是所有的执行者I都必须处于层次结构中Base,我们不能保证foo()不再有可用性。

Invariant

还有一个特殊功能:

invariant(<bool expression of fact>, "Message if not")

可以在外部控制流情况下使用。它本质上是对类型检查器的一个断言,即你在布尔语句中声明的是事实和真实的。

在上述任何精炼方案中,可以使用invariant()与条件检查相反的方法。