Optional Flappity Mappity

Consider the following four functions. Off the top of your head, without cheating, can you tell me which ones will compile and which ones won’t? And can you tell me what the type of newDate will be for each function? (In these examples, dateFormatter is a DateFormatter, oneDayOffset is DateComponents, and calendar is Calendar.)

func tomorrowsDateString1(dateString: String) -> String? {
    let date = dateFormatter.date(from: dateString)
    let newDate = date.map{ calendar.date(byAdding: oneDayOffset, to: $0) }
    return newDate.map(dateFormatter.string(from:))
}

func tomorrowsDateString2(dateString: String) -> String? {
    let date = dateFormatter.date(from: dateString)
    let newDate = date.flatMap{ calendar.date(byAdding: oneDayOffset, to: $0) }
    return newDate.flatMap(dateFormatter.string(from:))
}

func tomorrowsDateString3(dateString: String) -> String? {
    let date = dateFormatter.date(from: dateString)
    let newDate = date.flatMap{ calendar.date(byAdding: oneDayOffset, to: $0) }
    return newDate.map(dateFormatter.string(from:))
}

func tomorrowsDateString4(dateString: String) -> String? {
    let date = dateFormatter.date(from: dateString)
    let newDate = date.map{ calendar.date(byAdding: oneDayOffset, to: $0) }
    return newDate.flatMap(dateFormatter.string(from:))
}

And now consider this cute little hack, which will probably ensure you end up in hell:

/// High precedence
precedencegroup HighPrecedence { higherThan: BitwiseShiftPrecedence }

/// .? contextually performs map or flatMap
infix operator .?: HighPrecedence

/// Performs an in-line operator-based flatMap
public func .?<T, U>(lhs: T?, transform: (T) throws -> U?) rethrows -> U? {
    return try lhs.flatMap(transform)
}

/// Performs an in-line operator-based map
public func .?<T, U>(lhs: T?, _ transform: (T) throws -> U) rethrows -> U? {
    return try lhs.map(transform)
}

Which gives you this little one-operator hackapalooza:

/// And no, you don't have to figure out if you need map or flatMap
func tomorrowsDateString(dateString: String) -> String? {
    let date = dateFormatter.date(from: dateString)
    let newDate = date.?{ calendar.date(byAdding: oneDayOffset, to: $0) }
    return newDate.?dateFormatter.string(from:)
}

Pray for my soul.

2 Comments

  • Wow, it is so hacky and confusing my head hurts!
    I’m gonna lead a lot of help to digest this post 😛
    I feel like I need a step by step walk through.

    Anyway, interesting post (I think for what I understood) and it was really funny to play “which one compiles?” 🙂

  • Not sure I understand why tomorrowsDateString3 doesn’t end up double optional. Curious.