Swift is still sometimes so cumbersome

for ??, I use this extension for chaining:

extension Optional {
    func butIfNone(_ defaultValue: Wrapped) -> Wrapped {
        return self ?? defaultValue
    }
}

Example:

self.imageURLString =
    try container
    .decodeIfPresent(String.self, forKey: .imageURLString)
    .butIfNone(faker.internet.image())

That's actually not the same semantics. Your argument needs to be an auto closure, because the right operand expression is not evaluated unless it's needed.

4 Likes

I have to disagree. This method does nothing if the argument is nil... so why does it take an optional? Imagine having this in basically every method just because something somewhere could be nil...

1 Like

The author of the method wanted it to be optional. You can disagree with the author's decision to require an optional String, but, given that the author wishes to allow for a potentially nil string, it has to be handled within the method/function.

I can imagine "having this" (this being handling the possibility of a nil) in every method or function if I design said function interface to allow for a nil argument. I don't need to do anything if the interface is just a String, not a String?.

When I see this (and not knowing the full context of the use case) I'm wondering why the author is insisting on re-using the 'body' variable in the 2nd line? Not only does the result feel cumbersome, like he said, he's also changing the meaning of the variable, which places more cognitive load on the future readers who will need to process and understand what's going on with the code.

In my experience, on any reasonably designed platform of course, don't "fight the framework/language". If you are, you are likely missing an opportunity to create a piece of code that is harmonious with everything else. For example, maybe the Substring type would actually be ideal given an alternative implementation of your logger...

Anyway, hope this helps. I'd offer a code sample, but without knowing the method signature of the containing function and what additional functions will be needed before this code's goals are complete I don't think I can do better than some of the suggestions already made.

2 Likes

I'm not the original poster but let's assume the logging function is:

func log(_ s: String) {
    os_log("-> %@", s)
}

There are still places that we call this with regular strings, so we'll need to add another one:

func log(_ s: Substring) {
    os_log("-> %@", String(s))
}

Well, that's not ideal because we are copying the logic in two places, can we merge those into one function? I jump into the definitions and see:

public struct String { ... }

public struct Substring { ... }

So these are plain structs, no common superclass or anything. Maybe there is a common protocol? Well good luck finding it. After some googling, I finally find StringProtocol, this looks promising:

func log(_ s: StringProtocol) {
    os_log("-> %@", String(s))
}

That didn't work: Protocol 'StringProtocol' can only be used as a generic constraint because it has Self or associated type requirements

So I guess I have to make log generic?

func log<T: StringProtocol>(_ s: T) {
    os_log("-> %@", String(s))
}

Ok that worked! Now repeat this to every other function that may need to handle both String and Substring...

We all knew that it is possible to log a String and a Substring in Swift, the discussion is about it being cumbersome, which I agree that it is.

2 Likes

at the very least, it seems that might soon become func log(_ s: some StringProtocol) :slight_smile:

1 Like

It's sad that so much in Swift is specified as String instead of StringProtocol, and hardly surprising considering that StringProtocol is so cumbersome — both to implement and use.

For those interested, there was a proposal (that's sadly gone a bit stale) that would make os_log a lot easier to use by leveraging custom string interpolation.

Custom String Interpolation and Compile-time Interpretation applied to Logging.

I realize this doesn't exactly solve @idrougge's issues with Substring and String, but given the recent post about os_log I thought it was relevant.

In my opinion, the "?" is used to allow you to easily chain method calls, like a.getB()?.doC(). In this case, you're asking the larger question of whether something is a String, for which the answer should not be syntactically swept under the rug with a "?". I would write it like this:

var body:String?
if let temp = inResponse.result.value as? String {
    body = String(temp.prefix(1024))
}

@clayellis the improvements to Apple-specific os_log APIs detailed in that post is under development. A prototype implementation of the API is now available in stdlib/private directory: swift/stdlib/private/OSLog at main · apple/swift · GitHub. (The implementation is not yet complete.) Compiler support for optimizing these APIs is mainly implemented by this pass: swift/OSLogOptimization.cpp at main · apple/swift · GitHub. However, neither the optimization pass nor the APIs are stable at this point and is a work in progress. The API names have been changed so they are more aligned with swift-log as discussed in this post: Logging levels for Swift's server-side logging APIs and new os_log APIs.

3 Likes

Thanks for the update! I didn’t realize this was still being worked on. Aligning with the swift-log API is great news as well. I’m going to cross-post your reply on the original post for reference, if you don’t mind.

1 Like

This makes me think: What are the downsides to making String conform to Substring, and having most of your API deal with Substring? Would that just make a lot of problems go away?

Substring is a concrete type. StringProtocol is the protocol, which both String and Substring conform to.