i find that when i’m first learning how to use an API, such as:
func doSomethingWith(pants:Pants)
the first question i ask is: “how do i obtain an instance of Pants
”?
and usually, that is not hard to deduce, even if the API lacks documentation, because i can click through to the definition or doc page for Pants
, and see it has initializers, static func
constructors, etc. and one of those is probably the answer to “how do i obtain an instance of Pants
?”
but i find it’s much harder when the API looks like:
func doSomethingWith(socks:[Sock])
because my first question then becomes the slightly different: “how do i obtain an array of Sock
”?
and that’s way harder to deduce because the information you’re looking for probably isn’t going to be found by browsing Sock
’s initializers.
doSomethingWith(socks: [.leftSock(color), .leftSock(color)]) // nope!
instead, the API designer probably intended for you to obtain the array from some kind of factory method.
doSomethingWith(socks: drawer.getPairOfSocks(color)) // yes!
so, as an API designer, it might be more helpful if i made an Array-oid wrapper type like:
struct Socks
{
let elements:[Sock]
private
init(elements:[Sock])
{
self.elements = elements
}
}
extension Socks
{
public
init(from drawer:inout Drawer, color:Color)
{
...
}
}
so that the call site looks like:
doSomethingWith(socks: .init(from: &drawer, color: color))
which i feel is way better from a pedagogical standpoint, because you can look at the signature of doSomethingWith(socks:)
, click through to Socks
, notice that it has an init(from:color:)
API, and voilà, you have learned how to use the socks API.
but the thing is, Socks
’ pedagogical/syntactical role is its only role. the type is otherwise completely unjustified. and having lots of public types comes with costs that are still hard to optimize away.
so (at the risk of sounding radical), perhaps we need a new nominal type that is like a typealias but is able to “own” nested declarations in ways that typealias
symbols cannot?