[swift-evolution] [Discussion] Static methods as global functions

Tony Allevato tony.allevato at gmail.com
Fri Mar 10 14:13:54 CST 2017


On Fri, Mar 10, 2017 at 6:18 AM Haravikk via swift-evolution <
swift-evolution at swift.org> wrote:

> So the topic of global functions like min/max came up on the thread about
> adding a standard clamp method, and it got me to thinking whether there was
> a better way to define most global methods.
>
> Currently for example there are two global functions min and max; very
> useful, and don't make much sense as instance methods, but they're not as
> easily discoverable as a static method declared under relevant type(s). We
> could get around this by defining such functions both as a static method,
> and as a global function, but it means duplicate code and by convention
> only.
>
> An alternative is to remove the need for duplicate methods using a new
> global keyword for a method declaration, telling Swift to define it as both
> a static method *and* a global function. This allows the function to be
> grouped logically under a type, making it a bit neater, but without losing
> the benefit of the global definition. For example:
>
> protocol Comparable {
> global func min(_ a:Self, _ b:Self) -> Self { return a < b ? a : b }
> global func max(_ a:Self, _ b:Self) -> Self { return a > b ? a : b }
> }
>
> With this single definition both of the following are now valid:
>
> let min = Int.min(1, 3)
> let max = max(5, 10)
>
> In the second case, Swift looks at all global definitions for "max" in
> order to locate the best match, leading it to Int.max.
>
> It's a relatively small change, but helps with neatness. It may also be
> good for consistency if we ever get the ability to define operators within
> types (though I'm not sure where we are with that?), as they could
> potentially just use the same format like-so:
>
> struct MyType : Equatable {
> global func == (_ a:MyType, _ b:MyType) -> Bool { /* Determine equality */
> }
> }
>

"If we ever..."? SE-0091 allowed this (using static instead of global) and
it's implemented in Swift 3. Or are you asking about something different?

Your idea is related to that proposal (in the sense that it's the inverse,
kind of). So I would imagine that some of the compiler machinery to do
something similar here is (or was) already there or close to it. But the
type checker has already been augmented to look up static members
specifically for operators already, at least.

I've definitely felt the need for something like this recently—in writing
some generic code constrained by FloatingPoint, I wanted to be able to use
the trig functions as well. Since they aren't part of that protocol, I
created a new one named ExtendedFloatingPoint and added methods for them,
and then global trampolines that called them:

```
public protocol ExtendedFloatingPoint: FloatingPoint {
  static func cos(_ v: Self) -> Self
}
extension Float: ExtendedFloatingPoint {
  static func cos(_ v: Float) -> Float { return cosf(v) }
}
public func cos<T: ExtendedFloatingPoint>(_ v: T) -> T { return T.cos(v) }
```

It works, but I have two problems with this approach:

(1) I don't want two ways to call the same thing; the static method inside
the protocol is *purely* an implementation detail and I'd prefer it if it
was hideable so that only the global trampoline can call it. I don't want
anyone externally to ever write "Double.cos(someDouble)", because it's
redundant, but since it's a protocol requirement and needs to be
overridden, it must be at least as accessible as the type conforming to it.

(2) The trampoline is annoying boilerplate anyway (my original version of
SE-0091 relied on trampolines and Doug Gregor assisted with the rewrite
that made the lookup for statics "just work").

I'm going to offer a different take: solving (1) could be done if I had a
way to express a hidden requirement that other types can *implement* but
not *call*. It's kind of a weird idea, but it's something I've had come up
in my designs a few times when I've tried using protocols to separate
internal and public parts of various APIs. And idea like this might get
tricky when you start talking about types outside the module boundary that
defines the protocol, though. If Foo.T conforms to Bar.U and Bar.U has such
a requirement (say, func v())...

* Can anything in Foo call T.v() directly?
* Can anything in Foo.T itself call it, or does it also have to go through
the trampoline even on itself?
* What if Bar.U is a retroactive conformance on Foo.T via an extension?
Does/how does that affect the visibility of v()?

My C# is rusty but I believe its explicit interface implementation feature
is kind of like this—I don't know the details, though. I think the hiding
is decided entirely by the implementor, not by the interface, so it's not
quite the same.

An approach like this, of course, has the downside that it leaves the
trampoline boilerplate around.

That being said, I'll play devil's advocate a bit: just how much demand is
there for a feature that would only work specifically for globals? Are
there a lot of other use cases outside mathematic/numeric libraries to
motivate it? It seems fairly narrowly scoped (whereas the ability to hide
protocol implementations would be more flexible at the cost of slightly
more code).



>
I just think it's a neater way to do this than currently requiring separate
> declarations for the static and global versions, especially since one
> usually just calls the other anyway, anyone have any thoughts?
>
> I normally argue against new keywords, but in this case it may be
> justified, as I considered an attribute but it would only end up requiring
> static anyway, so it seemed neater to just have a new category "above"
> static that is both static and global, since a global non-static doesn't
> make any sense. However, one advantage of a @global attribute is that it
> could potentially take a different name for the global function, so I could
> (but probably wouldn't) define something like:
>
> @global("min") static func smallerOfTwo(_ a:Self, _ b:Self) -> Self {
> return a < b ? a : b }
>
> So here, although the method name is smallerOfTwo, it is exposed globally
> as "min". Though I don't know much need there would be for something like
> that.
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170310/7b6f14c9/attachment.html>


More information about the swift-evolution mailing list