Add #self, #Self to #function, #file, #line, #column


(Kenny Leung) #1

When writing something like a logging framework, it would be nice to be able to auto-add the calling instance or type to a function call, the same way we can throw in the file or line number. Adding them as #self and #Self seems like a logical extension, and they can evaluate to nil if not in a struct or class context.

On a related note, it would be nice to get all of this info automatically in an Error when you throw one (plus the backtrace).


Should we add more mutating methods to `StaticString`?
(Jordan Rose) #2

The calling instance is a little different from the others because it would mean actually running code instead of producing a static string, but I guess normal default arguments also run code. Still, #Self seems like a much more straightforward proposal than #self to me.


(Alexander Momchilov) #3

I would like all these values to be wrapped into something like a CodeContext struct, that you could get with something like #context, which can then provide #context.lineNumber, #context.file`, etc.

Passing all these values (which are currently 3, but there will surely be more in the future) as default values to a function is becoming really tedious. Especially for wrapper functions that need to forward all these values.


(Michael Pangburn) #4

Relevant: Supplement #file, #line, and #function with #context


(Kenny Leung) #5

For #Self, it would still be more useful to have the actual type rather than a StaticString with the type's name.


(Jordan Rose) #6

Hm, is it the static type or the dynamic type?

(Meta-point: I guess you'd/we'd need to come up with some concrete use cases before proposing this.)


(Kenny Leung) #7

Honestly, I don't know what the limitations are between having a handle to a static or a dynamic type. Any documentation?

In general, I don't have any good concrete examples yet (I just think having more info on hand is always better), but you could imagine with a logging framework that if some object was a subclass of your special domain (like HXObject), then it would have its own overridable methods to print itself especially for the logging framework.


(Slava Pestov) #8

What would be the type of #self or #Self? Any? (and Any.Type??) I'm generally uncomfortable with a language feature allowing you to inspect the caller's lexical scope, even indirectly. There should be no difference between calling something from a class method directly, or extracting part of the class method body into a top-level function and calling the top-level function, but with this change, you will see different behavior.


(Hooman Mehr) #9

I think the introduction #self and #Self can enable whole new ways of doing things that go far beyond logging frameworks.

In order to decide whether this is a good idea or whether it should be mostly static or dynamic, we need a better understanding of the problem domains that can benefit from these features.


(Karl) #10

Isn't that also true of #function?

I don't think we should use #Self for this, because Self usually refers to the current context (and will do so more universally once SE-0068 is implemented) and these kind of logging-macro-things are usually used as default arguments.

struct Logger {
  func doSomething1(_ callingType: Any.Type? = Self) // Logger.Type
  func doSomething2(_ callingType: Any.Type? = #Self) // Caller.Type
}

Rather than add a standalone thing for this, I'd rather put it inside of a #callingContext value which can include more-obvious .type and .instance members.


(Slava Pestov) #11

Yes, but #function produces a String and its not as easily abused to implement context-specific behavior as passing in a value or a type. It's fine for logging output to depend on context, such as functions, files and lines. But I think #self will encourage people to actually have behavior that depends on it, and not just logging output.


(Hooman Mehr) #12

This is what I meant. Having this feature will have far reaching consequences. I think it is debatable whether this feature will be more harmful than useful.


(Charlie Monroe) #13

I can see various nice debugging purposes to know the caller of the function (#self = self as Any) or at least the type of the called (Self). For example, during refactoring, you want to get rid of some parts of your app to call the other part, etc.

Other use cases can be e.g. security (disallow calls from outside of your framework), backward compatibility (calls from types that conform to deprecated protocols), etc.

See http://www.jot.fm/issues/issue_2009_01/article4/ for how it was envisioned for ObjC to have a sender within the method calls.

The availability of the sender in the lookup function enables to alter the dispatch strategy depending on the sender. This contributes to enhance the runtime ability for context-oriented programming. The notion of Context-oriented Programming (COP) directly supports variability depending on a large range of dynamic attributes. In eect, it should be possible to dispatch runtime behavior on any properties of the execution context. First prototypes have illustrated how multidimensional dispatch can indeed be supported eectively to achieve expressive runtime variation in behavior.


(Tomáš Znamenáček) #14

As someone who inherited a legacy codebase with years of technical debt, the idea of the callee inspecting the calling context for anything but logging purposes gives me the creeps, bringing in a whole new level of gore.


(Sindre Sorhus) #15

Regardless of whether #self and #Self are accepted, I think it's a good idea to move #function, #file, #line into a #context parameter.

I would also like to see #file become a relative path.


(Thomas Van Lenten) #16

The value depends on what was passed when swiftc was invoked. Changing it (and having it honor some options) has come up a few times. Some likely related threads:


(Karl) #17

Haven't we all? Swift has other features which should help with that (e.g. static typing). This isn't like Obj-C where you can just invoke selectors on id and maybe they will crash at runtime. You need to cast the value to a known type before you can start doing useful things with it.

Really, I think people are exaggerating the impact this would have. This isn't any kind of stack-trace inspection - it's a value passed by the caller, like any other argument, with the benefit that it can be automatically filled-in using the default argument system. That's all.

Take the following example, which I believe is illustrative of what people are fretting about. I don't consider this to be particularly good style, but at the same time I don't consider it to be particularly fragile or world-breaking.

protocol Callback {
  func operationCompleted()
}
func doSomething1(_ callback: Any? = #self) {
  if let cb = callback as? Callback { cb.operationCompleted() }
}
func doSomething2(_ callback: Callback? = nil) {
  callback?.operationCompleted()
}

class ABC: Callback {
  func operationCompleted() { /*...*/ }

  func test1() { doSomething1() } // 'self' can be filled as a default argument.
  func test2() { doSomething2(self) }
}

You could also imagine a system where the type of callback isn't Any but some more-constrained type, with the #self default argument only being filled when called from a compatible context.


(Tomáš Znamenáček) #18

I think it makes the code harder to read and reason about, since self can now escape to the called code anytime without me noticing. And it’s not like any other argument – since the default values for other arguments are supplied by the code being called, not pulled from the current context. I love the clarity of functional style programming where you can easily reason about a function call just from the arguments.


(Amir Abbas Mousavian) #19

I think having #self is necessary for Linux’s XCTestExpectation


(Lukas Stabe 🙃) #20

How so? You can create expectations decoupled from any XCTestCase instance using the normal initializer, and expectations that are associated with a test case using the expectation(description:) method defined in an extension on XCTestCase. No need to use a weird hidden side channel to pass the test instance.