{"action":"created","issue":{"url":"https://api.github.com/repos/square/metrics/issues/276","repository_url":"https://api.github.com/repos/square/metrics","labels_url":"https://api.github.com/repos/square/metrics/issues/276/labels{/name}","comments_url":"https://api.github.com/repos/square/metrics/issues/276/comments","events_url":"https://api.github.com/repos/square/metrics/issues/276/events","html_url":"https://github.com/square/metrics/pull/276","id":149034239,"number":276,"title":"use MakeFunction in place of explicit MetricFunction everywhere possible","user":{"login":"Nathan-Fenner","id":6179181,"avatar_url":"https://avatars.githubusercontent.com/u/6179181?v=3","gravatar_id":"","url":"https://api.github.com/users/Nathan-Fenner","html_url":"https://github.com/Nathan-Fenner","followers_url":"https://api.github.com/users/Nathan-Fenner/followers","following_url":"https://api.github.com/users/Nathan-Fenner/following{/other_user}","gists_url":"https://api.github.com/users/Nathan-Fenner/gists{/gist_id}","starred_url":"https://api.github.com/users/Nathan-Fenner/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Nathan-Fenner/subscriptions","organizations_url":"https://api.github.com/users/Nathan-Fenner/orgs","repos_url":"https://api.github.com/users/Nathan-Fenner/repos","events_url":"https://api.github.com/users/Nathan-Fenner/events{/privacy}","received_events_url":"https://api.github.com/users/Nathan-Fenner/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":{"login":"drcapulet","id":50848,"avatar_url":"https://avatars.githubusercontent.com/u/50848?v=3","gravatar_id":"","url":"https://api.github.com/users/drcapulet","html_url":"https://github.com/drcapulet","followers_url":"https://api.github.com/users/drcapulet/followers","following_url":"https://api.github.com/users/drcapulet/following{/other_user}","gists_url":"https://api.github.com/users/drcapulet/gists{/gist_id}","starred_url":"https://api.github.com/users/drcapulet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/drcapulet/subscriptions","organizations_url":"https://api.github.com/users/drcapulet/orgs","repos_url":"https://api.github.com/users/drcapulet/repos","events_url":"https://api.github.com/users/drcapulet/events{/privacy}","received_events_url":"https://api.github.com/users/drcapulet/received_events","type":"User","site_admin":false},"milestone":null,"comments":0,"created_at":"2016-04-18T03:57:29Z","updated_at":"2016-05-25T18:42:53Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/square/metrics/pulls/276","html_url":"https://github.com/square/metrics/pull/276","diff_url":"https://github.com/square/metrics/pull/276.diff","patch_url":"https://github.com/square/metrics/pull/276.patch"},"body":"To try to make it easier to define `MetricFunction`s without having to write repetitive evaluations and casts everywhere, a new function `function.MakeFunction` (possible rename to just `Make`) has been added.\r\n\r\nIt asks for an `interface{}` which is a function that acts like a metric function. It can take any of:\r\n\r\n- `string`\r\n- `float64`\r\n- `time.Duration`\r\n- `api.SeriesList`\r\n- `function.Value`\r\n- `function.Expression`\r\n\r\nIn addition, it can have optional arguments by requesting pointers to any of these:\r\n\r\n- `*string`\r\n- `*float`\r\n- `*time.Duration`\r\n- `*api.SeriesList`\r\n- `*function.Value`\r\n- `*function.Expression`\r\n\r\nNon-optional formal arguments cannot follow optional ones. (So, for example, `(float, *string, float)` would be illegal).\r\n\r\nFor all of these, the parameters will be evaluated before they are passed to the function except for `function.Expression` and `*function.Expression`, where it is up to the given function to evaluate them.\r\n\r\nAll evaluations are done **in parallel automatically**, so the function itself does not have to do any extra work here.\r\n\r\nThe argument function can also ask for an `function.EvaluationContext` to get the current context (or an `api.Timerange` to get just the current timerange).\r\n\r\nIf it wants to allow `group by` or `collapse by` clauses, it can ask for a `function.Groups` object. \r\n\r\nThe argument function can return either `(function.Value, error)` or just a `function.Value` (which means it never errors). Either of these can be replaced by any subtype implementing these interfaces (e.g. `api.SeriesList` or `SyntaxError` respectively).\r\n\r\nIf an input function violates any of these constraints, `MakeFunction` panics.\r\n\r\nTODO: test optional arguments, cleaning up reflection in `MakeFunction`.\r\n\r\nTODO: variadic functions for `MakeFunction` (for variadic functions in MQE)\r\n\r\nTODO: float, string, duration returns?\r\n\r\n# Example\r\n\r\nConsider the following toy metric function:\r\n\r\n## Old Way\r\n\r\n```\r\n// adder takes one or two numbers.\r\n// when given only one parameter, the second defaults to \"1.0\".\r\nfunction.MetricFunction {\r\n    Name: \"adder\",\r\n    MinArguments: 1,\r\n    MaxArguments: 2,\r\n    Compute: func(context function.EvaluationContext, arguments []function.Expression, groups function.Groups) (function.Value, error) {\r\n         left, err := function.EvaluateToDuration(arguments[0], context)\r\n         if err != nil {\r\n             return nil, err\r\n         }\r\n         right := float64(1.0)\r\n         if len(arguments) == 2 {\r\n             right, err = function.EvaluateToDuration(arguments[1], context)\r\n         }\r\n         return expression.Scalar(left + right), nil\r\n    },\r\n}\r\n```\r\n\r\n## New Way\r\n\r\n```\r\n// adder takes one or two numbers.\r\n// when given only one parameter, the second defaults to \"1.0\".\r\nfunction.MakeFunction(\r\n    \"adder\",\r\n    func(left float64, optionalRight *float64) float64 {\r\n         right := float64(1.0)\r\n         if optionalRight != nil {\r\n             right = *optionalRight\r\n         }\r\n         return left + right\r\n    },\r\n)\r\n```\r\nin addition, the arguments will be evaluated in parallel here (there are library functions to evaluate them in parallel using the old way, but it will add several more lines for error-checking).\r\n\r\n"},"comment":{"url":"https://api.github.com/repos/square/metrics/issues/comments/221668387","html_url":"https://github.com/square/metrics/pull/276#issuecomment-221668387","issue_url":"https://api.github.com/repos/square/metrics/issues/276","id":221668387,"user":{"login":"drcapulet","id":50848,"avatar_url":"https://avatars.githubusercontent.com/u/50848?v=3","gravatar_id":"","url":"https://api.github.com/users/drcapulet","html_url":"https://github.com/drcapulet","followers_url":"https://api.github.com/users/drcapulet/followers","following_url":"https://api.github.com/users/drcapulet/following{/other_user}","gists_url":"https://api.github.com/users/drcapulet/gists{/gist_id}","starred_url":"https://api.github.com/users/drcapulet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/drcapulet/subscriptions","organizations_url":"https://api.github.com/users/drcapulet/orgs","repos_url":"https://api.github.com/users/drcapulet/repos","events_url":"https://api.github.com/users/drcapulet/events{/privacy}","received_events_url":"https://api.github.com/users/drcapulet/received_events","type":"User","site_admin":false},"created_at":"2016-05-25T18:42:53Z","updated_at":"2016-05-25T18:42:53Z","body":"LGTM"}}
{"actor":{"display_login":"drcapulet"}}