|
| 1 | +--- |
| 2 | +layout: default |
| 3 | +title: "Language: Typecasting" |
| 4 | +--- |
| 5 | + |
| 6 | +[data type]: lang_data_type.html |
| 7 | +[data types]: lang_data_type.html |
| 8 | +[new function]: function.html#new |
| 9 | +[scanf function]: function.html#scanf |
| 10 | +[strings]: lang_data_string.html |
| 11 | +[numbers]: lang_data_number.html |
| 12 | +[booleans]: lang_data_boolean.html |
| 13 | +[arrays]: lang_data_array.html |
| 14 | +[hashes]: lang_data_hash.html |
| 15 | +[abstract types]: lang_data_abstract.html |
| 16 | +[regexp]: lang_data_regexp.html |
| 17 | +[binary]: lang_data_binary.html |
| 18 | +[time]: lang_data_time.html |
| 19 | + |
| 20 | +Every value in the Puppet language has a [data type][], and converting between types is something you ask |
| 21 | +for rather than something Puppet does for you. When you have a string that holds a number, or an array of |
| 22 | +pairs that ought to be a hash, you convert it by asking for a new value of the type you want. |
| 23 | + |
| 24 | +Converting never changes the original value. It produces a new value, leaving the one you started with |
| 25 | +alone. |
| 26 | + |
| 27 | +Two things do convert without being asked: arithmetic on a string that looks like a number, and string |
| 28 | +interpolation. Both are covered in [automatic coercion](#automatic-coercion-in-arithmetic) below. |
| 29 | + |
| 30 | +## Calling a data type as a constructor |
| 31 | + |
| 32 | +To convert a value, call the target data type as though it were a function, passing the value you want to |
| 33 | +convert: |
| 34 | + |
| 35 | +```puppet |
| 36 | +notice(Integer('42')) # 42 |
| 37 | +notice(Boolean('yes')) # true |
| 38 | +notice(String(42)) # 42 |
| 39 | +``` |
| 40 | + |
| 41 | +Calling a data type this way is exactly the same as calling the [`new` function][new function] on it. These |
| 42 | +two lines do the same thing: |
| 43 | + |
| 44 | +```puppet |
| 45 | +$a = Integer.new('42') |
| 46 | +$b = Integer('42') |
| 47 | +``` |
| 48 | + |
| 49 | +Most people find the shorter form easier to read, so the rest of this page uses it. |
| 50 | + |
| 51 | +Some conversions take extra arguments. You can pass them by position, or by name using a hash: |
| 52 | + |
| 53 | +```puppet |
| 54 | +notice(Integer('42', 8)) # 34 |
| 55 | +notice(Integer({'from' => '42', 'radix' => 8})) # 34 |
| 56 | +``` |
| 57 | + |
| 58 | +Both of those read the string `'42'` as an octal number, giving the decimal value 34. |
| 59 | + |
| 60 | +If the data type has constraints, the converted value is checked against them and an error is raised if it |
| 61 | +doesn't fit. For example, `Integer[0]` only accepts values of zero or more: |
| 62 | + |
| 63 | +```puppet |
| 64 | +Integer[0]('-100') |
| 65 | +# Error: Converted value from Integer[0].new() has wrong |
| 66 | +# type, expects an Integer[0] value, got Integer[-100, -100] |
| 67 | +``` |
| 68 | + |
| 69 | +You cannot construct most [abstract types][], such as `Variant`. `Optional[T]` and `NotUndef[T]` are |
| 70 | +exceptions: they convert exactly as their type argument `T` does, and `Optional[T]` also accepts `undef`: |
| 71 | + |
| 72 | +```puppet |
| 73 | +notice(Optional[Integer]('42')) # 42 |
| 74 | +notice(Optional[Integer](undef) =~ Undef) # true |
| 75 | +``` |
| 76 | + |
| 77 | +## Converting to numbers |
| 78 | + |
| 79 | +For what the resulting values can do once you have them, see [numbers][]. |
| 80 | + |
| 81 | +### Integer |
| 82 | + |
| 83 | +`Integer` accepts strings, floats, and booleans. When converting a string, you can give a radix (base) as a |
| 84 | +second argument: |
| 85 | + |
| 86 | +```puppet |
| 87 | +notice(Integer('0xFF', 16)) # 255 |
| 88 | +notice(Integer('010', 10)) # 10 |
| 89 | +``` |
| 90 | + |
| 91 | +If you don't specify a radix, Puppet detects it from the start of the string: a leading `0b` or `0B` means |
| 92 | +binary, `0x` or `0X` means hexadecimal, and a leading `0` means octal. Everything else is decimal. Passing |
| 93 | +`default` as the radix asks for this same detection explicitly. |
| 94 | + |
| 95 | +```puppet |
| 96 | +notice(Integer('010')) # 8 |
| 97 | +notice(Integer('0b1011')) # 11 |
| 98 | +notice(Integer('-42')) # -42 |
| 99 | +``` |
| 100 | + |
| 101 | +Converting a float truncates the fraction rather than rounding it, and a boolean becomes `1` or `0`: |
| 102 | + |
| 103 | +```puppet |
| 104 | +notice(Integer(3.99)) # 3 |
| 105 | +notice(Integer(-3.99)) # -3 |
| 106 | +notice(Integer(true)) # 1 |
| 107 | +notice(Integer(false)) # 0 |
| 108 | +``` |
| 109 | + |
| 110 | +A third argument of `true` makes the result absolute: |
| 111 | + |
| 112 | +```puppet |
| 113 | +notice(Integer(-38, 10, true)) # 38 |
| 114 | +``` |
| 115 | + |
| 116 | +### Float and Numeric |
| 117 | + |
| 118 | +`Float` always produces a float, adding a `.0` fraction to whole numbers: |
| 119 | + |
| 120 | +```puppet |
| 121 | +notice(Float('3.14')) # 3.14 |
| 122 | +notice(Float(3)) # 3.0 |
| 123 | +notice(Float(true)) # 1.0 |
| 124 | +notice(Float('0xFF')) # 255.0 |
| 125 | +``` |
| 126 | + |
| 127 | +`Numeric` picks the type to suit the value. A value with a decimal point or an exponent becomes a float, |
| 128 | +and anything else becomes an integer: |
| 129 | + |
| 130 | +```puppet |
| 131 | +notice(Numeric('3.14')) # 3.14 |
| 132 | +notice(Numeric('1e3')) # 1000.0 |
| 133 | +notice(Numeric('0xFF')) # 255 |
| 134 | +notice(Numeric(true)) # 1 |
| 135 | +``` |
| 136 | + |
| 137 | +Both accept an extra `true` argument to return an absolute value: |
| 138 | + |
| 139 | +```puppet |
| 140 | +notice(Numeric('-42.3')) # -42.3 |
| 141 | +notice(Numeric(-42.3, true)) # 42.3 |
| 142 | +``` |
| 143 | + |
| 144 | +> **Note:** conversion to a number is strict about the text it accepts. A string with surrounding whitespace, |
| 145 | +> such as `' 42 '`, cannot be converted, and neither can `undef`. Both raise an error rather than |
| 146 | +> returning a default. If you need to tolerate stray text, see |
| 147 | +> [extracting numbers from strings](#extracting-numbers-from-strings) below. |
| 148 | +
|
| 149 | +## Converting to strings |
| 150 | + |
| 151 | +`String` converts any value, including arrays and hashes, into a [string][strings]: |
| 152 | + |
| 153 | +```puppet |
| 154 | +notice(String(42)) # 42 |
| 155 | +notice(String(true)) # true |
| 156 | +notice(String([1,2,3])) # [1, 2, 3] |
| 157 | +``` |
| 158 | + |
| 159 | +A second argument gives a format directive controlling how the value is rendered: |
| 160 | + |
| 161 | +```puppet |
| 162 | +notice(String(255, '%x')) # ff |
| 163 | +notice(String(255, '%#x')) # 0xff |
| 164 | +notice(String(3.14159, '%.2f')) # 3.14 |
| 165 | +``` |
| 166 | + |
| 167 | +Two defaults are worth knowing. A float with no format is rendered with six decimal places, and `undef` |
| 168 | +becomes an empty string: |
| 169 | + |
| 170 | +```puppet |
| 171 | +notice(String(1.0)) # 1.000000 |
| 172 | +notice(String(undef) == '') # true |
| 173 | +``` |
| 174 | + |
| 175 | +`String` accepts a large set of format directives, and can take a hash mapping data types to formats so that |
| 176 | +values inside an array or hash are each rendered differently. For the full set, see the |
| 177 | +[`new` function][new function]. |
| 178 | + |
| 179 | +## Converting to booleans |
| 180 | + |
| 181 | +Unlike some languages, Puppet has no notion of "truthy" values to fall back on, so conversion to a |
| 182 | +[boolean][booleans] accepts a small, fixed vocabulary. The strings `'true'`, `'yes'`, and `'y'` become `true`, and |
| 183 | +`'false'`, `'no'`, and `'n'` become `false`. The comparison ignores case: |
| 184 | + |
| 185 | +```puppet |
| 186 | +notice(Boolean('true')) # true |
| 187 | +notice(Boolean('YES')) # true |
| 188 | +notice(Boolean('No')) # false |
| 189 | +``` |
| 190 | + |
| 191 | +For numbers, zero is `false` and everything else, including negative numbers, is `true`: |
| 192 | + |
| 193 | +```puppet |
| 194 | +notice(Boolean(0)) # false |
| 195 | +notice(Boolean(0.0)) # false |
| 196 | +notice(Boolean(1)) # true |
| 197 | +notice(Boolean(-1)) # true |
| 198 | +``` |
| 199 | + |
| 200 | +Any other string raises an error, so `Boolean('maybe')` fails rather than guessing. |
| 201 | + |
| 202 | +## Converting between arrays and hashes |
| 203 | + |
| 204 | +[Arrays][arrays] and [hashes][hashes] convert into one another, which is a common need when data arrives in |
| 205 | +one shape and your code expects the other. |
| 206 | + |
| 207 | +### To an array |
| 208 | + |
| 209 | +A hash becomes an array of key/value pairs: |
| 210 | + |
| 211 | +```puppet |
| 212 | +notice(Array({'a' => 1, 'b' => 2})) |
| 213 | +# [[a, 1], [b, 2]] |
| 214 | +``` |
| 215 | + |
| 216 | +An array is returned unchanged, an empty hash becomes an empty array, and a [`Binary`][binary] becomes an |
| 217 | +array of byte values: |
| 218 | + |
| 219 | +```puppet |
| 220 | +notice(Array(Binary('aGk='))) # [104, 105] |
| 221 | +``` |
| 222 | + |
| 223 | +Anything iterable is turned into an array of the things it iterates over, which is easy to trip over. A |
| 224 | +string iterates over its characters, and an integer iterates over the numbers below it: |
| 225 | + |
| 226 | +```puppet |
| 227 | +notice(Array('hello')) # [h, e, l, l, o] |
| 228 | +notice(Array(1)) # [0] |
| 229 | +``` |
| 230 | + |
| 231 | +When what you actually want is "wrap this in an array unless it already is one", pass a second argument of |
| 232 | +`true`: |
| 233 | + |
| 234 | +```puppet |
| 235 | +notice(Array('hello', true)) # [hello] |
| 236 | +notice(Array([1, 2], true)) # [1, 2] |
| 237 | +``` |
| 238 | + |
| 239 | +Note that `undef` wraps to an empty array, not to an array containing `undef`: |
| 240 | + |
| 241 | +```puppet |
| 242 | +notice(Array(undef, true)) # [] |
| 243 | +``` |
| 244 | + |
| 245 | +### To a hash |
| 246 | + |
| 247 | +An array of pairs becomes a hash, and so does a flat array with an even number of entries: |
| 248 | + |
| 249 | +```puppet |
| 250 | +notice(Hash([['a', 1], ['b', 2]])) # {a => 1, b => 2} |
| 251 | +notice(Hash(['a', 1, 'b', 2])) # {a => 1, b => 2} |
| 252 | +``` |
| 253 | + |
| 254 | +An empty array becomes an empty hash, and a hash is returned unchanged. An array with an odd number of |
| 255 | +entries raises an `odd number of arguments for Hash` error. |
| 256 | + |
| 257 | +### Tuple and Struct |
| 258 | + |
| 259 | +`Tuple` converts exactly as `Array` does, and `Struct` exactly as `Hash` does. The difference is that the |
| 260 | +result is then checked against the type you asked for: |
| 261 | + |
| 262 | +```puppet |
| 263 | +notice(Tuple[String, Integer](['a', 1])) # [a, 1] |
| 264 | +notice(Struct[{'a' => Integer}]([['a', 1]])) # {a => 1} |
| 265 | +``` |
| 266 | + |
| 267 | +## Extracting numbers from strings |
| 268 | + |
| 269 | +Conversion requires the whole string to be a number. To pull a number out of a longer piece of text, use the |
| 270 | +[`scanf` function][scanf function], which takes a format and always returns an array of what it found: |
| 271 | + |
| 272 | +```puppet |
| 273 | +notice(scanf('42 apples', '%d')) # [42] |
| 274 | +notice('2.5kg'.scanf('%f')) # [2.5] |
| 275 | +``` |
| 276 | + |
| 277 | +If nothing matches, you get an empty array rather than an error, which makes `scanf` a good way to handle |
| 278 | +input that might not contain a number at all: |
| 279 | + |
| 280 | +```puppet |
| 281 | +notice(scanf('no digits', '%d')) # [] |
| 282 | +``` |
| 283 | + |
| 284 | +## Automatic coercion in arithmetic |
| 285 | + |
| 286 | +Arithmetic operators can coerce a string to a number, but by default OpenVox treats doing so as an error: |
| 287 | + |
| 288 | +```puppet |
| 289 | +notice('1' + 1) |
| 290 | +# Error: The string '1' was automatically coerced to the |
| 291 | +# numerical value 1 |
| 292 | +``` |
| 293 | + |
| 294 | +This is controlled by the `strict` setting, which defaults to `error`. Setting it to `warning` lets the |
| 295 | +arithmetic proceed and logs a warning instead; setting it to `off` silences the message entirely. Rather |
| 296 | +than relaxing the setting, convert explicitly, which works under any value of `strict`: |
| 297 | + |
| 298 | +```puppet |
| 299 | +notice(Integer('1') + 1) # 2 |
| 300 | +``` |
| 301 | + |
| 302 | +String interpolation is a separate matter and is always allowed. Putting a value in a string converts it |
| 303 | +without complaint: |
| 304 | + |
| 305 | +```puppet |
| 306 | +$n = 42 |
| 307 | +notice("interpolated:${n}") # interpolated:42 |
| 308 | +``` |
| 309 | + |
| 310 | +## Other conversions |
| 311 | + |
| 312 | +A string becomes a [`Regexp`][regexp], which is useful when the pattern is built at runtime or comes from |
| 313 | +data: |
| 314 | + |
| 315 | +```puppet |
| 316 | +$r = Regexp('[a-z]+\.com') |
| 317 | +notice('foo.com' =~ $r) # true |
| 318 | +``` |
| 319 | + |
| 320 | +A string naming a data type becomes that data type: |
| 321 | + |
| 322 | +```puppet |
| 323 | +notice(Type('Integer') == Integer) # true |
| 324 | +``` |
| 325 | + |
| 326 | +The [`Timestamp` and `Timespan`][time] types have their own rich set of conversions from strings, numbers, |
| 327 | +and hashes, covered on their own page. |
| 328 | + |
| 329 | +## Further reading |
| 330 | + |
| 331 | +* The [`new` function][new function] documents every conversion signature in full, including the complete |
| 332 | + list of string format directives. |
| 333 | +* [Data type syntax][data types] covers how data types are written and where you can use them. |
| 334 | +* [Abstract data types][abstract types] covers `Optional`, `NotUndef`, `Variant`, and the rest. |
0 commit comments