In Ruby, what do Kernel#Array() and Kernel#String() return for nil, a single value and an existing array, and why use them?
answer
- capitalised conversion functions
- implicit first, explicit second
- one or many, never nil
- a hash splits into pairs
- String() fails only without to_s
basics
~10 sArray(x) tries to_ary, then to_a, then wraps: Array(nil) is [], Array("a") is ["a"] and an array comes back as is. String(x) tries to_str, then to_s: String(nil) is "" and String(6) is "6".
solid answer
~40 s`Kernel#Array` and `Kernel#String` normalise input to one type. `Array(x)` tries the implicit `to_ary`, then the explicit `to_a`, and otherwise wraps the value, so `Array(nil)` is `[]`, `Array("APL-6")` is `["APL-6"]`, and an array passes through. That fits methods accepting "one or many": `Array(skus).each { ... }` handles a single SKU, a list or nil. It beats `[x].flatten`, which flattens nested arrays and turns `nil` into `[nil]`, and `x.to_a`, which raises `NoMethodError` on a String. Watch the explicit fallback: `Array({apples: 6})` gives `[[:apples, 6]]` and `Array(1..3)` expands the range. `String(x)` tries `to_str`, then `to_s`, so nearly everything converts; it raises `TypeError` only when neither works, for example on a `BasicObject`.
code
ruby · 15 linesdef restock(skus)
Array(skus).each { |sku| puts "restock #{sku}" }
end
restock("APL-6") # restock APL-6
restock(["APL-6", "MLK-1"]) # restock APL-6, then restock MLK-1
restock(nil) # prints nothing: Array(nil) is []
Array({apples: 6}) # => [[:apples, 6]]
Array(1..3) # => [1, 2, 3]
[nil].flatten # => [nil]
String(nil) # => ""
String(6) # => "6"
String(BasicObject.new) # TypeError: can't convert BasicObject into Stringgo deeper
Recall Array(nil) is [], Array of a single value wraps it, and String(nil) is an empty string.
Explain the implicit-then-explicit order behind Array() and String(), and the hash-to-pairs and range-expansion results of the to_a fallback.
Use Array() to accept one-or-many input at API edges, and recognise when its to_a fallback silently changes the shape of a Hash or Range argument.
Decide whether public methods should accept loosely shaped input normalised with Array(), or demand one shape and fail loudly, and apply that consistently.
## The conversion functions `Kernel` defines a family of methods named after core classes: `Array()`, `String()`, `Hash()`, `Integer()` and `Float()`. They are ordinary private methods with capitalised names, available everywhere. Each converts its argument to the named type using a documented order of attempts: | Function | Tries first | Then | When both fail | |---|---|---|---| | `Array(x)` | `to_ary` | `to_a` | wraps: `[x]` | | `String(x)` | `to_str` | `to_s` | raises `TypeError` | | `Hash(x)` | returns a Hash as is; `nil` or `[]` gives `{}` | `to_hash` | raises `TypeError` | | `Integer(x)` | `to_int` | `to_i` | raises `TypeError` | The pattern is consistent: the **implicit** conversion (`to_ary`, `to_str`) comes first, because an object that defines it claims to *be* that type, and the **explicit** one (`to_a`, `to_s`) comes second. ## Array(): one or many, never nil Many methods accept either a single item or a collection. `Array()` turns all of those shapes into an array you can iterate: - `Array(nil)` returns `[]`, through `nil.to_a`. - `Array("APL-6")` returns `["APL-6"]`: a String has no `to_ary` or `to_a`, so it is wrapped. The string is never split. - `Array(["APL-6", "MLK-1"])` returns the same array. - `Array(:apples)` returns `[:apples]`. The explicit `to_a` fallback produces two results worth remembering: 1. `Array({apples: 6})` returns `[[:apples, 6]]`, because `Hash#to_a` yields key-value pairs. If you meant "a list of one hash", write `[hash]`. 2. `Array(1..3)` returns `[1, 2, 3]`, because `Range#to_a` enumerates the range. ## Array() compared with the alternatives | Input | `Array(x)` | `[x].flatten` | `x.to_a` | `[*x]` | |---|---|---|---|---| | `nil` | `[]` | `[nil]` | `[]` | `[]` | | `"APL-6"` | `["APL-6"]` | `["APL-6"]` | `NoMethodError` | `["APL-6"]` | | `[1, [2]]` | `[1, [2]]` | `[1, 2]` | `[1, [2]]` | `[1, [2]]` | - **`[x].flatten`** keeps `nil` as an element and also flattens nested arrays you may want to keep. - **`x.to_a`** only works when the object defines `to_a`; `String` does not. - **`[*x]`**, the splat, behaves like `Array()` for these inputs but reads less clearly. ## String(): nearly everything converts `String(x)` calls `to_str` if the object defines it, and `to_s` otherwise. Since `Object` defines `to_s`, almost every value converts: - `String(nil)` returns `""`. - `String(6)` returns `"6"`. - `String("apples")` returns the same string. - `String(BasicObject.new)` raises `TypeError` with `can't convert BasicObject into String`, because `BasicObject` defines neither method. If an object's `to_str` returns something that is not a String, `String()` raises `TypeError` naming the method, for example `can't convert Sku to String (Sku#to_str gives Integer)`. In practice `String(x)` behaves like `x.to_s` for ordinary objects; its value is readability and the implicit-first order. ## When Array() changes the shape Because `Array()` falls back to `to_a`, any object with a `to_a` method is taken apart rather than wrapped. Besides `Hash` and `Range`: - A **`Struct`** instance returns its member values: a `Struct` with members `sku` and `qty` becomes `["APL-6", 6]`, not a one-element list of the struct. - A **`Time`** returns a 10-element array of seconds, minutes, hours, day, month, year, weekday, day of year, DST flag and zone. - A **`Set`** or an **`Enumerator`** returns its elements. When a method accepts "one record or many records" and a record is one of these types, `Array(record)` quietly produces the wrong thing. In that case test for the collection type explicitly, or require callers to pass an array. ## Worked example: restocking from a grocery order A restocking method is called with SKUs read from a grocery-order CSV. Depending on the row, the SKU cell is blank (`nil`), holds one code, or has already been split into several codes by earlier code. - `Array(skus).each { |sku| restock(sku) }` handles all three shapes without branching. - A blank cell yields `[]`, so the loop simply does nothing for that row. - If the caller ever passes a Hash of SKU to quantity, `Array()` yields pairs, so the block receives `[sku, qty]` arrays; handle that shape deliberately or reject it.
- In Ruby, why is Array(skus) safer than [skus].flatten for one-or-many input?`[nil].flatten` is `[nil]`, so a blank value becomes one nil element that the loop then processes. `flatten` also recursively flattens nested arrays you may have meant to keep. `Array(nil)` is `[]`, and an existing array passes through unchanged.
- What does Hash(x) accept, and when does it raise?`Hash(x)` returns a Hash unchanged, returns `{}` for `nil` or an empty array, and otherwise calls `to_hash`. If the object has no `to_hash`, or it returns something other than a Hash, it raises `TypeError`, so `Hash([[:a, 1]])` fails even though `to_h` would accept that input.
saying these in an interview costs you the question
- Array("a,b") splits the string into elements
- Array(nil) returns [nil]
- Array(hash) returns a one-element array holding the hash
- String(nil) raises TypeError
- Every object responds to to_a, so x.to_a is as safe as Array(x)