As Swift5 is now released with stable ABI, I guess...
# kotlin-native
u
As Swift5 is now released with stable ABI, I guess it's time to ask for the plans regarding direct Swift interop again 🙂 I do understand that the unstable ABI was enough of a reason not to go for Swift introp yet. But I also understand that it was not the only reason. Any updates on that topic for the kotlin fans waiting?
➕ 2
s
Why are you waiting for direct Swift interop? What benefits do you expect to get?
k
There have been several recent posts on this. If I get some time I’ll try to dig them up. Short version is, it’s not just abi. They’re different languages. Structs, enums w associated data, etc. It’s not “no”, but doesn’t seem on near term.
➕ 2
a
For me interop with swift should add generics support, if it possible
👍 1
k
I’m working through adding more generics support to objc output, which I think can be useful, but it’s not entirely natural to either side.
For example, if you want generic param to be non-null, you need to add a constraint to the kotlin side.
class Arst<T:Any>()
a
Can I see some info about your solution?)
k
Not end of the world, but it takes nullability out of the type def and puts on class def
Should have more today/tomorrow-ish? I have an earlier version pushed, but no docs
Something is wrong with my internet now, so just getting links is taking forever…
a
When you can then send, I can wait)
k
It’ll be forked from the version that should ship with 1.3.30
a
👍
k
Had more replies but home internet died. Short version, complications are nullability, variance, and special types (primitives, collections, etc). Working on tests and edge cases now. Updates tomorrowish, but work will be ongoing for a bit. However, want feedback and yes/no thoughts
s
Btw, generics is one of the noticeable differences between Kotlin and Swift. For example, the latter doesn’t have use-site and declaration-site variance (`in`/`out`) or star projections (
*
). So it doesn’t seem to be possible to provide perfect interop support for generics. @alex009 how do you use generics? Do you use generic interfaces, `in`/`out`/`*` in your public API to be consumed by Swift source?
k
That’s where we’re at now. Figuring out if this partial support makes sense. I think it does, if you’re aware of the limitations and code with them in mind. I don’t think either side of the kotlin/swift interop on a dev team should code without consideration for what the other side will need to deal with.
For example, if you know a type won’t be used with nulls, or you have some flexibility on how it’ll be used, you can put a non-null constraint on the generic param. Arguably, if you know the generic can’t be null you should have one anyway, but still.
As it stands, generics can only be added to classes, without variance info, but assuming that doesn’t cause more issues with swift, the extra type info seems useful.
Now, as to whether my PR would make it through review, even if the team agreed it was a good idea, we’ll see, but I wanted to publish something functional so we can evaluate if this is practical. In general developers seem interested, but maybe less so once we see compromises necessary? We’ll see.
r
So it doesn’t seem to be possible to provide perfect interop support for generics.
Kotlin and Java treat generics differently as well, but they still interop fine and there's annotations you can use if you need fine-grained control. Something similar for Swift would be huge. Lack of generics support is a huge pain-point for convincing iOS developers to use Kotlin right now.
👆 3
s
Kotlin and Java treat generics differently as well, but they still interop fine
The difference is that in Java you can represent Kotlin concepts like generics variance by using wildcards. How do you suggest to import
Foo<out Bar>
to Swift?
k
Foo<Bar>
You lose the variance. The big question is, can you cast around that when you absolutely have to, and the answer seems to be yes. Alternatively, is it better to have no generic info in that case? Those I think are the two big questions here.
Also, although not “clean”, there could be related annotations. This exists now (@JvmName, etc)
As a concrete example,
abstract class Query<out RowType : Any>
from sqldelight. The variance exists to define more how this class is used, but in almost all cases, it doesn’t need to be. The class is returning the type defined in the generic. However, from a readability standpoint in swift, this is pretty useful.
fun executeAsList(): List<RowType>
,
fun executeAsOne(): RowType
, etc.
Recently I’ve been running into the unpleasant edge cases. If, for example, you have a nullable generic, but return a list, the type defined in the list generic is
id
, or to Swift
Any?
. However, having some info, and being aware of what does/doesn’t interop well is useful. My opinion, though. Feedback would be good.
r
I think my opinion at this point is that anything is better than nothing. But I certainly haven't though much about edge-cases. I think we can accept that there will be some limitations, but the Kotlin team tends to be pretty good at finding a pragmatic balance between these sorts of language constraints.
k
Well, JB/Kotlin team (@svyatoslav.scherbina), if you decide this is something you’re going to work on, please let me know and I’ll stop 🙂 For now I’m going to see where it goes. Demo video from last week of this working on the swift side. Obviously not dealing with difficult cases, but having the type info in swift is pretty nice:

https://www.youtube.com/watch?v=gxrdARbkXwA&amp;feature=youtu.be▾

👍 1
s
If, for example, you have a nullable generic, but return a list, the type defined in the list generic is
id
, or to Swift
Any?
.
Unfortunately, this is required since Objective-C collections can’t contain
null
, and Kotlin follows here the convention to use
NSNull
instead. And
id
is used as a common supertype.
if you decide this is something you’re going to work on, please let me know and I’ll stop 🙂 For now I’m going to see where it goes.
I consider working on this somewhat later. I would really appreciate opinions from the community on how should we handle edge cases to cover common patterns and existing code.
👍 4
k
Yeah, the NSArray/null thing is just something that people will need to be aware of. Not something to “fix”.
For common patterns and existing code, I think we need to have them listed somewhere to get feedback. I’m sure I’m oversimplifying the issue, but I would imagine most people are as well because we’re not forced to think about these issues. Slack isn’t really a great place to do that comprehensively. Github issue?
s
how should we handle edge cases
Particularly, as I’ve already mentioned above: • How to represent
class Foo<in/out T>
? Should we erase its type parameter or make it invariant? • How to represent type
Foo<in/out T>
? Should we erase the type to Objective-C
Foo *
? • How to represent type
Foo<*>
?
Foo<upperBound>*
or
Foo*
?
Do you mean sharing the list of edge cases or common patterns?
k
We were posting simultaneously, but your post is a good start. I think the list of open issues should be in one place to get feedback, and it should be somewhat editable as I’m sure more questions/issues will pop up. Slack threads are OK, but age quickly and disappear. Maybe github issue or forum discussion?
For my part, I’ll just keep plugging away and introduce some of these to play with options. Personally, as long as you can cast your way out of trouble, providing more type info for general cases is valuable, but there may be a need to override default behavior with annotations in places.
s
I don’t find sharing list of issues useful. I suppose we should get feedback about some generics support prototype.
b
I think we also have to wait on stable module ABI anyway, which I think is Swift 5.1
k
From a political standpoint, the lack of generics and forced casting on the swift side is very much an issue when “selling” to the iOS side of a team.
☝️ 11
b
Improved generics support would be great, even if it’s not perfect
k
Even if ultimately abandoned, being able to point to the failed attempt is better.
“I don’t find sharing list of issues useful. I suppose we should get feedback about some generics support prototype.” Yeah, fair. Back to work I guess 🙂
s
Btw, there is at least one reason to erase type parameters `class Foo<in/out T>`: I don’t expect that Swift would like casts like
Foo<Bar>() as! Foo<Any>
. This may work ok while we use Objective-C classes but definitely wouldn’t if interop with Swift was direct.
k
I will have an update in the next couple days that adds some tests to the framework values tests, and attempts to deal with some of these issues explicitly. While I have attention of an audience, does Objc generics support multiple constraints, and what is the syntax? For some reason that particular answer has been difficult to dig up.
“Foo<Bar>() as! Foo<Any>” are you sure that wouldn’t work? That has been my overriding question on variance. Can you cast your way out of problems, and I think you can, however ugly it may be
I, of course, could be super wrong there.
I’ve only been looking at Objc interop
s
While I have attention of an audience, does Objc generics support multiple constraints, and what is the syntax?
Does this make a trick?
Copy code
@protocol Foo
@end;

@protocol Bar
@end;

@interface Baz : NSObject
@end;

@interface Test<T : Baz <Foo, Bar> * >
@end;
“Foo<Bar>() as! Foo<Any>” are you sure that wouldn’t work?
I mean that this doesn’t work in pure Swift:
Copy code
class Foo {}
class Test<T> {}

Test<Foo>() as! Test<Any>
So I suppose this will likely be a problem with direct Swift interop.
k
Yeah, was just playing with that.
s
For the same reason we can’t represent type
Test<out Foo>
as
Test<Foo>
😞 This would work for now, but I’m not sure it is ok with direct Swift interop. In fact, direct Swift interop appears to be somewhat less suitable for generics support ¯\_(ツ)_/¯
k
Interesting. So, this tends to work with objc interop but won’t with swift.
That’ll be frustrating. In the case of generics at least, there’s a case for not interoping directly with swift. That is a surprise. Direct swift interop will get more complex still when considering structs
➕ 2
g
What about support of Swift enums?
k
Hmm. Well, anyway, continuing with objc generics for now. Will think on the direct swift interop some too
e
One other big thing to consider alongside `in`/`out` variance is that Swift generics aren’t type-erased, but ones in Kotlin are. That causes some limitations on what you can do in ways that are not going to be immediately obvious to a lot of iOS/Swift devs. Pretty much the only reason I understand this stuff is because I wrote a whole chapter about it so I had to learn it to explain it to other people, and I’m pretty sure that doesn’t scale 🙃
🔍 2
k
It’s getting complicated.
i
Just my 5 cents. The issue with lack of any kind support of generics (on the same page as @kpgalligan) it is super hard to sell the KN to be adopted at the company. In our case of using KN for any kind multi platform SDKs is rejected due to non iOS dev friendly API on Swift side. Force casts everywhere makes Swift guys be super opposed to any KN initiatives.
👆 1
So
Swift's generic is invariance
why we can’t as temp solution just drop variance
in/out
? When exporting objc headers
k
Thats what i'm doing
i
Is it considered by JB as temporary option to add at least some kind support of generics, should we expect it any time soon?
s
This solution would kinda work with current interop through Objective-C layer, but I don’t expect it working properly with direct Swift interop later (if any). So I can see two options here: 1) Approximate variance properly. For example, type
Foo<out Bar>
is (formally speaking) a union of all
Foo<T>
where
T : Bar
. Kotlin has exact type for it, Swift doesn’t, so we have to use some approximation, e.g. common supertype. If
class Foo<T> : Base
, then
Base
is most common supertype for all these types. Declaration-site variant generics (e.g.
class Holder<out T>
) have to be erased completely (so
Holder
wouldn’t have generic parameters in Swift). 2) Drop the variance now (so type
Foo<out Bar>
becomes
Foo<Bar>
,
class Holder<out T>
becomes
class Holder<T>
), but maybe apply 1) later, when implementing direct interop with Swift, thus breaking existing code and making it ugly,
2️⃣ 1
k
I don't think option 2 is very ugly in practice. Even if direct swift interop emerges, I assume Objective-C as an interface option would remain, for projects who talk to kotlin with objective c. Changing to direct swift interop will be an intentional choice, and generic changes would be part of that consideration. Will think about option 1 some more, but just my thoughts about 2
👆 2
🙏 1
➕ 2
Taking a little longer than planned, but mostly due to other obligations. Still working on this, though: https://github.com/kpgalligan/kotlin-native/tree/kpgalligan/20190315/generics
👍 3
Need to add type constraints to generics, then do some cleanup passes. Being able to force cast because it’s coming from Objc makes this simpler, but you do have to be careful in cases where the variance casting would matter. You can make casts that don’t make sense (and will likely crash). However, the common case of simply having the generic type info seems to work OK and would I think be useful.
a
k
Looking at that sample, some notes and some output code.
In the LiveData class, map and flatMap will lose the generic param on the function because Objc generics can’t be added to functions
The add/remove observer generics are nullable on the functions, which seems odd, but in any case, is dropping the types when getting generated. I’m not sure why, but will look at it. When I make those non-nullable, it looks better. Swift calls aren’t ideal, but are more readable
let ld = LiveData<State<NSString, KotlinThrowable>>() ld.addObserver(observer: { (state: State<NSString, KotlinThrowable>) -> KotlinUnit in print(“\(state)“) return KotlinUnit() })
The ever present “KotlinUnit” for lambdas is unfortunate, but it is what it is
Going to play around with this more, but the generated objc code: https://gist.github.com/kpgalligan/a1906590d595ebe606225305a977d43d
Some things I need to look at. In general, explicitly nullable generic values on already nullable types is just coming out with ‘id’ rather than a nullable function on the generic type.
- (id _Nullable)dataValue __attribute__((swift_name(“dataValue()“))); - (id _Nullable)errorValue __attribute__((swift_name(“errorValue()“)));
Anyway, perhaps verbose, but the generics work
let sdata = State<NSString, KotlinThrowable>.Data<NSString, KotlinThrowable>(data: “Hello”) print(“from data: \(sdata.data)“)
a
map & flatMap is used in kotlin only, so lose generic param is not bad..for my team 🙂 on swift side we only create observer to livedata & bind data to UI, without instantiate new objects from classes of kotlin. so main purpose of generics support for us - compiler time typechecks. https://kotlinlang.slack.com/archives/C3SGXARS6/p1554051070039000?thread_ts=1553601869.442600&amp;cid=C3SGXARS6 looks nice, it was generated by this version of compiler? https://github.com/kpgalligan/kotlin-native/tree/kpgalligan/20190315/generics
k
That version is a touch behind, but shouldn’t matter much for the discussion. I’ve added some variance output, but Swift ignores it, and am playing around with <*> generics, which come out as
id
in Objc, but it looks like casting rules for Swift->Objc interop are pretty loose.
Anyway, yeah, that build is outputting that, but I’m looking to publish something more complete this week
👍 2
s
@alex009 Is using
out
-generics necessary for
State.*
classes?
a
@svyatoslav.scherbina at kotlin side - necessary, but on swift side not. on swift side will be good concrete type generic like
State<User, Throwable>
s
on swift side will be good concrete type generic like
State<User, Throwable>
And this is still somewhat incorrect, as already discussed in this thread.
s
This is not just Swift thing. Rather general fundamental type system one.
a
just for us usage we get from kotlinside some
liveData: LiveData<State<User, String>>
and want to use it like
liveData.observe { titleLable.text = $0.data?.firstName }
liveData.observe { errorLabel.text = $0.error }
k
Updated the code mostly. This is what the translated objc now looks like from that sample: https://gist.github.com/kpgalligan/bf15a0c4df7860eba17902a000a30f90
I think this is a good sample of generics usage, but it’s a wider discussion. From my perspective, going back to type issues, calling into the Kotlin api from Swift/objc is mostly for consuming the api. So far the cases I’ve see where variance is defined on the type, it’s similar to here. Necessary inside Kotlin but not on the Swift/Objc side, because they’re just calling the library and/or iterating through results.
I’ve been working to get generics support added as an experimental flag, so we have some hope of getting into into something people can play with that doesn’t require building the whole kotlin native platform locally. The final stage of that, getting all the tests to run properly, is giving me trouble, so perhaps that needs a better way to set the flag (using java property which can be set from gradle.properties rather than changing config schema).
👍 2
Still working up the doc about decisions, but essentially variance on classes is output in Objc (and ignored by swift). Use site variance is ignored, and where SomeClass<*> appears, it’s SomeClass<id> in Objc. That call all be cast around in bad cases, but the hope is that the 90% case remove the cast and makes calling the Kotlin api from Objc/Swift more readable.
The big surprise was upper bounds. This does not translate will to Objc:
public abstract class Enum<E : Enum<E>>
. I’ve simply disabled upper bounds for now, except as it relates to nullability.
r
Is that specific to translating Kotlin and Swift enums or is it a problem for any recursively-defined generic?
k
Specific to objc not liking it. Dont know about swift. That is upper bounds only, so assuming you were doing something legal to kotlin it would still work
Simple upper bound definitions work. If we wanted partial support, that could be discussed, but i've been poking around this branch for a while, and the point is to kick off discussion, so i figured it was better to stop there and start discussing.
So, if you're creating an instance with a generic type param, you'll need to know it's defined upper bound. Xcode wont complain. On one hand, that would be true without generics. On the other, maybe defining generics without the upper bound is more confusing.
y
amazing
s
Thank you for the PR and the article! I’d like to add a few remarks to the article too. How can I comment it?
🎉 2
k
Article is on a new blog host. Not sure you can comment on it directly. I’m exploring options to move off of medium, but wasn’t thinking about commenting. Will take a look.
s
I’ve added a couple of comments as private notes on medium.