146 lines
7.5 KiB
Markdown
146 lines
7.5 KiB
Markdown
|
# `cty` Functions system
|
||
|
|
||
|
Core `cty` is primarily concerned with types and values, with behavior
|
||
|
delegated to the calling application. However, writing functions that operate
|
||
|
on `cty.Value` is expected to be a common enough case for it to be worth
|
||
|
factoring out into a shared package, so
|
||
|
[the `function` package](https://godoc.org/github.com/apparentlymart/go-cty/cty/function)
|
||
|
serves that need.
|
||
|
|
||
|
The shared function abstraction is intended to help applications provide the
|
||
|
expected behavior in handling `cty` complexities such as unknown values and
|
||
|
dynamic types. The infrastructure in this package can do basic type checking
|
||
|
automatically, allowing applications to focus on the logic unique to each
|
||
|
function.
|
||
|
|
||
|
## Function Specifications
|
||
|
|
||
|
Functions are defined by calling applications via `FunctionSpec` instances.
|
||
|
These describe the parameters the function accepts, the return value it would
|
||
|
produce given a set of parameters, and the actual implementation of the
|
||
|
function.
|
||
|
|
||
|
The return type is defined by a function, allowing the definition of generic
|
||
|
functions whose return type depends on the given argument types or values.
|
||
|
|
||
|
### Function Parameters
|
||
|
|
||
|
Functions can have both fixed parameters and variadic arguments. Each fixed
|
||
|
parameter has its own separate specification, while the variadic arguments
|
||
|
together share a single parameter specification, meaning that they must
|
||
|
all be of the same type.
|
||
|
|
||
|
[`Parameter`](https://godoc.org/github.com/apparentlymart/go-cty/cty/function#Parameter)
|
||
|
represents the description of a parameter. The `Params` member of
|
||
|
`FunctionSpec` is a slice of positional parameters, while `VarParam` is
|
||
|
a pointer to the description of the variadic arguments, if supported.
|
||
|
|
||
|
Parameters have the following fields:
|
||
|
|
||
|
* `Name` is not used directly by this package but is intended to be useful
|
||
|
in generating documentation based on function specifications.
|
||
|
* `Type` is a type specification that a given argument must _conform_ to.
|
||
|
(see the `TestConformance` method of `cty.Type` for information on
|
||
|
what exactly that means.)
|
||
|
* `AllowNull` can be set to `true` to permit the caller to provide null values.
|
||
|
If not set, passing a null is treated as an immediate error and the
|
||
|
implementation function is not called at all.
|
||
|
* `AllowUnknown` can be set to `true` if the implementation function is
|
||
|
prepared to handle unknown values. If not set, calls with an unknown argument
|
||
|
will immediately return an unknown value of the function's return type,
|
||
|
and the implementation function is not called at all.
|
||
|
* `AllowDynamicType` can be set to `true` to allow not-yet-typed values to be
|
||
|
passed. If not set, calls with a dynamic argument will immediately return
|
||
|
`cty.DynamicVal`, and neither the type-checking function nor the
|
||
|
implementation function will be called.
|
||
|
|
||
|
Since dynamic values are themselves unknown, `AllowUnknown` and
|
||
|
`AllowDynamicType` must be set together to permit `cty.DynamicVal` to be
|
||
|
passed as an argument to the implementation function, but setting
|
||
|
`AllowDynamicType` _without_ setting `AllowUnknown` has the special effect
|
||
|
of allowing dynamic values to be passed into the type checking function
|
||
|
_without_ also passing them to the implementation function, allowing a more
|
||
|
specific return type to be specified even if the input type isn't
|
||
|
known.
|
||
|
|
||
|
### Return Type
|
||
|
|
||
|
A function returns a single value when called. The return type function,
|
||
|
specified via the `Type` field in `FunctionSpec`, defines the type this
|
||
|
value will have for the given arguments.
|
||
|
|
||
|
The arguments are passed to the type function as _values_ rather than as
|
||
|
types, though in many cases they will be unknown values for which the only
|
||
|
useful operation is to call the `Type()` method on them. Unknown values
|
||
|
can be passed to the type function regardless of how the `AllowUnknown`
|
||
|
flag is set on the associated parameter specification.
|
||
|
|
||
|
If `AllowDynamicType` is set on a parameter specification, a corresponding
|
||
|
argument may be `cty.DynamicVal`. The return type function can then handle
|
||
|
this how it wishes. If the parameter _itself_ is typed as
|
||
|
`cty.DynamicPseudoType` then the corresponding argument may be a value of
|
||
|
_any_ type. These behaviors together allow the return type function to behave
|
||
|
as a full-fledged _type checking_ function, returning an error if the caller's
|
||
|
supplied types do not conform to some requirements that are not simple enough
|
||
|
to be expressed via the parameter specifications alone.
|
||
|
|
||
|
Returning `cty.DynamicPseudoType` from the type checking function signals that
|
||
|
the function is not able to determine its return type from the given
|
||
|
information. Hopefully -- but not necessarily -- the function _implementation_
|
||
|
will produce a value of a known type once the argument values are themselves
|
||
|
known.
|
||
|
|
||
|
Calling applications may elect to pass _known_ values for type checking, which
|
||
|
then allows for functions whose return type depends on argument _values_.
|
||
|
This is a relatively-rare situation, but one key example is a hypothetical
|
||
|
JSON decoding function, which takes a string value for the JSON structure to
|
||
|
decode. If given `cty.Unknown(cty.String)` as an argument, this function would
|
||
|
need to specify its return type as `cty.DynamicPseudoType`, but if given
|
||
|
a _known_ string it could infer an appropriate return type from that string.
|
||
|
|
||
|
### Function Implementation
|
||
|
|
||
|
The `Impl` field in `FunctionSpec` is used to specify the function's
|
||
|
implementation as a Go function pointer.
|
||
|
|
||
|
The implementation function takes a slice of `cty.Value` representing the
|
||
|
call arguments and the `cty.Type` that was returned from the return type
|
||
|
function. It must then either produce a value conforming to that given type
|
||
|
or return an error.
|
||
|
|
||
|
A function implementer can write any arbitrary Go code into the implementation
|
||
|
of a function, but `cty` functions are intended to behave as pure functions,
|
||
|
so side-effects should be avoided unless the function is specialized for a
|
||
|
particular calling application that is able to accept such side-effects.
|
||
|
|
||
|
If any of the given arguments are unknown and their corresponding parameter
|
||
|
specifications _permit_ unknowns, the function implementation must handle
|
||
|
this situation, normally by immediately returning an unknown value of the
|
||
|
required return type. A function should _not_ return unknown values unless
|
||
|
at least one of the arguments is unknown, since to do otherwise would
|
||
|
violate the `cty` guarantee that a caller can avoid dealing with the
|
||
|
complexity of unknown values by never passing any in.
|
||
|
|
||
|
## The `cty` Standard Library
|
||
|
|
||
|
The set of operations provided directly on `cty.Value` is intended to cover
|
||
|
the basic operators of a simple expression language, but there are several
|
||
|
higher-level operations that can be implemented in terms of `cty` values,
|
||
|
such as string manipulations, standard mathematical functions, etc.
|
||
|
|
||
|
[The standard library](https://godoc.org/github.com/apparentlymart/go-cty/cty/function/stdlib)
|
||
|
contains a set of `cty` functions that are intended to be generally useful.
|
||
|
For the convenience of calling applications, each function is provided both
|
||
|
as a first-class Go function _and_ as a `Function` instance; the former
|
||
|
could be useful for Go code dealing directly with `cty.Value` instances,
|
||
|
while the latter is likely more useful for exposing functions into a
|
||
|
language interpreter.
|
||
|
|
||
|
The standard library also includes some functions that are just thin wrappers
|
||
|
around the operations on `cty.Value`. These are somewhat redundant, but
|
||
|
exposing them as functions has the advantage that their operands can be
|
||
|
described as function parameters and so automatic type checking and error
|
||
|
handling is possible, whereas the `cty.Value` operations prefer to `panic`
|
||
|
when given invalid input.
|
||
|
|