Fluent syntax (replacing void with a useful default return value)


(Tino) #1

Hi there,

this idea might be quite unconventional, but it is simple and wouldn't break any existing code.
Returning void has no use beside telling "there is nothing to return" and makes it impossible to perform method chaining, which drives popular systems like iostream and LINQ (the concept was promoted by Martin Fowler as fluent interface - but wikipedia has a good explanation on this: https://en.wikipedia.org/wiki/Fluent_interface)
One of its most popular use cases is database access, which imho will become quite important for Swift as it might gain popularity in the server domain.
It is useful in other areas as well, but I'll stick to databases in an example:

let kids = userDatabase.select(.name).where(.age < 18)

This is already possible in Swift today, but with a simple change, it could be much more fun:
I guess void is the natural choice for a default return value - what else is guaranteed to exist in a function?
For methods, on the other hand, there is a real object that is always available: self.
If self would be the default return value, we would get method chaining for free in all places where we now stuck with void - and whoever doesn't like the concept can still ignore the value as he did with ().

I have to admit that right now there is a proposal that wants to sanction ignoring non-void return values with warnings, which would interfere with this idea; but imho even in this case the workarounds wouldn't be that complicated.

So, please give feedback wether it is worth the burden of writing a official proposal or start by creating a library with the current toolset and try to prove the usefulness of fluent interfaces :wink:

Best regards,
Tino


(Dave Abrahams) #2

In order to evaluate this more fully, I'd like to see some examples of real code that would be improved by having this facility, but in general, I'd be concerned that this proposal making mutating/side-effectful method calls look more like purely-functional ones.

-Dave

···

On Dec 19, 2015, at 9:21 AM, Tino Heth via swift-evolution <swift-evolution@swift.org> wrote:

Hi there,

this idea might be quite unconventional, but it is simple and wouldn't break any existing code.
Returning void has no use beside telling "there is nothing to return" and makes it impossible to perform method chaining, which drives popular systems like iostream and LINQ (the concept was promoted by Martin Fowler as fluent interface - but wikipedia has a good explanation on this: https://en.wikipedia.org/wiki/Fluent_interface)
One of its most popular use cases is database access, which imho will become quite important for Swift as it might gain popularity in the server domain.
It is useful in other areas as well, but I'll stick to databases in an example:

let kids = userDatabase.select(.name).where(.age < 18)

This is already possible in Swift today, but with a simple change, it could be much more fun:
I guess void is the natural choice for a default return value - what else is guaranteed to exist in a function?
For methods, on the other hand, there is a real object that is always available: self.
If self would be the default return value, we would get method chaining for free in all places where we now stuck with void - and whoever doesn't like the concept can still ignore the value as he did with ().

I have to admit that right now there is a proposal that wants to sanction ignoring non-void return values with warnings, which would interfere with this idea; but imho even in this case the workarounds wouldn't be that complicated.

So, please give feedback wether it is worth the burden of writing a official proposal or start by creating a library with the current toolset and try to prove the usefulness of fluent interfaces :wink:


(Lily Ballard) #3

This kind of pattern (which I've always considered the "builder
pattern") is useful in various circumstances, but I don't think it is
reasonable to assume that it's the appropriate pattern to use by default
for any mutating method.

I'd much rather see an explicit method cascade operator added (e.g.
Dart's .. operator), which fulfills the same desire but without
requiring any API or even ABI changes, and without adding the overhead
of duplicating values all over the place (e.g. all the now-implicit-self
return values that aren't actually used by the caller).

-Kevin Ballard

···

On Sat, Dec 19, 2015, at 09:21 AM, Tino Heth via swift-evolution wrote:

Hi there,

this idea might be quite unconventional, but it is simple and wouldn't
break any existing code. Returning void has no use beside telling
"there is nothing to return" and makes it impossible to perform method
chaining, which drives popular systems like iostream and LINQ (the
concept was promoted by Martin Fowler as fluent interface - but
wikipedia has a good explanation on this:
https://en.wikipedia.org/wiki/Fluent_interface) One of its most
popular use cases is database access, which imho will become quite
important for Swift as it might gain popularity in the server domain.
It is useful in other areas as well, but I'll stick to databases in an
example:

let kids = userDatabase.select(.name).where(.age < 18)

This is already possible in Swift today, but with a simple change, it
could be much more fun: I guess void is the natural choice for a
default return value - what else is guaranteed to exist in a function?
For methods, on the other hand, there is a real object that is always
available: self. If self would be the default return value, we would
get method chaining for free in all places where we now stuck with
void - and whoever doesn't like the concept can still ignore the value
as he did with ().

I have to admit that right now there is a proposal that wants to
sanction ignoring non-void return values with warnings, which would
interfere with this idea; but imho even in this case the workarounds
wouldn't be that complicated.

So, please give feedback wether it is worth the burden of writing a
official proposal or start by creating a library with the current
toolset and try to prove the usefulness of fluent interfaces :wink:

Best regards, Tino

_________________________________________________
swift-evolution mailing list swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Tino) #4

In order to evaluate this more fully, I'd like to see some examples of real code that would be improved by having this facility, but in general, I'd be concerned that this proposal making mutating/side-effectful method calls look more like purely-functional ones.

Mh, maybe I'm a little bit old-fashioned, but I expect side effects everywhere unless purity is guaranteed :wink:

It's really no big thing - one example is small enough to inline it instead of pushing a playground.
As Kevin pointed out, there are solutions that are probably better, but require a real change in the language.

I think Objective-C didn't copy the chaining-pattern from Smalltalk because it's quite ugly to calculate the number of opening braces in advance, but in Swift, I really like that approach.
Builders are actually the most common use case, but the pattern fits well wherever you have to do configuration.
Of cause, it's always a bit of personal preference how to rate a solution, but for those who like the concept of fluent interfaces, returning self is much more valuable than void (well, considering the traffic, it doesn't seem that many Swift-Users care for the topic ;-).

import Cocoa

func backgroundStuff() {
  print("Working very hard...")
  NSThread.sleepForTimeInterval(0.5)
}

func finish() {
  print("Just imagine I'm doing some GUI-cleanup")
}

func current() {
  let queue = NSOperationQueue()

  let mainOperation = NSBlockOperation(block: backgroundStuff)
  mainOperation.name = "Download-Task"
  mainOperation.queuePriority = .VeryHigh
  queue.addOperation(mainOperation)

  let cleanup = NSBlockOperation(block: finish)
  cleanup.queuePriority = .Low
  cleanup.addDependency(mainOperation)
  NSOperationQueue.mainQueue().addOperation(cleanup)
}

extension NSOperation {
  func addToQueue(queue: NSOperationQueue = NSOperationQueue.mainQueue()) -> Self {
    queue.addOperation(self)
    return self
  }

  func changePriority(priority: NSOperationQueuePriority = .Normal) -> Self {
    queuePriority = priority
    return self
  }

  func changeName(name: String) -> Self {
    self.name = name
    return self
  }

  func waitFor(dependency: NSOperation) -> Self {
    addDependency(dependency)
    return self
  }
}

func fluent() {
  let queue = NSOperationQueue()
  let mainOperation = NSBlockOperation(block: backgroundStuff).changeName("DownloadTask").changePriority(.VeryHigh)
  mainOperation.addToQueue(queue)
  NSBlockOperation(block: finish).changePriority(.Low).waitFor(mainOperation).addToQueue()
}

// second one - this wont actually compile, but the needed code wouldn't be complicated
self.view.layer.changeBorderWidth(3).changeBorderColor(UIColor.redColor().CGColor)


(Radek Pietruszewski) #5

Same as Kevin. I like this pattern, but an implicit behavior of this sort doesn’t seem appropriate.

— Radek

···

On 20 Dec 2015, at 04:05, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:

This kind of pattern (which I've always considered the "builder pattern") is useful in various circumstances, but I don't think it is reasonable to assume that it's the appropriate pattern to use by default for any mutating method.

I'd much rather see an explicit method cascade operator added (e.g. Dart's .. operator), which fulfills the same desire but without requiring any API or even ABI changes, and without adding the overhead of duplicating values all over the place (e.g. all the now-implicit-self return values that aren't actually used by the caller).

-Kevin Ballard

On Sat, Dec 19, 2015, at 09:21 AM, Tino Heth via swift-evolution wrote:

Hi there,

this idea might be quite unconventional, but it is simple and wouldn't break any existing code.
Returning void has no use beside telling "there is nothing to return" and makes it impossible to perform method chaining, which drives popular systems like iostream and LINQ (the concept was promoted by Martin Fowler as fluent interface - but wikipedia has a good explanation on this: https://en.wikipedia.org/wiki/Fluent_interface)
One of its most popular use cases is database access, which imho will become quite important for Swift as it might gain popularity in the server domain.
It is useful in other areas as well, but I'll stick to databases in an example:

let kids = userDatabase.select(.name).where(.age < 18)

This is already possible in Swift today, but with a simple change, it could be much more fun:
I guess void is the natural choice for a default return value - what else is guaranteed to exist in a function?
For methods, on the other hand, there is a real object that is always available: self.
If self would be the default return value, we would get method chaining for free in all places where we now stuck with void - and whoever doesn't like the concept can still ignore the value as he did with ().

I have to admit that right now there is a proposal that wants to sanction ignoring non-void return values with warnings, which would interfere with this idea; but imho even in this case the workarounds wouldn't be that complicated.

So, please give feedback wether it is worth the burden of writing a official proposal or start by creating a library with the current toolset and try to prove the usefulness of fluent interfaces :wink:

Best regards,
Tino

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Andrey Tarantsov) #6

I'd much rather see an explicit method cascade operator added (e.g. Dart's .. operator)

This. The only right way to deal with it. Although is funny to mention Dart in this context; method cascades date back to SmallTalk at least. (I never understood why they didn't make it into Objective-C.)

A.