Skip to content

Use strong typing instead of ruby-like 1.seconds #4

@lilyball

Description

@lilyball

Extending Int and Double with properties second, seconds, minute, etc. seems like a rather bad idea. There's two problems with this:

  1. Anyone using your code in a project with other 3rd-party code that wants to do something similar will have a compile-time naming collision, making it impossible to use either one.
  2. It's weakly-typed. You're still taking NSTimeInterval as your actual time type, and all it takes is for someone to accidentally leave off the .minutes and they'll get the wrong time. This isn't a huge issue, as NSTimeInterval is used everywhere to mean seconds and people are used to it, but we can still do better.

The better approach is to use an actual Duration type that requires the user to type the unit as part of the constructor. With the ruby-like approach you can just say NSTimer.after(1) { ... } but with a proper strong type there's no way to do this. I'd suggest something like

/// A type that represents a given duration.
public struct Duration: Comparable, Hashable, Printable, DebugPrintable {
    /// The time interval of the `Duration`, in seconds.
    let seconds: NSTimeInterval
    /// The time interval of the `Duration`, in minutes.
    var minutes: NSTimeInterval {
        return seconds / 60
    }
    /// The time interval of the `Duration`, in hours.
    var hours: NSTimeInterval {
        return seconds / 3600
    }
    /// The time interval of the `Duration`, in milliseconds.
    /// Sub-millisecond values are truncated.
    var milliseconds: Int64 {
        return Int64(seconds * 1_000)
    }
    /// The time interval of the `Duration`, in microseconds.
    /// Sub-microsecond values are truncated.
    var microseconds: Int64 {
        return Int64(seconds * 1_000_000)
    }
    /// The time interval of the `Duration`, in nanoseconds.
    var nanoseconds: Int64 {
        return Int64(seconds * 1_000_000_000)
    }

    /// Construct a `Duration` for a given number of seconds.
    public init(seconds: NSTimeInterval) {
        self.seconds = seconds
    }
    /// Construct a `Duration` for a given number of minutes.
    public init(minutes: NSTimeInterval) {
        self.init(seconds: minutes * 60)
    }
    /// Construct a `Duration` for a given number of hours.
    public init(hours: NSTimeInterval) {
        self.init(seconds: hours * 3600)
    }
    /// Construct a `Duration` for a given number of milliseconds.
    // Use Int64 because milliseconds are generally not floating-point
    // values
    public init(milliseconds: Int64) {
        self.init(seconds: NSTimeInterval(milliseconds) / 1_000)
    }
    /// Construct a `Duration` for a given number of microseconds.
    public init(microseconds: Int64) {
        self.init(seconds: NSTimeInterval(microseconds) / 1_000_000)
    }
    /// Constructs a `Duration` for a given number of nanoseconds.
    // How much tolerance does a timer actually support?
    public init(nanoseconds: Int64) {
        self.init(seconds: NSTimeInterval(nanoseconds) / 1_000_000_000)
    }

    public var description: String {
        // TODO: Display human-readable string with multiple units
        return toString(seconds)
    }

    public var debugDescription: String {
        return "Duration(\(seconds))"
    }

    public var hashValue: Int {
        return seconds.hashValue
    }
}

public func +(lhs: Duration, rhs: Duration) -> Duration {
    return Duration(seconds: lhs.seconds + rhs.seconds)
}
public func -(lhs: Duration, rhs: Duration) -> Duration {
    return Duration(seconds: lhs.seconds - rhs.seconds)
}
// NB: Don't implement multiplication/division, that doesn't make any sense for
// durations. As such, we don't conform to IntegerArithmeticType either.
public func <(lhs: Duration, rhs: Duration) -> Bool {
    return lhs.seconds < rhs.seconds
}
public func ==(lhs: Duration, rhs: Duration) -> Bool {
    return lhs.seconds == rhs.seconds
}

This way you can then say NSTimer.after(Duration(seconds: 1)) { ... }. You could also experiment with replacing all those initializers with static functions instead (e.g. static func seconds(seconds: NSTimeInterval)) so that way you can say NSTimer.after(.seconds(1)) { ... }.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions