The Puppet Type System in Puppet 4.0.0 (and in 3.7 when the future parser/evaluator is in use) has undergone some change. I am posting this update for those that have already experimented with the Type system and that just want to know what has changed.
Also, the already published posts in this series about the type system will be updated with these changes (where not already done).
Object Renamed to Any
We felt that the word object had too many associations with an object oriented programming language and did not fit very well with the rest of the Puppet Language. There is already confusion over what a "class" is (especially if you come from an OO language).
From now on, the most abstract type in the Puppet Type System is Any
. As the name
implies, it accepts assignment of an instance of any other type, including Undef
. Thus,
there is no need to use Optional[Any]
.
"All Your Types are Belong to Any"2
All types are now also Any
. Earlier you would have to use a Variant
type if you
wanted a type to be able to accept both Type
and Object
instances
(i.e. Variant[Object, Type]
), now you just use Any
.
The Ruby[name] Type Renamed to Runtime['ruby', name]
The Puppet Type System supports references to types in an underlying runtime system. Currently
only Ruby
, but the Puppet master will run on JRuby on top of a JVM and there is then also
expected to be the need to reference types in the JVM name-space. The implementation in 3.6
supports a type called Ruby
, and the specification reserves names of other runtime systems (e.g. Java
).
The 3.6 implementation does however block usage of those names (e.g. Ruby
, Java
) as the name of a
resource type (plugin), or user defined resource type (define
) using
the short notation, and users would be required to use the longer notation for form e.g. Resource[java]
to reference such a resource.
This is unfortunate as the obvious names of the types in the type system also are obvious names for managing these technologies with Puppet. References to runtime types are used far more seldom so we
decided to rename Ruby[class_name]
, to the more generic Runtime['ruby', type_name]
.
Runtime['ruby',class_name]
is currently the only supported runtime type, but you can expect
there to be a Runtime['jvm']
or Runtime['java']
when/if the need arises.
This change only affects those who have played with the advanced features in the puppet bindings
system or played with advanced puppet functions where a reference to a Ruby
type was passed
using a Ruby type defined in .pp
logic, or in internal ruby logic inside the puppet runtime.
The Default Type
We also realized that we forgot about one symbolic value in the Puppet Language, the default
. It is a value in the language (represented by the Ruby symbol :default
internally), and it can be passed around. In 3.6, the type of a default expression is Ruby['Symbol']
, and would have been Runtime['ruby', 'Symbol']
in 4.0 unless we did something.
The solution was to add a type to the type system unsurprisingly called Default
. There is only one
value that is an instance of this class, and such an instance is only assignable to Any
or Default
.
Note that the value itself holds no magic powers unless it is used in a position that acts on it; like in a case expression where the case expression takes the default value to mean 'match against anything'. If you do this matching yourself, say 1 == default
the result is false
.
The 'default value' has practical value where there is a need to pass two different kinds of unknown values as well as values. You can use it to get one behavior for undef
/missing, one for given values,
and one for the default
value. Note that passing a value of default
, does not mean that it will assign a parameter's default value, it means setting that parameter to the special value of Default
type.
Also note that this is not a default-type; a type that is used by default, that type is called Any
.
The Callable Type
Also added is the Callable
Type. It currently has no practical use in the Puppet Language since
it is not possible to assign or pass a lambda/block as a value. It is however of importance when writing Puppet functions in Ruby using the 4x function API since it can accept lambdas/blocks and there is the need to also be able to define the types of an acceptable block's parameters.
Although, you may see references to the Callable type in error messages, if you are not into writing functions using the 4x function API that accepts lambdas/blocks, you can probably skip the rest of this post as such errors should be understandable from context.
Here is an excerpt from the Puppet Language Specification:
Callable
is the type of callable elements; functions and lambdas. The Callable
type
will typically not be used literally in the Puppet Language until there is support for
functions written in the Puppet Language.
Callable
is of importance for those who write functions in Ruby and want to type
check lambdas that are given as arguments to functions in Ruby. They are also important
in error messages when communicating why a given set of arguments do not match a signature.
The signature of a Callable
denotes the type and multiplicity of the arguments it accepts and consists of a sequence of parameters; a list of types, where the three last entries may optionally be min count, max count, and a Callable
(i.e. calling a lambda with another lambda).
- If neither min or max are specified the parameters must match exactly.
- A min < size(params) means that the difference is optional.
- If max > size(params) means that the last type repeats until the given max cap number of arguments
- if max is literal
default
, the max value is unbound (+Infinity). - If no types and no min/max are given, the Callable describes any callable i.e.
Callable[0, default]
(i.e. no type constraint, and any number of parameters). Callable[0,0]
is a callable that does not accept parameters- If no types are given, and the min/max count is not
[0,0]
, then the callable describes only the untyped arity and it places no constraints on the parameter types, e.g.Callable[2,2]
means callable with exactly 2 parameters.
Callable
type algebra is different from other types as it seems to work in reverse. This is because its purpose is to describe the callability of the instance, not its essence (even if the type
serves dual purpose by simply reversing the comparison). (This is known as Contravariance in computer science).
As an example, a lambda that is Callable[Numeric]
can be called with one
argument being a Numeric
, Float
, or an Integer
, but not with a Scalar
, or Any
. Thus, while it seems intuitive that a Callable[Integer
] should be assignable to a Callable[Any]
(since Any
is a wider type), this is not true because it cannot be called with an Any
. The reason for checking the type of a callable is to detect if it can be called a certain way - thus assignable?(Callable[Any], Callable[Integer])
really is a declaration that there is an intent to call the callable with one Any
argument (which it does not accept).
This also means that generality works the opposite way; Callable[String] ∪ Callable[Scalar]
yields Callable[String]
- since both can be called with a String
, but both cannot be called with any Scalar
.
You can read the full specification text for Callable in the Puppet Language Specification.
Isn't something missing?
If you read all of the above about the Callable
type, you may have wondered how the type system deals with callables that do not specify the types of the parameters. What are they? They cannot really be typed as Any
for the reasons given above - are they just Undef
or nil
?
The answer is that there is a type that is used internally in the type system to represent this case. This type is known as Unit
, and it is basically a chameleon that says 'I am whatever you want me to be' - technically the contravariant of Any
.
It cannot be used directly from the Puppet Language; you can however observe instances of this type when specifying something like Callable[1,1]
(a callable that accepts exactly one parameter)
in your 4x function API for a block parameter and then introspect the created type.
You are not expected to ever use this internal type directly. If you type Unit
in the Puppet Language, you actually get a reference to the resource type Resource[Unit]
. The internal type is however required in the type system to avoid special cases, and since you may observe it or come across it when reading the source code of puppet I thought it was worth mentioning.
No comments:
Post a Comment