
Swift Generics Evolution - mpweiher
https://www.timekl.com/blog/2019/04/14/swift-generics-evolution/
======
comex
I've always thought of Swift as a sort of "sister language" to Rust, since so
many features have almost the same semantics and syntax, but with some keyword
names switched around and slightly different punctuation.

These proposed changes continue in that vein :)

\- As the post notes, `any Shape` is the same as Rust's `dyn Shape` - both
languages originally allowed you to write simply `Shape` (i.e. a
trait/protocol name) to get an existential type, but later decided that was
confusing.

\- `some Shape` is the same as Rust's `impl Shape`.

\- `Shape<.Renderer == T>` is equivalent to Rust's `Shape<Renderer=T>`.

(This is not meant as a criticism of Swift; Rust has copied some things from
Swift as well. Just interesting to note.)

~~~
twic
Is `some Shape` actually the same as `impl Shape`? In Rust, `impl Shape` means
"a fixed, statically knowable type which implements Shape, and which the
implementation of this function knows but its callers do not". I get the
impression that in Swift it means "one of the possible numerous which
implements Shape, chosen at runtime by the implementation, and returned to the
caller as an existential".

I may have misunderstood what is planned for Swift though.

~~~
skohan
I believe that's incorrect. Swift generics deal with statically-knowable
types.

My reading was:

`some` -> Some specific type with these constraints which will be known at
compile time

`any` -> Any value conforming to these constraints which may vary at runtime

~~~
slavapestov
In the current “opaque types” prototype implementation `some` still hides the
implementation of the type across module boundaries, meaning it can be changed
without recompiling clients. The specific guarantee is that (in a single
process execution) each invocation of a some-returning function returns the
same concrete type.

------
the_duke
Swift is a really cool language. Quite similar to Rust, but easier to use. (
GC, no borrow checker, no multi-threading safeguards, ...)

I just wish cross platform support was a real priority. The Ubuntu packages
are all there is, and much of the ecosystem is centered around (Mac/i)OS.

~~~
jfk13
I don't really see how "quite similar to Rust" and "GC, no borrow checker, no
multi-threading safeguards" can both be true at the same time.

~~~
0815test
It's quite similar to Rust... if Rust literally used Arc<Mutex<T>> everywhere.
And Arc<Mutex<T>> everywhere might actually have _lower_ throughput than
tracing GC! (Though it's arguably preferable for low-latency code, so it might
make some sense for the typical use cases of ObjC and Swift.)

~~~
dep_b
The advantage of ARC is that the cost of deallocating is very constant and
always happens at the same moment. This helps enormously when you are doing
performance sensitive stuff like scrolling over a list of complex cells.

With the Java style of GC the minimum and maximum cost and timing of GC are
just not so predictable so in real-world scenarios ARC based apps feel more
fluent.

It's like playing games. You'll notice the 10FPS dips more than the difference
between 50FPS or 60FPS on average.

~~~
pjmlp
Not really.

Cascading deletes of highly nested data structures are similar to pause the
world in tracing GCs.

If the way destruction is triggered does not take this into account, stack
overflows are bound to happen.

Finally it introduces slowdowns in shared data structures used across threads.

Herb Sutter has a very good CppCon talk about these issues.

EDIT: Forgot to mention that Swift was the loosing language at CCC talk about
implementing device drivers in memory safe languages. All the ones with
tracing GC had a better outcome.

~~~
dep_b
I think nothing of what you just wrote undermines what I just said.

> Cascading deletes of highly nested data structures are similar to pause the
> world in tracing GCs.

Which is simply difficult for every language. But even then still more
predictable than Java "we'll do it when we feel ready for it" approach.

~~~
pjmlp
Yet all the languages with a tracing GC wipe the floor of Swift's reference
counting implementation.

"35C3 - Safe and Secure Drivers in High-Level Languages"

[https://www.youtube.com/watch?v=aSuRyLBrXgI](https://www.youtube.com/watch?v=aSuRyLBrXgI)

[https://github.com/ixy-languages/ixy-languages](https://github.com/ixy-
languages/ixy-languages)

The subject of Herb's talk was actually going through all the reference
counting problems to introduce in the end a lightweight implementation of
deferred pointers, which isn't nothing more than basic tracing GC.

[https://www.youtube.com/watch?v=JfmTagWcqoE](https://www.youtube.com/watch?v=JfmTagWcqoE)

~~~
dep_b
Well if you think a driver is somewhat similar to a front-end implementation
to begin with then it's OK. If "wiping the floor" means what I see on a day by
day basis comparing JVM based UI's versus the same thing built in Swift you're
probably talking about something different to begin with.

I'm talking about predictable performance and guaranteed minimum performance.
I don't think you're speaking about the same thing.

The video seems to be interesting anyway, so thanks for that.

~~~
pjmlp
Actually it is, that network packet is not going to wait.

"Wiping the floor" means getting last place against all tracing GC languages
used to research the mentioned paper.

Yes, many JVM based UIs do suck, mostly because the authors didn't bother to
learn how to use Swing properly by reading books like Filthy Rich Clients.

The thing with Swift UIs, is that they are actually C and C++ UIs, given that
those are the languages used to implement Core Graphics, Core Animation and
Metal. Additionally Cocoa is still mostly Objective-C, with performance
critical sections making use of NSAutoreleasePool like in the old good
NeXTSTEP days.

Coming back to Java, factory automation and high integrity systems are
perfectly fine with it.

[https://www.aicas.com/cms/](https://www.aicas.com/cms/)

[https://www.ptc.com/en/products/developer-
tools/perc](https://www.ptc.com/en/products/developer-tools/perc)

~~~
dep_b
Well try to use C++ without the C part, see how that goes. C is a part of
Objective-C and you can write Swift code that will compile byte for byte into
code as performant as C. It's ugly, it's unsafe but it's part of the language
and if you need it it's there.

You sound like those people that complain that Objective-C is so slow compared
to C because NSArray is much slower than it's C counterpart. Objective-C is
always exactly as fast as C since you can always write C in Objective-C.

And if all Java based UI's suck despite the fact that it's the most popular
programming language and despite the fact that it powers the most popular
operating system you might start suspecting there's something wrong with it,
but nope, you've read a paper and saw a video. About a kernel driver. OK.

~~~
pjmlp
The funny part is that the languages with tracing GC that wipe Swift's memory
management on the paper aren't even Java, rather OCaml, Go and C#.

Your focus on Java, without spending one second reading the paper benchmarks,
from a well respected researcher in the CCC community, just demonstrates a
typical defensive reaction among reference counting proponents.

Using C++ without the C part is the whole point of modern C++ and the Core
Guidelines. In fact there are several benchmarks where the C++ version gets
better optimized.

As for Android, it isn't Java as we know it, and any travel through
/r/androiddev will teach Google might have tons of PhDs, but they certainly
don't work on Android, given how a lot of things are implemented.

~~~
dep_b
I'm kind of baffled you never got back to the first statement I made. It's
like you got totally lost in your mission to prove that Swift sucks. I never
claimed magic performance in Swift, I said ARC might be slower but it's a lot
more predictable with a better minimum performance.

The only thing I see is "here's a ton of text and video to slough through made
by people smarter than me", nothing concrete that undermines my original
statement in any way.

And yes C++ cannot exist without C as it is an extension to C. You can write
pure C in a C++ file and it will still work. You can write pure C in an
Objective-C file and it will still work. You can write some ugly bastard
syntax version of C in Swift and it will still work.

I'm very much willing to accept Swift's performance for a device driver
(possibly the first device driver ever written in Swift) is worse than in many
other languages if you stick to the safe bits of Swift. It just doesn't have
much to do with what I wrote.

------
dep_b
Working with Generics in Swift often made me feel I was missing something,
pushing me to refactor my code once things started pushing past the trivial
usage of generics because types started fighting back. Indeed I noticed that
the requirement that types are declared “from the outside” made things harder.

Another thing is combining protocols with generics. That feels a lot more
convoluted than might be necessary.

Seeing people express exactly what is the problem and what could be a solution
is impressive. I hope we’ll see reverse generics soon.

------
tambourine_man
Whenever possible, I like using tech that I can mostly understand.

A small simple language is something Swift is not.

~~~
melling
What’s your ideal simple language?

I’d argue that with type inference, immutability, and non-null as a default,
Swift is an easy language for anyone to be productive quite quickly.

    
    
        let x = “I’m immutable and not null”
    
        let favorite = [“Java“, “Perl”, “Swift”].shuffled().first

~~~
romanovcode
Why is `shuffled()` a function and `first` a property? Seems weird and not
simple.

~~~
the_duke
Yeah first being a property is weird to me too.

Although I do believe Swift has "virtual properties" that are really a method
under the hood.

~~~
skohan
The reverse seems weird to me. Things like the first element, or the count of
an array seem conceptually more like properties than functions: they're just
values derived from the data structure.

The fact that you need to use function call syntax for things like this in
other languages seems like it is unnecessarily exposing implementation details
to the API because of limitations of the language.

~~~
the_duke
For me it's exactly the other way around.

Having "first" as a property feels like exposing implementation details to me.
Sure, a array/vector is a contiguous slice of memory filled with items of a
certain size. But the vector/array is is basically a pointer to the start of
the memory + a size, so "first" is not a inherent part of the data structure,
rather a computed/derived property.

When I see a property I think member of a struct, which is not the case here.

(ps: this is all really just hair splitting, it's a minor difference that you
would get used to pretty quickly either way)

~~~
wool_gather
It's a reasonable point. But here `first` is actually declared in the
`Collection` interface, which `Array` implements. There's no guarantee about
what it's actually doing. (Although possibly it has a documented expectation
to be O(1), can't remember at the moment.)

~~~
saagarjha
Collection's startIndex and subscripting is supposed to be O(1), so I'd assume
that first is also supposed to be O(1) since it's trivial to implement it by
composing the first two (in fact, this is how Swift implements it by default
if you don't override it:
[https://github.com/apple/swift/blob/e08b2194487d883896a377a0...](https://github.com/apple/swift/blob/e08b2194487d883896a377a0f343577775845cd3/stdlib/public/core/Collection.swift#L1095))

------
tsss
I don't get the motivation behind this. I assume Swift has subtype
polymorphism, so why not do this:

    
    
      func allEncompassingShape() -> Shape {
        return SpecialGiantShape()
      }
    

This function returns _some_ type that may be a subtype of Shape.

~~~
jakobegger
Because that does not work with generic protocols. You can't have a function
return a sequence:

    
    
        func make_numbers() -> Sequence<Int> {
         // Cannot specialize non-generic type 'Sequence'
        }
        func make_numbers() -> Sequence {
         // Protocol 'Sequence' can only be used as a generic constraint because it has Self or associated type requirements
        }
    

Whereas with this change you probably could at some point do something like
this:

    
    
        func make_numbers() -> some Sequence<.Element == Int>

~~~
tsss
What about

    
    
      func make_numbers() -> Sequence<Any>
    

This should work as long as Sequence is covariant on Element. You could return
any possible Sequence here, like Sequence<Int> or Sequence<Double>. The user
of the function wouldn't know.

You could also do something like

    
    
      func make_numbers() -> Sequence<Comparable&PartialOrder>

~~~
justinrlle
I think your confusing Swift's protocols with something akin to Java's
interfaces. I don't really know Swift, but it looks a lot like Rust, so I'm
going to assume that they work the same.

In Java, the following function definition compiles just fine:

    
    
      IShape getShape() {
          return new Rectangle(20, 40);
      }
    

And, assuming that `IShape` has a `draw()` method, you can write:

    
    
      IShape shape = getShape();
      shape.draw();
    

It works because there is dynamic dispatch occurring at runtime: the JVM will
look for the implementation of `draw()` in the `Rectangle` class (not sure of
the exact mechanism, but that's the idea), and call it. To find it, the value
(here, shape) must holds a reference either to its class so the JVM can go and
look for the implementation, or to a table of all methods it implements (I
think it's the first). So the compiler code doesn't know the layout of the
concrete type used, it just add instructions to go look for the implementation
at runtime. But that's fine, because any Object in Java is in fact like that:
a fat pointer, containing a pointer to the data, and a pointer to the
implementations.

I won't work in Swift because, as far as I know, there is no dynamic dispatch,
at least by default. When you write the following:

    
    
      func render<T: Shape>(_ shape: T, at point: Point) { … }
    

The compiler will know what is the concrete type of `T`, and so will use its
implementation of the `draw()` method. It won't be found at runtime, it is
known at compile time. What this means is that a protocol is not a type. This
is the important part. An interface in Java is a type, because it doesn't
dictate how the method is called, it allows the concrete type to live under
the hood, and call the right method at runtime. A class in Swift is a type
because you know how to call it, how to access it. But a protocol is just a
set of constraints on a type, not a type by itself.

That's why you need the `some` in return position. Well, you don't really need
the syntax, but it helps understanding the difference with the "same" Java
code. The `some` keyword says that the function will returns _some_ type that
will implements the `Shape` protocol. It will in fact return the concrete
type, but this is not included in the type signature, so it can change, it can
hide implementation details, without making a breaking change in the API. It
also means that the following won't compile (I'm not sure here, but it works
that way in Rust):

    
    
      func union(_ leftShape: some Shape, _ rightShape: some Shape) -> some Shape {
          if isEmptyShape(leftShape) {
              return rightShape
          } else if isEmptyShape(rightShape) {
              return leftShape
          }
          return Union(leftShape, rightShape) 
      }
    

Because here, all code path don't return the same concrete type. If you want
different concrete types, you'll need the other keyword, `any`. `any` is in
fact a lot like the interfaces in Java, because it uses dynamic dispatch under
the hood (if it works like it does in Rust). The compiler will know how to
turn the concrete type into the dynamically dispatched one.

~~~
slavapestov
Generic functions in Swift are compiled separately from their callers.
Protocol requirements called on values of generic parameter type are
dynamically dispatched via a witness table passed in under the hood for each
generic requirement.

------
bsaul
I haven’t followed C# for a long time, but i remember the type system beeing
both obvious and powerful. Could anyone here explain what is swift trying to
accomplish here that just copying the C# way wouldn’t be enough ?

~~~
_old_dude_
I see two use cases, Swift is a precompiled language unlike C# or Java so a
call to a method through a protocol (an interface) can not be inlined. Let
suppose you have an interface IList and an IEnumerator,

    
    
      interface IList<T> {
        IEnumerable<T> iterator();
      }
    

it means that despite the fact that you may know the exact implementation of
the IList, you have no idea of the exact type of the method iterator() at
compile time because it's hidden behind an interface.

Enter the existential types,

    
    
      interface IList<T> {
        type IEnumerator<T> Iterator;  // this is a type declaration
    
        Iterator iterator();
      }
    

now if you have an implementation of IList, you will have

    
    
      class FunnyList<T>: IList<T> {
        type FunnyListEnumerator<T> Iterator;
    
        Iterator iterator();
      }
    

so at compile time, when you use a FunnyList, the compiler knows the exact
type of the method iterator().

The other usual use case, which is a restricted version, is to be able to
specified the variance at use site, like the wildcards in Java (List<? extends
Foo>) which is a way to specify that you don't want a method to be
parameterized but you want a type of a parameter to be covariant

    
    
      void foo<T:Foo>(List<T> list)  // the method is parameterized
    
      void foo(List<some Foo> list)  // the type is parameterized
    

C# doesn't have variance at use site, only at definition site with 'in' and
'out', Kotlin has both. Scala and C++ with type name form.

~~~
pjmlp
C# and Java also have AOT compilers available.

In fact that is their execution model in UWP, Unity (Consoles, iOS, Xamarin
(iOS), Android and some embedded deployments.

------
TazeTSchnitzel
Is this describing something similar to how Haskell's return types work?

~~~
r-w
How so? I know Haskell has pretty extensive type inference, but I’m not sure
exactly how it relates here.

------
iddan
Swift is so awesome! If it was simpler to start a non-UI project and compile
time was shorter I would have definitely joined onboard.

~~~
tokyodude
what makes it awesome? reading the spec nothing sticks out. I often wonder if
it's just ObjC survivor bias but if there is anything that makes it actually
special I'd love to know.

~~~
omeid2
Light syntax, algebraic datatypes, type parameters, very solid enumeration,
the whole ObjC library ecosystem, semi-decent (compared to Haskell/OCaml et.
al) pattern matching, immutable and mutable data structures (Array vs
ArraySlice for example).

~~~
skohan
Don't forget the whole C library ecosystem, which IMO is much more useful.

~~~
saagarjha
This can be somewhat annoying to work with in Swift, though it’s gotten better
recently.

~~~
skohan
It's not perfect, but it's loads better than using something like JNI.

~~~
pjmlp
JNI was made that way on purpose, because Sun wanted to discourage native
code, as Mark Reinhold already mentioned a few times on Java conferences.

Panama will thankfully fix that.

[https://jdk.java.net/panama/](https://jdk.java.net/panama/)

Just don't expect it to ever be supported on Android.

But they surely will,

[https://en.wikipedia.org/wiki/List_of_Java_virtual_machines#...](https://en.wikipedia.org/wiki/List_of_Java_virtual_machines#Proprietary_implementations)

