rethrows as first-class type annotation


(Alexandre Lopoukhine) #1

Hello all,

I’m not sure that I’ve expressed myself correctly, but essentially, I want the following type declaration to be legal: "I rethrows -> O”. I’ve looked through the mailing list and the proposals, and haven’t seen this mentioned, but I apologise if this has already been discussed.

The following example is rather specific, but I’m sure that the solution to this problem can be useful elsewhere. In playing around with the concept of pipes and combinations of functions, I thought that it would be nice to have an operator with the following property:
(Class.instanceFunction `operator` input)(instance) === instance.instanceFunction(input)
, which would allow me to pass the operator construct into “map”, and other higher-order operations.

I attempted to implement this with the following operator functions:

1)
public func |++|<C,TI,TO,O>(classFunc: C -> (TI -> TO) -> O, transform: (TI -> TO)) -> C -> O {
    return {classFunc($0)(transform)}
}

2)
public func |++|<C,TI,TO,O>(classFunc: C -> (TI -> TO) throws -> O, transform: (TI -> TO)) -> C -> O {
    return {try! classFunc($0)(transform)}
}

3)
public func |+++|<C,TI,TO,O>(classFunc: C -> (TI throws -> TO) throws -> O, transform: (TI throws -> TO)) -> C throws -> O {
    return {try classFunc($0)(transform)}
}

They can be used in the following way:

1) (Array.myMap |++| double)([1,2,3]) // Where myMap doesn’t accept a throwing function.

2) (Array.map |++| double)([1,2,3])

3) try? (Array.map |+++| throwIfEven)([1,2,3])

As you can see, this is rather messy. I think that the type system should be extended to have a true equivalent of the rethrows annotation in function declarations. It would then allow the following statement:

4)
public func |++|<T,I,O>(classFunc: T -> I rethrows -> O, input: I) -> T rethrows -> O {
    return {try classFunc($0)(input)}
}

T rethrows -> O will then be able to be unified with a non-throwing function type
  iff the type of classFunc($0) unifies with a non-throwing function
  (iff input is a non-throwing function in the of Array.map)

Thus, all of the above uses should be legal, with the benefit of simpler unification.

My limited knowledge of compilers doesn’t doesn’t give me any reason to think that this isn’t possible, and my impression is that moving “rethrows” from a special annotation to a more general one would simplify the language.

What do you all think?

— Sasha


(Dmitri Gribenko) #2

Hi Alexandre,

I think for this use case we don't actually need 'rethrows' to become
a part of the closure type, we just need the compiler to allow and
"instantiate" it in more places.

The case where we would need 'rethrows' to become a first class part
of the type system is if we wanted 'rethrows' to be a part of the
signature of the closure itself, for example:

(swift) let forEach = [ 10, 20, 30 ].forEach
// forEach : (@noescape (Int) throws -> Void) throws -> () = (Function)

Here, a more precise type would be (@noescape (Int) throws -> Void)
rethrows -> Void.

Dmitri

···

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/


(Alexandre Lopoukhine) #3

Hi Dmitri,

This is a better example than any that I have come up with so far as to why “rethrows” should be a part of the signature. You shouldn’t have to use “try!” to apply a non-throwing function, like {print($0)} to “forEach”.

— Sasha

···

On 20 Dec 2015, at 13:37, Dmitri Gribenko <gribozavr@gmail.com> wrote:

Hi Alexandre,

I think for this use case we don't actually need 'rethrows' to become
a part of the closure type, we just need the compiler to allow and
"instantiate" it in more places.

The case where we would need 'rethrows' to become a first class part
of the type system is if we wanted 'rethrows' to be a part of the
signature of the closure itself, for example:

(swift) let forEach = [ 10, 20, 30 ].forEach
// forEach : (@noescape (Int) throws -> Void) throws -> () = (Function)

Here, a more precise type would be (@noescape (Int) throws -> Void)
rethrows -> Void.

Dmitri

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/


(Jordan Rose) #4

John, IIRC you had some reason why this wasn't a great idea, but I can't remember it. It seems useful to me too, if not something that comes up too often.

Jordan

···

On Dec 20, 2015, at 2:46 , Alexandre Lopoukhine via swift-evolution <swift-evolution@swift.org> wrote:

Hi Dmitri,

This is a better example than any that I have come up with so far as to why “rethrows” should be a part of the signature. You shouldn’t have to use “try!” to apply a non-throwing function, like {print($0)} to “forEach”.

— Sasha

On 20 Dec 2015, at 13:37, Dmitri Gribenko <gribozavr@gmail.com> wrote:

Hi Alexandre,

I think for this use case we don't actually need 'rethrows' to become
a part of the closure type, we just need the compiler to allow and
"instantiate" it in more places.

The case where we would need 'rethrows' to become a first class part
of the type system is if we wanted 'rethrows' to be a part of the
signature of the closure itself, for example:

(swift) let forEach = [ 10, 20, 30 ].forEach
// forEach : (@noescape (Int) throws -> Void) throws -> () = (Function)

Here, a more precise type would be (@noescape (Int) throws -> Void)
rethrows -> Void.

Dmitri

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/

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


(David Waite) #5

I don’t know what the compiler does internally. However, I look at a signature like

func forEach(fn:Element throws->()) rethrows

and imagine that conceptually this exposes two overloads for this “forEach” function:

a) func forEach(fn:Element->())

b) func forEach(fn:Element throws->()) throws

In fact, I expected the swift CLI to give me an error when I attempted to define all three of these as functions, but no such luck.

-DW

···

On Dec 21, 2015, at 7:09 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

John, IIRC you had some reason why this wasn't a great idea, but I can't remember it. It seems useful to me too, if not something that comes up too often.

Jordan

On Dec 20, 2015, at 2:46 , Alexandre Lopoukhine via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi Dmitri,

This is a better example than any that I have come up with so far as to why “rethrows” should be a part of the signature. You shouldn’t have to use “try!” to apply a non-throwing function, like {print($0)} to “forEach”.

— Sasha

On 20 Dec 2015, at 13:37, Dmitri Gribenko <gribozavr@gmail.com <mailto:gribozavr@gmail.com>> wrote:

Hi Alexandre,

I think for this use case we don't actually need 'rethrows' to become
a part of the closure type, we just need the compiler to allow and
"instantiate" it in more places.

The case where we would need 'rethrows' to become a first class part
of the type system is if we wanted 'rethrows' to be a part of the
signature of the closure itself, for example:

(swift) let forEach = [ 10, 20, 30 ].forEach
// forEach : (@noescape (Int) throws -> Void) throws -> () = (Function)

Here, a more precise type would be (@noescape (Int) throws -> Void)
rethrows -> Void.

Dmitri

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com <mailto:gribozavr@gmail.com>>*/

_______________________________________________
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


(John McCall) #6

John, IIRC you had some reason why this wasn't a great idea, but I can't remember it. It seems useful to me too, if not something that comes up too often.

I don’t remember off-hand. I think it’s theoretically supportable, but it adds extra complexity to the type system that I wanted to avoid if possible.

John.

···

On Dec 21, 2015, at 6:09 PM, Jordan Rose <jordan_rose@apple.com> wrote:

Jordan

On Dec 20, 2015, at 2:46 , Alexandre Lopoukhine via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi Dmitri,

This is a better example than any that I have come up with so far as to why “rethrows” should be a part of the signature. You shouldn’t have to use “try!” to apply a non-throwing function, like {print($0)} to “forEach”.

— Sasha

On 20 Dec 2015, at 13:37, Dmitri Gribenko <gribozavr@gmail.com <mailto:gribozavr@gmail.com>> wrote:

Hi Alexandre,

I think for this use case we don't actually need 'rethrows' to become
a part of the closure type, we just need the compiler to allow and
"instantiate" it in more places.

The case where we would need 'rethrows' to become a first class part
of the type system is if we wanted 'rethrows' to be a part of the
signature of the closure itself, for example:

(swift) let forEach = [ 10, 20, 30 ].forEach
// forEach : (@noescape (Int) throws -> Void) throws -> () = (Function)

Here, a more precise type would be (@noescape (Int) throws -> Void)
rethrows -> Void.

Dmitri

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com <mailto:gribozavr@gmail.com>>*/

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


(Alexandre Lopoukhine) #7

I definitely see where John is coming from, but my intuition (which I have to agree is of arguable worth) is telling me that it’s a good direction to explore. I believe that the current system is broken, even if the degree of this is not significant, and doesn’t affect most uses. As the language matures, and the use of error-throwing with a more functional style of programming becomes more prevalent, I think that some form of advance in the matching system will have to be made.

Here’s my perspective on this: (Disclaimer: I’m having a bit of trouble getting my head around the compiler, and exactly how “rethrows" and type unification work.)

* I’m very fond of the availability of rethrows as a function declaration annotation, it’s very clear in its requirements and in its guarantees. By allowing it to be a type annotation, these same contracts can be used by the readers of the code, and by the compiler.
(For example, a function is throwing iff there is a reachable, uncaught, throwing expression; a function is rethrowing iff it is not throwing and there is a reachable, uncaught, call to a function, taken as input, that is marked as throwing. This may or may not be useful to the compiler, but it would definitely help me when dealing with throwing code.)

* Dmitri’s suggestion of making the compiler advanced enough to have the rethrows generated automatically doesn’t seem to be any less complicated than requiring the programmer to add it to the function type by hand. I’m also not entirely sure how it would work in a context where you declare that a function with rethrowing semantics is expected. Maybe I just missed the point of the comment entirely.

— Sasha

P.S. Is there documentation somewhere of the rules by which types are unified today, and of the way that "rethrows" is currently handled, or should I just keep working at reading the code?

···

On 22 Dec 2015, at 20:41, John McCall <rjmccall@apple.com> wrote:

On Dec 21, 2015, at 6:09 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:
John, IIRC you had some reason why this wasn't a great idea, but I can't remember it. It seems useful to me too, if not something that comes up too often.

I don’t remember off-hand. I think it’s theoretically supportable, but it adds extra complexity to the type system that I wanted to avoid if possible.

John.

Jordan

On Dec 20, 2015, at 2:46 , Alexandre Lopoukhine via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi Dmitri,

This is a better example than any that I have come up with so far as to why “rethrows” should be a part of the signature. You shouldn’t have to use “try!” to apply a non-throwing function, like {print($0)} to “forEach”.

— Sasha

On 20 Dec 2015, at 13:37, Dmitri Gribenko <gribozavr@gmail.com <mailto:gribozavr@gmail.com>> wrote:

Hi Alexandre,

I think for this use case we don't actually need 'rethrows' to become
a part of the closure type, we just need the compiler to allow and
"instantiate" it in more places.

The case where we would need 'rethrows' to become a first class part
of the type system is if we wanted 'rethrows' to be a part of the
signature of the closure itself, for example:

(swift) let forEach = [ 10, 20, 30 ].forEach
// forEach : (@noescape (Int) throws -> Void) throws -> () = (Function)

Here, a more precise type would be (@noescape (Int) throws -> Void)
rethrows -> Void.

Dmitri

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com <mailto:gribozavr@gmail.com>>*/

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