> So essentially all the cases where we used to use 'undef' to specify a
form of 'nothingness' so that the default for that parameter on the type we
were passing it is was respected are now handled by undef.
Eh, I meant to say "are now handled by default".
On Friday, 10 October 2014 00:06:31 UTC-7, Daniele Sluijters wrote:
>
> Hi list,
>
> As an exercise to get me familiar with the new type system I started a
> from-scratch rewrite of the apt module (don't worry I won't publish it like
> that). The reasons for this are legion but it's mostly an exercise to get
> acquainted with the type system and rethink a few of the things we're
> doing, try out new stuff etc. All my testing has been done with Puppet
> 3.7.1's version of the future parser.
>
> First, some code (if the arrows don't line up it's a font thing, they line
> up in Vim just fine):
>
> class apt::params {
>
> $purge_defaults = { 'sources_file' => true,
> 'sources_dir' => true,
> 'preferences_file' => true,
> 'preferences_dir' => true, }
>
> $update_defaults = { 'policy' => 'changed',
> 'timeout' => undef,
> 'tries' => undef, }
>
> $proxy_defaults = { 'host' => undef,
> 'port' => 8080, }
> }
>
> class apt (
> Hash[Enum[sources_file,
> sources_dir,
> preferences_file,
> preferences_dir], Boolean] $purge = {},
>
> Struct[{policy => Optional[Enum[changed, always, daily, weekly]],
> timeout => Optional[Integer],
> retries => Optional[Integer],}] $update = {},
>
> Struct[{host => Optional[String],
> port => Optional[Integer[1,65535],}] $proxy = ${},
>
> Hash $sources = {},
> Hash $keys = {},
> ) inherits apt::params {
>
> $merged_purge = merge($purge_defaults, $purge)
> $merged_update = merge($update_defaults, $update)
> $merged_proxy = merge($proxy_defaults, $proxy)
>
> }
>
> As a rationale for all the hashes... What I want to have is not four
> purge_* parameters but one purge parameter with four keys. The user should
> then be allowed to supply a partial hash (meaning only the keys that need
> to change from the default) to configure behaviour (this is what the three
> merge() calls achieve in the body).
>
> First of all, this is awesome. Being able to express what kind of input
> you expect this way is great. Especially because you can go to great
> lengths to make sure that what you receive is what you would expect. Even
> though the Type code might be a bit much at this point it's going to spare
> us a whole load of headaches later on because we can just use a
> variable/value and be sure that it's set to something sensible. Hurray!
>
> Unfortunately, if you look at the type code, even for the first Hash, it
> quickly becomes difficult to read. It would be great if we could alias
> these things somehow, along the lines of:
>
> type purge_validation = Hash[Enum[sources_file, sources_dir,
> preferences_file, preferences_dir], Boolean]
> class apt {
> Purge_validation $purge = $::apt::params::purge_defaults,
> ) inherits apt::params { }
>
> I don't have a preference for a keyword but type seems sensible, alias
> could probably work too. Even though the validation code is still quite
> complex the class declaration itself becomes mighty easy to read, about as
> easy as the current form without all the type annotations.
>
> By the way, if you're wondering what that Type declaration says: I expect
> a Hash, that can contain 0-* keys (I did not specify a size on the hash so
> it is allowed to be empty). Those keys must be named one of these four
> things (those in the Enum[]) and I expect all values associated with those
> keys to be Boolean, so true or false.
>
> This means people can no longer torture you and your beautiful module with
> crap like this:
>
> class { 'apt':
> purge => { 'source_file' => 'yes', 'sources_dir' => 'false',
> 'preference_file' => 'UNDEF', 'preferences_dir' => true },
> }
>
> Puppet will simply throw errors at them. The errors themselves aren't very
> informative though. Currently you get them in the form of: Expected
> parameter 'purge' to have type <the whole type definition here> but got
> <something else>. It would be nice if we could get error messages along the
> lines of: Expected parameter 'purge' to be <a more human description> but
> got <another more human description> I know that's a tall order, but on the
> list of "nice to have" I suppose.
>
> Onwards! Optional is causing me some trouble. According to the blog
> "Luckily, the type system has a type called Optional that does exactly what
> we want in this situation, it accepts something of a specific type or
> Undef."
>
> This would mean that notice(undef =~ Optional[Numeric]) should evaluate to
> true, and indeed it does:
> Notice: Scope(Class[main]): true
> Notice: Compiled catalog for nvc2542 in environment production in 0.33
> seconds
> Notice: Finished catalog run in 0.01 seconds
>
> So this should also work:
>
> class test (
> Optional[Numeric] $number = undef,
> ) {}
>
> include test
>
> However, it does not:
> Error: Expected parameter 'number' of 'Class[Test]' to have type
> Optional[Numeric], got Runtime[ruby, Symbol] on node nvc2542.
>
> I tried to change that to Variant[Numeric, Undef], even though that's
> exactly what Optional is defined as, but no dice either. This feels like a
> bug to me, I'm hoping Henrik or Andy can shed some light on the situation.
>
> One really awesome sauce feature of Optional is when it comes to hashes.
> Earlier I showed this piece of code:
> Struct[{policy => Optional[Enum[changed, always, daily, weekly]],
> timeout => Optional[Integer],
> retries => Optional[Integer],}] $update = {},
>
> What I'm defining here is that I want a Hash (Struct[{}]), whose keys are
> named 'policy', 'timeout' and 'retries'. By setting their values to
> Optional however you are now allowed to pass in a partial hash, so just
> sending { 'policy' => 'changed' } into update will work without it
> complaining that you're missing the 'timeout' and 'retries' keys.
>
> One thing that does strike me as slightly odd though is that even though
> the value is defined as Optional, which should allow us to send in undef,
> you cannot. If you do { 'policy' => undef } you'll get an error based on
> that validation. Now, I'm very glad it doesn't allow me to do so because
> that's really the behaviour I want in this specific case, but that might
> not always be true.
>
> There are places where I really would like to allow undef as a value for a
> hash key, but not always. I haven't found a way to express that yet though.
> So essentially want to be able to say both:
> - key may be omitted but if available must be of value Integer <- current
> behaviour of Optional[Integer] with a Structs{{ key => }]
> - key may be omitted but if available may be Integer or Undef <- I can't
> seem to express this. Though there is no need in the case of $update to
> pass in '{retries => undef}' a user should be allowed to do so even if it
> doesn't achieve anything and from my understanding of the Optional
> definition, it should.
>
> Lets add a bit to my confusion:
> class test (
> Struct[{policy => Optional[Enum[changed, always, daily, weekly]],
> timeout => Optional[Integer],
> retries => Optional[Integer],}] $update = {}
> ) {
>
> }
>
> include test
>
> 23:35:37 ~/D/g/d/p/apt (master) $ puppet apply test.pp --parser future
> Notice: Compiled catalog for nvc2542.nedap.local in environment production
> in 0.41 seconds
> Notice: Finished catalog run in 0.01 seconds
>
> It gets a bit weirder because this is valid too:
> class test (
> Struct[{policy => Optional[Enum[changed, always, daily, weekly]],
> timeout => Optional[Integer],
> retries => Optional[Integer],}] $update = { 'retries' => undef, }
> ) {
>
> }
>
> include test
>
> 23:35:58 ~/D/g/d/p/apt (master) $ puppet apply test.pp --parser future
> Notice: Compiled catalog for nvc2542.nedap.local in environment production
> in 0.42 seconds
> Notice: Finished catalog run in 0.01 seconds
>
>
> But:
> class test (
> Struct[{policy => Optional[Enum[changed, always, daily, weekly]],
> timeout => Optional[Integer],
> retries => Optional[Integer],}] $update = {}
> ) {
>
> }
>
> class { 'test':
> update => { 'retries' => undef, 'policy' => 'changed', 'timeout' => 1},
> }
>
> Results in:
> 23:38:01 ~/D/g/d/p/apt (master) $ puppet apply test.pp --parser future
> Error: Expected parameter 'update' of 'Class[Test]' to have type
> Struct[{'policy'=>Optional[Enum['changed', 'always', 'daily', 'weekly']],
> 'timeout'=>Optional[Integer], 'retries'=>Optional[Integer]}], got
> Hash[String, Runtime[ruby, Symbol]] at
> /Users/daenney/Development/github/daenney/puppet/apt/test.pp:9 on node
> nvc2542.nedap.local
>
> At this point I'm utterly confused. It looks like I can't have Optional
> accept undef on a 'top' parameter, I can use it on a hash and initialise
> that hash with a key that is set to undef but I cannot pass in a hash with
> that same key set to undef.
>
> Maybe I've misunderstood the behaviour of Optional or something is going
> wrong in the way undef is being parsed, the Runtime[ruby, Symbol] I find
> very suspicious, but someone should look at this and figure out what's
> going on. I'm betting the answer is going to be "you're being an idiot" but
> I would really like to understand why.
>
> Except for my troubles with Optional all I have to say is "sweeeeeeeeet".
> As a module maintainer, this will prevent a lot of headaches. If only
> puppet-strings could parse a human-understandable description of the Type
> annotation into the docs it generates... :).
>
> --
> Daniele Sluijters
>
--
You received this message because you are subscribed to the Google Groups
"Puppet Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/puppet-dev/92edd3e3-cf1b-4644-b865-8f0b7e5221f2%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.