On 2014-03-11 18:19, John Bollinger wrote:
On Friday, October 31, 2014 8:22:36 PM UTC-5, henrik lindberg wrote:
[...]
Yet again someone was bit by the automatic String to Numeric conversion
that is in Puppet (and also in --parser future).
I must confess to a certain dark amusement at Puppet struggling with its
weak-typing legacy and moving more and more in the direction of strong
typing. Not that I am in any way happy about these difficulties, but
I'm an old-school dinosaur, and weak typing has never seemed like such a
great idea to me.
It is never a great idea to have weak / no typing. It is an even worse
idea to have all types represented as strings...
[...]
We fixed PUP-3602 by not converting strings that are floating point 0
with exponential part and we also do not convert values that are
floating point infinite in Ruby (e.g. 4e999 and such). This is a crutch
though, and it is only a matter of time until someone stumbles over the
next SURPRISE !
Indeed so. If you rely on heuristics to choose behavior then you have
to accept that sometimes the wrong behavior will be chosen. In this
case, it is purely a guess that a string starting with "0e[digit]" is
not meant to be interpreted as a number.
Yes, this was a bit of a panic fix. It really sucks.
The best cure is naturally to never do String to numeric conversion.
What about numeric to string conversion? I guess Puppet doesn't do that
automatically now, so maybe this isn't the time to start, but that /is/
an alternative approach to mixed string/number comparison.
True, 4.0.0 will not do numeric to string automatically (because of
radix, precision and floating point formatting). I do not see that
coming back.
And
we wonder what people feel about that. Should we go for this in Puppet
4.0.0 (and have a 3.7.4 release as the last of the 3x series where this
behavior is implemented when using --parser future).
I think avoiding automatic string to numeric conversion is consistent
with forbidding bare strings starting with a digit..
Good point.
It would be a lot
better, though, if in the context of the manifest it were clearer which
expressions are strings and which are numeric. That's no problem for
literals, of course, but none of this is interesting in the case where
all the values involved are literals. I (think I) understand that with
the P4 parser and evaluator it will be possible to declare types
specifically enough to address that issue, but it is also my
understanding that expressions won't /necessarily/ have formal types
specific enough for that.
It is a bit difficult since operators are overloaded on type. The good
part is that if we stop transforming strings to numbers there will be
errors for arithmetic expressions.
The bad part is that ==, != cannot raise errors (since a string is
simply not equal to a number). Currently comparisons order all numbers
to be smaller than all strings. We could change those to instead error
if the types are not comparable to each other.
It is highly desirable to give manifest authors sufficient control over
conversions to avoid unwanted ones, but it is not altogether clear to me
whether the best approach is to nix all automatic string-to-number
conversions. A lot of existing manifests rely on such conversions,
since they used to be the only alternative. P4 is at liberty to break
backward compatibility, but maybe a little less breakage would be wiser?
While I am not so worried about the logic in the manifests themselves.
There has not been that many problems reported with respect to the
conversion in the other direction. For resource attributes however the
situation is worse since there are many types out there where it is
unknown how they deal with data types, what sort of munging / processing
they do of strings/numbers etc.
This would be the primary reason (IMO) to not do this until resource
types can type their parameters. (Since typed parameters direct
serialization, and there is no longer a question if a serialized "42"
should be a number or a string).
* Add === operator to compare both type and value. This is a slippery
slope since we probably want Integer and Float to compare equal - say
0.0 and 0. It adds yet another operator, and we have to decide what
case, selector and in should use since there is no way to specify if
one
or the other should be used.
I agree that as proposed, the '===' operator would be troublesome.
There is always the alternative, though, of keeping '==' as it is, and
making '===' simply perform a comparison without string/number
conversion. I think 'case', selector, and 'in' behavior are
collectively a red herring, though: if '===' were adopted in any form
then 'case', selector, and 'in' behavior would still be whatever is
specified for them, whether that's their current behavior or a variant
one based on '==='. There is no requirement that that behavior be
selectable between different senses of equality.
I'm not necessarily advocating that solution, but I think it's
appropriate to take a careful, unbiased look at all the alternatives.
I'm not certain they're all on the table, yet. For example, how about this:
* The value of an expression may be converted to a different type only
to the extent that the target type is consistent with the expression's
/formal/ type.
For example, if a class parameter is declared to be type String then
it's value cannot be automatically converted to Numeric or any of its
subtypes, but if it is type Scalar or Object and happens to /contain/ a
string, then the string value /can/ be automatically converted to a
number (a Float, for instance). That could yield backward compatibility
for existing manifests that do not declare types, while still allowing
authors to control the allowed conversions.
Interesting idea, but problematic to implement and having good
performance. Now the type of an expression is encoded in the resulting
instance / value. Adding the more advanced "declared type narrows
conversion", I think it is required to also keep track of the declared
type of every (intermediate) value. All functions must declare their
return type etc. Since no functions do that now, we would basically
always operate on the Any type, and all conversions would be allowed.
Instead, since we already have sprintf for value to string conversion,
and there is a scanf in Ruby which does the conversion the other
way, we
can simply add that function to core puppet for value conversion.
We could also add to_number(s), or to_number(s, format_string).
When doing that though, we risk ending up with a plethora of to_xxx
functions, and we could instead offer one more universal
convert_to(Type, value, options) function e.g.
# best effort, or fail
convert_to(Number, value)
# only if it is an integer, or fail
convert_to(Integer, value, {base => 10})
# convert integer to hex string with some extra text (the sprintf way)
convert_to(String, value, {format => "Hex %x"})
# convert to array of string (give full control over all
# nested conversions.
#
convert_to(Array[String], value, {
Integer => { format => "0x%x" },
Float => { format => "%#.4G" }})
# etc.
I do think there need to be type conversion facilities in some form.
I'm inclined to like the idea of a generic facility based on types, as
opposed to a host of specific conversion functions.
So - "Boldly break all the (s)t(r)hings"?
I don't know how costly it would be to implement, but I rather like the
combination of control, flexibility, and backward compatibility that
could be afforded by using formal types to limit the scope of allowed
conversions, instead of altogether forbidding (some) automatic conversions.
In general I think narrowing the conversions to declared type would be
difficult. It could possibly be done when passing arguments to
functions, defined types, or parameterized classes. Currently it only
checks for type compliance, and I don't have any immediate ideas for how
to specify type conversion except something like specifying a lambda per
parameter to do type conversion, or special type conversion functions
that apply in different scopes. I think that adds complexity that is
more difficult to deal with than explicit conversion (i.e. accept a
broader type, then convert/assert inside the body of the construct).
- henrik
--
Visit my Blog "Puppet on the Edge"
http://puppet-on-the-edge.blogspot.se/
--
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 puppet-dev+unsubscr...@googlegroups.com.
To view this discussion on the web visit
https://groups.google.com/d/msgid/puppet-dev/m3avtq%24l44%241%40ger.gmane.org.
For more options, visit https://groups.google.com/d/optout.