Ramblings on Stylish Closures, Currying, and Operators

Two things that happened recently got me thinking about code style. One was this post on reddit.Labelled Closure Arguments Seeing that picture was one of those times where I thought “Oh, of course. That’s how everybody hould have been doing it.” Let me show you why:

extension SequenceType {
  func scan<T> (
    var initial: T,
    @noescape combine: (T, Generator.Element) -> T
    ) -> [T] {
      return map {
        initial = combine(initial, $0)
        return initial
      }
  }
}

That’s a handy little function that works very similarly to reduce(), except that it gives you intermediate accumulating results, instead of just one at the end.

[1, 2, 3].scan(0, combine: +) // [1, 3, 6]

That’s what it looks like without the labelled closure argument. Here’s what it looks like with:

extension SequenceType {
  func scan<T>(
    var initial: T,
    @noescape combine: (
      accumulated: T,
      element: Generator.Element
    ) -> T
    ) -> [T] {
      return map {
        initial = combine(accumulated: initial, element: $0)
        return initial
      }
  }
}

What’s important to note here is that none of the calling of this function is changed – you can still use an anonymous closure, or your own argument names, or another function altogether. Only one thing changes, and that’s the autocomplete:

Screen Shot 2015-07-21 at 11.16.26

Which is pretty great! The order of those arguments is easy to get wrong (especially if you get into functions like scanl() and scanr()), and if the type of initial and whatever’s in the sequence are the same, it would compile just fine.

The second thing was an update in Beta 2:

Initializers can now be referenced like static methods by referring to .init on a static type reference or type object:

let x = String.init(5)
let stringType = String.self
let y = stringType.init(5)
let oneTwoThree = [1, 2, 3].map(String.init).reduce(“”, combine: +)

This means that instead of the common pattern of:

[1, 2, 3, 4].map { String($0) }

You now get to do:

[1, 2, 3, 4].map(String.init)

Doesn’t seem like a big deal, I know. Until you’ve got map()s and flatMap()s absolutely all over the place, and curly brackets are beginning to hurt your eyes.

This is the part that really got me thinking. Can I get rid of all the curly brackets?

The first thing to realise about passing around functions to higher-order functions is that Swift will try really hard to not make you use a closure. So, if you have a function with the same amount of arguments as some tuple or something in a list, there’s no need to whip out your $0s, you can just pass it as-is:

let arrayOne = [1, 2, 3]
let arrayTwo = [10, 20, 30]

let together = zip(arrayOne, arrayTwo) // (1, 10), (2, 20), (3, 30)

// First version:
together.map { // [11, 22, 33]
  firstNumber, secondNumber in
  return firstNumber + secondNumber
}

// We can do it shorter: return is inferred
together.map {
  firstNumber, secondNumber in firstNumber + secondNumber
}

// Still shorter again, with anonymous argument names (although
// maybe a little less readable)
together.map { $0 + $1 }

// The most readable of them all, and the shortest
together.map(+)

But there’s a slight issue. A lot of the time, the number of arguments a function takes doesn’t match the thing the higher-order function applies to perfectly. Like, say we wanted to add 3 to everything in a list:

[1, 2, 3].map { $0 + 3 }

Eww… curlies. The solution here is currying. Let’s define a new add function to curry with:

func add<T : IntegerArithmeticType>(lhs: T)(_ rhs: T) -> T {
  return lhs + rhs
}

It’s not very different from the normal way of defining a function, except that between the parameters you’ve got )( instead of , . This is reflected in how you call it, as well:

add(1)(2)

So where’s the magic? Let’s go back to our map, with the new and improved add function:

[1, 2, 3].map(add(3))

Yeah, that’s cool. It would work with other things, as well:

(0..<100).filter(greaterThan(10))

If we had defined a curried greaterThan function earlier. But we’ve not gone far enough. As we all know, the most stylish code is sugary code. But it’s not immediately clear how you could curry syntactic sugar in this case.

The answer is a little (a lot) dodgy, and it’s more of a demonstration of a technique than a recommendation, but here it is: you define a unary operator that takes two arguments, curried. Here’s what it looks like:

postfix operator < {}
postfix func < <C : Comparable>(lhs: C)(_ rhs: C) -> Bool {
  return lhs < rhs
}

(1..<100).filter(10<)

Despite the prettiness of that syntax, there are some caveats. First off, you can’t define a prefix <, or a postfix >, as those are reserved for other syntax. This means you can only define a “greater than”. Also, if you do a prefix >, the order of operations is a little surprising:

prefix func > <C : Comparable>(rhs: C)(_ lhs: C) -> Bool {
  return lhs > rhs
}

The right-hand-side is the left-hand-side argument, since it’s resolved first.

Also, if we tried this with the + operator, you’d get some ambiguity:

+3
// does this mean "positive three" or "plus three"

It gets far worse with the -

-3
// ??? 

Still, it’s cool. I wouldn’t do it with the standard operators, for the reasons above. Plus, if you’re already defining your own operators, you’ve committed a mortal sin already, so in for a penny…

There’s one more thing with these curried operators. Check if you’ve found yourself doing something like this switch statement before:

switch x {
case _ where x > 4: print("4<")
case 4            : print("4")
default           : print("4>")
}

The where bothers me. We know that what the switch statement uses under the hood is the ~= operator, so we can overload that to do our own thing, like the filter function:

func ~= <T> (lhs: T -> Bool, rhs: T) -> Bool {
  return lhs(rhs)
}

combine that with the curried operator:

switch someVar {
case 4<: print("4<")
case 4 : print("4")
default: print("4>")
}

And you’ve got a pretty cool-looking switch statement.

I think that the curried operators probably entails too many problems for what it’s worth. But currying your functions to allow them to be easier used with higher-order functions definitely increases readability. Annoyingly, when you define a function as curried, you can’t call it with non-curried syntax:

func add<T : IntegerArithmeticType>(lhs: T)(_ rhs: T) -> T {
  return lhs + rhs
}
add(1, 2) // doesn't work

I don’t know why – overloading works for other functions, and you can dispatch on arity all the time. There’s probably some reason I’m not thinking of. At any rate, bear it in mind for your APIs.

The overloading of the ~= operator, on the other hand, makes a lot of sense to me. I can’t really see a disadvantage to the case above, and it definitely increases readability.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s