Mitigating repetitive code

I find myself wishing for C-style preprocessor macros in order to help with repetitive code. For example, I recently added this to my code:

extension NSColor
{
  static var branchStroke: NSColor { return NSColor(named: "branchStroke")! }
  static var remoteBranchStroke: NSColor { return NSColor(named: "remoteBranchStroke")! }
  static var tagStroke: NSColor { return NSColor(named: "tagStroke")! }
  static var refStroke: NSColor { return NSColor(named: "refStroke")! }
  // ...and several others
}

If macros were available, this could be simplified:

#define COLOR(_name_) static var _name_: NSColor { return NSColor(named: #_name_) }

extension NSColor
{
  COLOR(branchStroke)
  COLOR(remoteBranchStroke)
  COLOR(tagStroke)
  COLOR(refStroke)
}

I looked around for discussions on why Swift doesn't have macros, but so far everything I've seen focuses on defining constants and checking for build flags, and of course Swift has ways to accomplish both. But what about for this kind of use case?

There is of course the "dangerous if misused" angle, but code duplication is a particular pet peeve of mine and I'd really like some way of cleaning that up.

Macros are something that has been discussed and many people (myself included) want them. It's just not something people have really started hammering out. I think most people don't want another c-preprocessor. We want proper macros that could manipulate the AST during compilation.

9 Likes

I'm indifferent about macros; I find it a crutch for features that are not yet added to the language, but wouldn't mind if it was added.

(Alternative approach for the example you provided.)

As an aside, the example you provided can be (sort of) rewritten using the newly introduced @dynamicMemberLookup attribute:

@dynamicMemberLookup protocol ColorLookup {}

extension ColorLookup {
  subscript (dynamicMember key: String) -> NSColor {
    return NSColor(named: key)!
  }
}

extension NSColor: ColorLookup {}

Using it:

NSColor().branchStroke

But this is entering abuse territory.

1 Like

In the spirit of ignoring the general question and focusing on the example, perhaps an enum with a String raw value could be used in some of these cases, e.g. ignoring all the bad name choices because I'm not sure exactly what the context of your code is:

enum StrokeColorType: String {
  case branch, removeBranch, tag, ref // …
}

extension NSColor {
  static func strokeColor(_ type: StrokeColorType) -> NSColor {
    return NSColor(named: type.rawValue + "Stroke")!
  }
}

NSColor.strokeColor(.tag)
4 Likes

@dynamicMemberLookup Is tempting, but I'd rather be explicit about which colors are available and get compile time checking. The enum I think is appealing because it's a way to explicitly categorize the colors.

Actually, with a good macro system, a few language features could be implemented in the Standard Library, resulting in a simpler language: Codable, Equatable, Hashable, CasseIterable synthesizing.

When we get around to seriously considering a macro system, I think that we should start by looking at the places where the compiler auto synthesizes stuff and try to generalize it, and move these features into the stdlib:

  1. Property macros (like the old property behaviors proposal), to generalize lazy and didSet/willSet observers.
  2. Protocol member synthesis macros to generalize hashable, equatable, allCases, etc.
    perhaps others
  3. Look at the stuff currently being gyb'd in the standard library and sort it out by use cases.

At the end of this, I think there will be room for a general hygienic expander of some sort, e.g. a "compile time #for loop" that iterates over compile time defined lists - which could provide functionality similar to the .def files in the swift/llvm/clang codebases.

That said, I personally prefer to wait on that general feature until we explore the other structured approaches (along with related things like constexprs) and when there is a specific really important use case driving it.

-Chris

8 Likes