2019-04-17 19:45:27 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
final class PhabricatorChartFunctionArgument
|
|
|
|
extends Phobject {
|
|
|
|
|
|
|
|
private $name;
|
|
|
|
private $type;
|
Separate the "configuration" and "evaluation" phases of chart functions
Summary:
Depends on D20446. Currently, chart functions are both configured through arguments and evaluated through arguments. This sort of conflates things and makes some logic more difficult than it should be.
Instead:
- Function arguments are used to configure function behavior. For example, `scale(2)` configures a function which does `f(x) => 2 * x`.
- Evaluation is now separate, after configuration.
We can get rid of "sourceFunction" (which was basically marking one argument as "this is the thing that gets piped in" in a weird magical way) and "canEvaluate()" and "impulse".
Sequences of functions are achieved with `compose(u, v, w)`, which configures a function `f(x) => w(v(u(x)))` (note order is left-to right, like piping `x | u | v | w` to produce `y`).
The new flow is:
- Every chartable function is `compose(...)` at top level, and composes one or more functions. `compose(x)` is longhand for `id(x)`. This just gives us a root/anchor node.
- Figure out a domain, through various means.
- Ask the function for a list of good input X values in that domain. This lets function chains which include a "fact" with distinct datapoints tell us that we should evaluate those datapoints.
- Pipe those X values through the function.
- We get Y values out.
- Draw those points.
Also:
- Adds `accumluate()`.
- Adds `sum()`, which is now easy to implement.
- Adds `compose()`.
- All functions can now always evaluate everywhere, they just return `null` if they are not defined at a given X.
- Adds repeatable arguments for `compose(f, g, ...)` and `sum(f, g, ...)`.
Test Plan: {F6409890}
Reviewers: amckinley
Reviewed By: amckinley
Subscribers: yelirekim
Differential Revision: https://secure.phabricator.com/D20454
2019-04-19 01:42:17 +02:00
|
|
|
private $repeatable;
|
2019-04-17 19:45:27 +02:00
|
|
|
|
|
|
|
public function setName($name) {
|
|
|
|
$this->name = $name;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getName() {
|
|
|
|
return $this->name;
|
|
|
|
}
|
|
|
|
|
Separate the "configuration" and "evaluation" phases of chart functions
Summary:
Depends on D20446. Currently, chart functions are both configured through arguments and evaluated through arguments. This sort of conflates things and makes some logic more difficult than it should be.
Instead:
- Function arguments are used to configure function behavior. For example, `scale(2)` configures a function which does `f(x) => 2 * x`.
- Evaluation is now separate, after configuration.
We can get rid of "sourceFunction" (which was basically marking one argument as "this is the thing that gets piped in" in a weird magical way) and "canEvaluate()" and "impulse".
Sequences of functions are achieved with `compose(u, v, w)`, which configures a function `f(x) => w(v(u(x)))` (note order is left-to right, like piping `x | u | v | w` to produce `y`).
The new flow is:
- Every chartable function is `compose(...)` at top level, and composes one or more functions. `compose(x)` is longhand for `id(x)`. This just gives us a root/anchor node.
- Figure out a domain, through various means.
- Ask the function for a list of good input X values in that domain. This lets function chains which include a "fact" with distinct datapoints tell us that we should evaluate those datapoints.
- Pipe those X values through the function.
- We get Y values out.
- Draw those points.
Also:
- Adds `accumluate()`.
- Adds `sum()`, which is now easy to implement.
- Adds `compose()`.
- All functions can now always evaluate everywhere, they just return `null` if they are not defined at a given X.
- Adds repeatable arguments for `compose(f, g, ...)` and `sum(f, g, ...)`.
Test Plan: {F6409890}
Reviewers: amckinley
Reviewed By: amckinley
Subscribers: yelirekim
Differential Revision: https://secure.phabricator.com/D20454
2019-04-19 01:42:17 +02:00
|
|
|
public function setRepeatable($repeatable) {
|
|
|
|
$this->repeatable = $repeatable;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getRepeatable() {
|
|
|
|
return $this->repeatable;
|
|
|
|
}
|
|
|
|
|
2019-04-17 19:45:27 +02:00
|
|
|
public function setType($type) {
|
|
|
|
$types = array(
|
|
|
|
'fact-key' => true,
|
|
|
|
'function' => true,
|
|
|
|
'number' => true,
|
2019-04-29 20:55:22 +02:00
|
|
|
'phid' => true,
|
2019-04-17 19:45:27 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
if (!isset($types[$type])) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'Chart function argument type "%s" is unknown. Valid types '.
|
|
|
|
'are: %s.',
|
|
|
|
$type,
|
|
|
|
implode(', ', array_keys($types))));
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->type = $type;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getType() {
|
|
|
|
return $this->type;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function newValue($value) {
|
|
|
|
switch ($this->getType()) {
|
2019-04-29 20:55:22 +02:00
|
|
|
case 'phid':
|
|
|
|
// TODO: This could be validated better, but probably should not be
|
|
|
|
// a primitive type.
|
|
|
|
return $value;
|
2019-04-17 19:45:27 +02:00
|
|
|
case 'fact-key':
|
|
|
|
if (!is_string($value)) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'Value for "fact-key" argument must be a string, got %s.',
|
|
|
|
phutil_describe_type($value)));
|
|
|
|
}
|
|
|
|
|
|
|
|
$facts = PhabricatorFact::getAllFacts();
|
|
|
|
$fact = idx($facts, $value);
|
|
|
|
if (!$fact) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'Fact key "%s" is not a known fact key.',
|
|
|
|
$value));
|
|
|
|
}
|
|
|
|
|
|
|
|
return $fact;
|
|
|
|
case 'function':
|
|
|
|
// If this is already a function object, just return it.
|
|
|
|
if ($value instanceof PhabricatorChartFunction) {
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!is_array($value)) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'Value for "function" argument must be a function definition, '.
|
|
|
|
'formatted as a list, like: [fn, arg1, arg, ...]. Actual value '.
|
|
|
|
'is %s.',
|
|
|
|
phutil_describe_type($value)));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!phutil_is_natural_list($value)) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'Value for "function" argument must be a natural list, not '.
|
|
|
|
'a dictionary. Actual value is "%s".',
|
|
|
|
phutil_describe_type($value)));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$value) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'Value for "function" argument must be a list with a function '.
|
|
|
|
'name; got an empty list.'));
|
|
|
|
}
|
|
|
|
|
|
|
|
$function_name = array_shift($value);
|
|
|
|
|
|
|
|
if (!is_string($function_name)) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'Value for "function" argument must be a natural list '.
|
|
|
|
'beginning with a function name as a string. The first list '.
|
|
|
|
'item has the wrong type, %s.',
|
|
|
|
phutil_describe_type($function_name)));
|
|
|
|
}
|
|
|
|
|
|
|
|
$functions = PhabricatorChartFunction::getAllFunctions();
|
|
|
|
if (!isset($functions[$function_name])) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'Function "%s" is unknown. Valid functions are: %s',
|
|
|
|
$function_name,
|
|
|
|
implode(', ', array_keys($functions))));
|
|
|
|
}
|
|
|
|
|
|
|
|
return id(clone $functions[$function_name])
|
|
|
|
->setArguments($value);
|
|
|
|
case 'number':
|
|
|
|
if (!is_float($value) && !is_int($value)) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'Value for "number" argument must be an integer or double, '.
|
|
|
|
'got %s.',
|
|
|
|
phutil_describe_type($value)));
|
|
|
|
}
|
|
|
|
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new PhutilMethodNotImplementedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|