1

I am wondering why I am not required to provide any parameters at the call site of the function on line 19

Code Snippet of a custom timer

I have a strong feeling its because of the static keyword on the function but I still would like more clarity / insight. Thanks

Code in text form below (if it helps)

  import SwiftUI
  import Combine


   final class CurrentTime: ObservableObject {
    @Published var seconds: TimeInterval = CurrentTime.currentSecond(date: Date())
    
    private let timer = Timer.publish(every: 0.2, on: .main, in: .default).autoconnect()
    private var store = Set<AnyCancellable>()
    
    init() {
        timer.map(Self.currentSecond).assign(to: \.seconds, on: self).store(in: &store) // <-- Why am I not asked for a parameter here
    }
    
    
    private static func currentSecond(date: Date) -> TimeInterval {
        let components = Calendar.current.dateComponents([.year, .month, .day], from: date)
        let referenceDate = Calendar.current.date(from: DateComponents(year: components.year!, month: components.month!, day: components.day!))!
        return Date().timeIntervalSince(referenceDate)
    }
}
Sergio Bost
  • 2,591
  • 2
  • 11
  • 29
  • What does `map` take? How does the supplied function (by name) 'fit' this requirement? – user2864740 Apr 30 '21 at 23:40
  • Option clicked map... `func map(_ transform: @escaping (Self.Output) -> T) -> Publishers.Map` – Sergio Bost Apr 30 '21 at 23:42
  • Which parameter are you referring to? – New Dev Apr 30 '21 at 23:44
  • @NewDev in the declaration of `currentSecond` its said that it has one parameter named `date` but I am not required to call it at the call site – Sergio Bost Apr 30 '21 at 23:46
  • 1
    You're passing the function as a reference to `map`. `map` will call it. `map` accepts a closure parameter of the type `(Output) -> T`, which in this case is inferred to be: `(Date) -> TimeInterval`, which is the same type as `currentSecond` – New Dev Apr 30 '21 at 23:47
  • 1
    Read more here: https://docs.swift.org/swift-book/LanguageGuide/Functions.html#ID174 – New Dev Apr 30 '21 at 23:53

2 Answers2

4

TL:DR There is no "call site". You are referring to a function, not calling it.


Let's take a simpler case:

let array = [1,2,3]
func doubler(_ i:Int) -> Int { i*2 }
let array2 = array.map(doubler)
print(array2) // [2,4,6]

The goal is to use map to multiply every element of an array of Int by 2. And we did that.

Now, we could have done it like this:

let array2 = array.map {$0*2}

But what is that {$0*2}? It's shorthand for this:

let array2 = array.map { (i:Int) -> Int in
    let i2 = i*2
    return i2
}

In other words, the curly braces are the body of an anonymous function that takes an Int and returns an Int.

But no law says that the map parameter must be an anonymous function. It happens we already have a real function that takes an Int and returns an Int — doubler.

So instead of a function body in curly braces, we can pass the named function. How? By a function reference. We are not calling doubler, we are telling map the name of the function that it will call.

The simplest way to form a function reference is just to use the bare name of the function. It isn't the only way, but there's no problem using it here. (For more about function reference syntax, see my How do I resolve "ambiguous use of" compile error with Swift #selector syntax?)


Extra for experts: my favorite use of this shorthand is:

let sum = array.reduce(0, +)
print(sum) // 6

What just happened??? The same thing: in Swift, + is the name of a function.

matt
  • 515,959
  • 87
  • 875
  • 1,141
2

currentSecond is a function that takes a Date and returns a TimeInterval.

map is declared like this:

func map<T>(_ transform: @escaping (Date) -> T) -> Publishers.Map<Timer.TimerPublisher, T>

Its parameter type is (Date) -> T. T is a generic parameter that can be any type of your choice.

(Date) -> T is the type of functions that takes a Date as parameter and return T.

That's exactly what currentSecond is, if you set T to be TimeInterval. This is why you can pass Self.currentSecond, a function, to map. Also note that you are not calling the function at this point. map decides when to call it, and how many times to call it, if ever.

You can't pass Self.currentSecond(date: someParameter) because that would be passing a TimeInterval to map, and map doesn't want that.

static just means that the function belongs to the class, rather than individual instances of the class. The author probably thought it made more sense this way. Syntactically though, there's nothing stopping you from removing static, in which case you should also remove Self..

Sweeper
  • 213,210
  • 22
  • 193
  • 313