[Idea] Syntactic sugar for using methods as functions


(Manuel Krebber) #1

Hi everyone,

I was thinking it would be nice to have a similar short notation for
using methods in a functional way as there is for enum cases (and as
I've been told static members in general).
Let me give a rather contrived example:

struct Foo {
    let bar: Int

    func getBar() -> Int {
        return self.bar
    }

}

let foos = [Foo(bar: 1), Foo(bar: 2), Foo(bar: 3)]
let bars = foos.map{ $0.getBar() }

What I am suggesting is to add syntactic sugar to bridge the gap between
functional and object oriented programming. So instead you could write
the following:

let bars = foos.map(.getBar)

While for parameterless functions this might not seem like much of an
improvement, I think it helps when there are parameters involved:

struct Foo {
    let bar: Int
    
    func combine(other: Foo) -> Foo {
        return Foo(bar: other.bar + self.bar)
    }
}

let foos = [Foo(bar: 5), Foo(bar: 6), Foo(bar: 1)]
let reduced = foos.reduce(Foo(bar: 0)) { $0.combine(other: $1) }

Which could become:

let reduced = foos.reduce(Foo(bar: 0), .combine)

This would also enable easier usage of custom operators for partial
functions etc. on these methods.

Basically whenever there is a parameter type (T, ...) -> U somewhere,
one could write the prefix dot shortcut and the compiler would look for
a method in type T with a signature (...) -> U and wrap the call to the
method in a closure. In case that no such method can be found or it
cannot be uniquely determined, it will result in a compile time error.

This is just an idea though. Do you think this would be useful? I could
see it help libraries like the Dollar library. It could then be used in
both functional and object oriented ways, since most functions could
become methods while maintaining a short syntax.

Kind regards, Manuel


(David Rönnqvist) #2

+1. This makes sense to me.

Since the API Guidelines say: “Prefer methods and properties to free functions”, it would make sense to add sugar like this to make methods just as convenient and clear to pass to higher order functions as free functions currently are.

I’m also wondering if the same syntactic sugar could work for property getters.

- David

···

On 27 Jun 2016, at 12:00, Manuel Krebber via swift-evolution <swift-evolution@swift.org> wrote:

Hi everyone,

I was thinking it would be nice to have a similar short notation for
using methods in a functional way as there is for enum cases (and as
I've been told static members in general).
Let me give a rather contrived example:

struct Foo {
   let bar: Int

   func getBar() -> Int {
       return self.bar
   }

}

let foos = [Foo(bar: 1), Foo(bar: 2), Foo(bar: 3)]
let bars = foos.map{ $0.getBar() }

What I am suggesting is to add syntactic sugar to bridge the gap between
functional and object oriented programming. So instead you could write
the following:

let bars = foos.map(.getBar)

While for parameterless functions this might not seem like much of an
improvement, I think it helps when there are parameters involved:

struct Foo {
   let bar: Int

   func combine(other: Foo) -> Foo {
       return Foo(bar: other.bar + self.bar)
   }
}

let foos = [Foo(bar: 5), Foo(bar: 6), Foo(bar: 1)]
let reduced = foos.reduce(Foo(bar: 0)) { $0.combine(other: $1) }

Which could become:

let reduced = foos.reduce(Foo(bar: 0), .combine)

This would also enable easier usage of custom operators for partial
functions etc. on these methods.

Basically whenever there is a parameter type (T, ...) -> U somewhere,
one could write the prefix dot shortcut and the compiler would look for
a method in type T with a signature (...) -> U and wrap the call to the
method in a closure. In case that no such method can be found or it
cannot be uniquely determined, it will result in a compile time error.

This is just an idea though. Do you think this would be useful? I could
see it help libraries like the Dollar library. It could then be used in
both functional and object oriented ways, since most functions could
become methods while maintaining a short syntax.

Kind regards, Manuel

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


(Anton Zhilin) #3

struct Foo {
    let bar: Int

    func getBar() -> Int {
        return self.bar
    }

}

let foos = [Foo(bar: 1), Foo(bar: 2), Foo(bar: 3)]
let bars = foos.map(.getBar)

This can be done now:
let bars = foos.map(Foo.getBar)

While for parameterless functions this might not seem like much of an
improvement, I think it helps when there are parameters involved:

struct Foo {
    let bar: Int

    func combine(other: Foo) -> Foo {
        return Foo(bar: other.bar + self.bar)
    }
}

let foos = [Foo(bar: 5), Foo(bar: 6), Foo(bar: 1)]
let reduced = foos.reduce(Foo(bar: 0), .combine)

This also can be done now:
let reduced = foos.reduce(Foo(bar: 0), Foo.combine)

Are you suggesting to drop class name? That might make sense, but not
for Swift 3.


(Haravikk) #4

While I see some potential in this, I wonder if it's a bit too ambiguous at the moment, and might require a wider rethink of the dot shorthand; in the example above it's not clear at a glance that this references the .getBar method of elements, vs a property or method of foos, or self, or Self. Enums are kind of a special case, but they are at least a static member so there's a clearer case for them vs this example which is an instance member, which could lead to ambiguity.

I mean, I'd be all for allowing this shorthand for instance members as well, but we'd need to get around issues of this ambiguity, perhaps with a shorthand identifier for type? As pointed out, you can actually use Foo.getBar, but while this is fine for a type with such a short name, it's not convenient if your type name is much longer, meaning in many cases it will remain easiest to just do { $0.getBar() } or similar. If we could reserve a special type identifier, or use $T or something it might be easier, but it'd need to be an elegant choice (I don't really like either of mine, it's just to give the idea).

···

On 27 Jun 2016, at 11:00, Manuel Krebber via swift-evolution <swift-evolution@swift.org> wrote:

Hi everyone,

I was thinking it would be nice to have a similar short notation for
using methods in a functional way as there is for enum cases (and as
I've been told static members in general).
Let me give a rather contrived example:

struct Foo {
   let bar: Int

   func getBar() -> Int {
       return self.bar
   }

}

let foos = [Foo(bar: 1), Foo(bar: 2), Foo(bar: 3)]
let bars = foos.map{ $0.getBar() }

What I am suggesting is to add syntactic sugar to bridge the gap between
functional and object oriented programming. So instead you could write
the following:

let bars = foos.map(.getBar)


(Félix Cloutier) #5

I don't have anything against it, besides that it is an additive feature and should probably wait for after Swift 3.

Félix

···

Le 27 juin 2016 à 07:22:42, David Rönnqvist via swift-evolution <swift-evolution@swift.org> a écrit :

+1. This makes sense to me.

Since the API Guidelines say: “Prefer methods and properties to free functions”, it would make sense to add sugar like this to make methods just as convenient and clear to pass to higher order functions as free functions currently are.

I’m also wondering if the same syntactic sugar could work for property getters.

- David

On 27 Jun 2016, at 12:00, Manuel Krebber via swift-evolution <swift-evolution@swift.org> wrote:

Hi everyone,

I was thinking it would be nice to have a similar short notation for
using methods in a functional way as there is for enum cases (and as
I've been told static members in general).
Let me give a rather contrived example:

struct Foo {
  let bar: Int

  func getBar() -> Int {
      return self.bar
  }

}

let foos = [Foo(bar: 1), Foo(bar: 2), Foo(bar: 3)]
let bars = foos.map{ $0.getBar() }

What I am suggesting is to add syntactic sugar to bridge the gap between
functional and object oriented programming. So instead you could write
the following:

let bars = foos.map(.getBar)

While for parameterless functions this might not seem like much of an
improvement, I think it helps when there are parameters involved:

struct Foo {
  let bar: Int

  func combine(other: Foo) -> Foo {
      return Foo(bar: other.bar + self.bar)
  }
}

let foos = [Foo(bar: 5), Foo(bar: 6), Foo(bar: 1)]
let reduced = foos.reduce(Foo(bar: 0)) { $0.combine(other: $1) }

Which could become:

let reduced = foos.reduce(Foo(bar: 0), .combine)

This would also enable easier usage of custom operators for partial
functions etc. on these methods.

Basically whenever there is a parameter type (T, ...) -> U somewhere,
one could write the prefix dot shortcut and the compiler would look for
a method in type T with a signature (...) -> U and wrap the call to the
method in a closure. In case that no such method can be found or it
cannot be uniquely determined, it will result in a compile time error.

This is just an idea though. Do you think this would be useful? I could
see it help libraries like the Dollar library. It could then be used in
both functional and object oriented ways, since most functions could
become methods while maintaining a short syntax.

Kind regards, Manuel

_______________________________________________
swift-evolution mailing list
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


(Charlie Monroe) #6

struct Foo {
   let bar: Int

   func getBar() -> Int {
       return self.bar
   }

}

let foos = [Foo(bar: 1), Foo(bar: 2), Foo(bar: 3)]
let bars = foos.map(.getBar)

This can be done now:
let bars = foos.map(Foo.getBar)

This will only get you an array of closures:

let bars = foos.map(Foo.getBar) // [() -> Int, () -> Int, () -> Int]
bars[0] // () -> Int
bars[0]() // 1

···

On Jun 27, 2016, at 7:33 PM, Anton Zhilin via swift-evolution <swift-evolution@swift.org> wrote:

While for parameterless functions this might not seem like much of an
improvement, I think it helps when there are parameters involved:

struct Foo {
   let bar: Int

   func combine(other: Foo) -> Foo {
       return Foo(bar: other.bar + self.bar)
   }
}

let foos = [Foo(bar: 5), Foo(bar: 6), Foo(bar: 1)]
let reduced = foos.reduce(Foo(bar: 0), .combine)

This also can be done now:
let reduced = foos.reduce(Foo(bar: 0), Foo.combine)

Are you suggesting to drop class name? That might make sense, but not
for Swift 3.

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


(Manuel Krebber) #7

This also can be done now:
let reduced = foos.reduce(Foo(bar: 0), Foo.combine)

Are you suggesting to drop class name? That might make sense, but not
for Swift 3.

That is what I am suggesting. Basically I would like to extend the .foo shorthand that already works for static members to methods in case the target type is a function type.

I guess I will bring it up again later after Swift 3 is done.

Kind regards, Manuel


(Anton Zhilin) #8

Charlie Monroe via swift-evolution <swift-evolution@...> writes:

This will only get you an array of closures:

let bars = foos.map(Foo.getBar) // [() -> Int, () -> Int, () -> Int]
bars[0] // () -> Int
bars[0]() // 1

Just checked:

struct A {
    func a() -> Int { return 0 }
}
print(A.a.dynamicType) //=> (A) -> (()) -> Int

Wut?

First of all, (()) -> Int should mean function with one parameter, since
tuple splat behaviour is going to be removed.

Then, with uncurrying proposal, A.a.dynamicType should be (A) -> Int,
because both self and other parameters are now passed together.

My guess is that both accepted proposals are still not implemented. Once
it's done, we will get (A.a)(A()) == 0 and [A(), A()].map(A.a) == [0, 0]


(Félix Cloutier) #9

Wow. I did not see that coming!

Either way, I agree that Foo.bar works, but then again, so does Enum.value and we give that one a free pass. Beyond "it's not how it works right now", I don't really have any serious argument against the feature. I'd like to have that at some point.

Félix

···

Le 27 juin 2016 à 14:07:46, Anton Zhilin via swift-evolution <swift-evolution@swift.org> a écrit :

Charlie Monroe via swift-evolution <swift-evolution@...> writes:

This will only get you an array of closures:

let bars = foos.map(Foo.getBar) // [() -> Int, () -> Int, () -> Int]
bars[0] // () -> Int
bars[0]() // 1

Just checked:

struct A {
   func a() -> Int { return 0 }
}
print(A.a.dynamicType) //=> (A) -> (()) -> Int

Wut?

First of all, (()) -> Int should mean function with one parameter, since
tuple splat behaviour is going to be removed.

Then, with uncurrying proposal, A.a.dynamicType should be (A) -> Int,
because both self and other parameters are now passed together.

My guess is that both accepted proposals are still not implemented. Once
it's done, we will get (A.a)(A()) == 0 and [A(), A()].map(A.a) == [0, 0]

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