Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@Egorand
Copy link
Contributor

@Egorand Egorand commented Mar 15, 2018

We're sometimes calling toOptional() from Java and it would be more convenient to have Optional.toOptional() instead of OptionalKt.toOptional().

Copy link
Contributor

@artem-zinnatullin artem-zinnatullin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm putting "Request changes" for now just to block merge until we have consensus on 2.x release, no actual changes in PR required :)

@@ -1,3 +1,5 @@
@file:JvmName("Optional")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I 100% agree with this change, the only problem is that it's a breaking change for Java consumers (class name change) and we'll need to make a 2.x release for that

However I'm not opposed to that

@dmitry-novikov @ming13 @nostra13 are you ok with 2.x release?

If yes — we'll need separate issue and PRs for that (Group name will also need to be updated to com.gojuno.koptional2)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm OK with bumping major version to indicate breaking change, but against package change because is will affect a lot of consumers and most of them are Kotlin based. So I would better force Java consumers to refactor their codebase because of class name change =)

Copy link
Member

@arturdryomov arturdryomov Mar 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@artem-zinnatullin, you are too much in all this package change for no reason thing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let’s make a minor release and that’s it. Koptional is a Kotlin-first library, I see no harm in changing Java compatibility as a minor change. It is OK to change since it is no radical. Otherwise every library should follow Chrome or Firefox versions scheme and have versions in double or triple digits since API change all the time and hey, there are artifact versions exactly for that. Artem just has some kind of fashion taste in bumping package names (even for internal libraries with a single user, yep) :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that Koptional is Kotlin-first, I'd agree that this is a minor change, even though it breaks Java consumers. Changing the package name will require Kotlin consumers to change imports everywhere, and bumping the major version feels excessive for such a small change - compare it with RxJava 2 vs RxJava 1 or Dagger 2 vs Dagger 1. Even though it's breaking, it's trivial to fix.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's trivial to fix for sure (basically find + replace for package name)

However because Koptional was initially designed to be very minimalistic it might be used by other libraries which means that in such a case we're breaking compatibility between user's code and other library.

That's why i suggest both major version bump and groupid update :)

GitHub search results: https://github.com/search?utf8=%E2%9C%93&q=com.gojuno.koptional&type=Code

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Search shows no usage from Java code, so...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which doesn't mean there are no private Java libs that depend on class name…

This change is still breaking even for @Egorand himself because they use Koptional from Java (that's the point of PR), which means that we should at least bump major version https://semver.org/

Do we need to add number to groupId and package name?
I think yes, it's very trivial to migrate to new one in same commit as dependency update and we allow users to migrate their own code and dependencies gradually in case of class-name problem

I might not agree with everything Jake thinks but http://jakewharton.com/java-interoperability-policy-for-major-version-updates/ is very good

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let’s assume we have more Kotlin users than Java users. Changing a package name will force all users to do a Find + Replace. Changing a Java name will do the same for Java users only. I’m in for the version change, but against the package change. I totally understand all your points and, believe me, I would be on your side regarding anything related to these kind of changes. But! This PR does not introduce any behavior changes. If .toNullable was changed, that’s for sure forces the package change. At the same time, the library is so tiny I cannot even believe we are arguing about this 😆

@bensandee
Copy link

I'm just a consumer of the API (private repo, would be affected by the breaking change but not in a way I couldn't recover from in 5 minutes), but it seems that the argument can be distilled into two things:

  • go ahead and break it, not enough people use it and we don't want to have a secondary artifact/package (potentially with version numbers).
  • don't break things, follow semantic versioning, it's the principle of the thing.

Let me ask a rhetorical question -- when driving, do you use a turn signal when you think nobody is looking? IMO, it's best to do the "right thing" even when nobody's looking.

@arturdryomov
Copy link
Member

@tbsandee, I just got some serious 13 Reasons Why vibe.

Still not convinced that renaming can be considered a behavior change though. I’m kind of in, but still consider this case an extreme over pragmatism at its best.

The worst thing I can imagine right now is related to an argument from Artem about Optional being an exchange class between components.

  • We have a library A 1.0.0 and library B 1.0.0 exchanging data using Optional.
  • A moves to 2.0.0 and com.gojuno.koptional2.
  • B updates A to 2.0.0 and is forced to use com.gojuno.koptional2.

Should we provide an interop then? I need a drink.

@vanniktech
Copy link

I sure hope it's a good drink :D

@artem-zinnatullin
Copy link
Contributor

We don't have to provide interop (although we can):

  • Library is very small and interop can be done through going back to nullable types or by having really small conversion extension functions
  • As pointed above, for most of the users conversion can be done in 100% automated "find and replace" manner and we can provide sed command in the changelog that'll do the migration

@artem-zinnatullin
Copy link
Contributor

Soooo?

@artem-zinnatullin
Copy link
Contributor

Can I go ahead and:

  1. Merge this PR
  2. Make a PR with groupId and packageName update
  3. Release v2
  4. Provide single cli command in release notes to update imports

?

Pros:

  • We don't break people
  • We ship new version with better class name for Java consumers
  • We can sleep well knowing that we shipped new version and didn't break people

Cons:

  • People will need to run command (or find + replace in IDE) instead of just version change in build config

I'd like to get this going.

@arturdryomov
Copy link
Member

@artem-zinnatullin, I’m in. Still sounds ridiculous for such a small library, but we made a mistake not taking Java into account and we are gonna pay the price of having 2 in the package name. Let’s do it.

@artem-zinnatullin
Copy link
Contributor

Kool, implementing then 👍

@artem-zinnatullin
Copy link
Contributor

@Egorand there is a compilation error (you can see it on CI):

e: /opt/project/koptional/src/main/kotlin/com/gojuno/koptional/Optional.kt: (1, 1): Duplicate JVM class name 'com/gojuno/koptional/Optional' generated from: package-fragment com.gojuno.koptional, Optional
e: /opt/project/koptional/src/main/kotlin/com/gojuno/koptional/Optional.kt: (5, 1): Duplicate JVM class name 'com/gojuno/koptional/Optional' generated from: package-fragment com.gojuno.koptional, Optional

Copy link

@vanniktech vanniktech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there also be a Java test to verify this change?

@artem-zinnatullin
Copy link
Contributor

That'd be nice!

@artem-zinnatullin
Copy link
Contributor

@Egorand can you please update PR to make it compile and add few Java test cases?

@Egorand
Copy link
Contributor Author

Egorand commented Apr 10, 2018

Sure, will update the PR tomorrow

@Egorand
Copy link
Contributor Author

Egorand commented Apr 10, 2018

I think I've actually discovered a better solution that won't break existing clients and will improve Java experience: adding a companion object with an extra toOptional() method. Kotlin clients should continue using the top-level extension function, Java clients would have to change from OptionalKt.toOptional() to Optional.toOptional() (the latter still works though). Also:

  1. There's no way to fix compilation with @JvmName since we're ending up with two classes called Optional - one for the sealed class Optional and the other for the top-level function - that's a broken approach.
  2. I added a test in Java, I used plain JUnit, let me know if you'd like me to make it in Spec for Java (never used it, is it even a thing?) or JUnit 5 or anything else.
  3. I thought about documenting the two toOptional() version, but I see that there's no documentation at all in the file - should I add it?

@artem-zinnatullin
Copy link
Contributor

  1. Indeed, I don't see a way to solve this atm
  2. Yep, looks good 👍
  3. I have a stupid idea that seems to work (see below)

So, companion obj + @JvmStatic pretty much hides this 2nd toOptional() from Kotlin code, right.

What if we "hide" the first toOptional() from Java? #yolo

If you add @JvmName("_toOptionalKotlin") to the top-level toOptional() function, this will remove it from autocomplete on Java side

@Egorand
Copy link
Contributor Author

Egorand commented Apr 11, 2018

That would be a breaking change, since Java clients who were using OptionalKt.toOptional() will have to use the new method name. I like the idea in general, and it would be good to implement it for the next major release, for now I think it's fine to document correct usage for Kotlin and Java.

@artem-zinnatullin
Copy link
Contributor

Yeah, we already agreed on 2.x release though.

Would be great to make initial approach with single toOptional function declared properly for both Kotlin and Java work…

Technically it seems okay to have toOptional declared as a static function in Optional class, maybe we should submit a ticket to https://youtrack.jetbrains.com and describe our use case

Another solution is to write Koptional core in Java with proper Kotlin annotations and properly placed functions and see if Kotlin compiler works with that, I mean, that sounds like a fun experiment (not that I have time for it tho…)

@Egorand
Copy link
Contributor Author

Egorand commented Apr 11, 2018

The problem is that extension functions declared inside classes or objects can only be used as extensions inside those classes/objects, hence you'll need to always do Optional.toOptional(), even in Kotlin. Rewriting the core in Java destroys the potential of making the library multi-platform. Opening a discussion on YouTrack sounds like a great idea, I'd love to get more insight on the issue.

@arturdryomov
Copy link
Member

Let’s go ahead without hacks and do a 2.x as it was proposed in the first place. We screwed up with Java compatibility and it is OK to acknowledge it.

@arturdryomov
Copy link
Member

So, actually, what do we agree on?

@Egorand
Copy link
Contributor Author

Egorand commented Apr 20, 2018

As I mentioned, the change is not breaking, so it'd be fine as a minor update

@dmitry-novikov
Copy link

@ming13, @artem-zinnatullin, are you OK to merge it as minor update?

@artem-zinnatullin
Copy link
Contributor

ok, I double checked current version of PR, I think I'm good to merge this as non breaking change 👍

Kotlin:

  • Nothing changes, you still work with toOptional() extension most of the time
  • Now you can also use Optional.toOptional() which is probably not that bad tbh (I didn't like that back then, but now I find it fine)

Java:

  • We're still backwards compatible, extension is still there and you can keep calling OptionalKt.toOptional() (I didn't like it back then, but now I find it ok, library was not intended to be used from Java so it's fine)
  • Now you can use Optional.toOptional() which is very good

@Egorand just couple things:

  • Can you please add a Java test to verify that we keep compatibility with OptionalKt.toOptional()? I mean it works, but a test would be great to make sure we don't break it in future
  • Can you please add javakotlindoc to both functions and explain that OptionalKt one should not be used from Java and @JvmStatic one is preferred?
  • You can add Java interop section to the README, or I can do it later, up2u
  • Maybe we should duplicate the body of original toOptional() extension to avoid method call, some people are already upset about having to wrap their data with Optional, additional indirection might make their feelings worse, I don't think that's critical though, it should be easily inlined by JIT or AOT
  • I'm personally really sorry it took us so long to figure out, wish things were easier :(

@Egorand
Copy link
Contributor Author

Egorand commented May 21, 2018

Yeah no worries! I'll try to get the changes in today

@Egorand
Copy link
Contributor Author

Egorand commented May 21, 2018

@artem-zinnatullin 0dafabb

Copy link
Contributor

@artem-zinnatullin artem-zinnatullin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, few comments

companion object {

/**
* Wrap an instance of T (or null) into an [Optional]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

supernit: Wrap[s]

/**
* Wrap an instance of T (or null) into an [Optional]:
*
* ```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we specify language here?

Like ```java


fun <T : Any> T?.toOptional(): Optional<T> = if (this == null) None else Some(this)
/**
* Wrap an instance of T (or null) into an [Optional]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

* using the static [Optional.toOptional] method.
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <T : Any> T?.toOptional(): Optional<T> = if (this == null) None else Some(this)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah sorry for not being clear, that's not what I meant :)

Inlining this extension everywhere can inflate user's class files noticeably, we intentionally didn't make this one inline

What I was talking about is that we can copy-paste this body to the @JvmStatic one so we don't have to do two method calls just because we're calling it from Java

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got your point initially, just thought inlining would be a better option here, as opposed to duplication. Since it's a one-liner I don't see it noticeably affecting the bytecode size, but I can change it to just use the same code as the other version.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, well it's one-liner in Kotlin 😸 , bytecode is not that small

L0
    LINENUMBER 21 L0
    ALOAD 0
    IFNONNULL L1
    GETSTATIC com/gojuno/koptional/None.INSTANCE : Lcom/gojuno/koptional/None;
    CHECKCAST com/gojuno/koptional/Optional
    GOTO L2
L1
    NEW com/gojuno/koptional/Some
    DUP
    ALOAD 0
    INVOKESPECIAL com/gojuno/koptional/Some.<init> (Ljava/lang/Object;)V
    CHECKCAST com/gojuno/koptional/Optional
L2
    ARETURN
L3
    LOCALVARIABLE $receiver Ljava/lang/Object; L0 L3 0
    MAXSTACK = 3
    MAXLOCALS = 1

Wonder why it does CHECKCAST com/gojuno/koptional/Optional, seems useless, maybe we need to update compiler

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah well, hidden costs of Kotlin!

*/
@JvmStatic
fun <T : Any> toOptional(t: T?): Optional<T> = t.toOptional()
fun <T : Any> toOptional(t: T?): Optional<T> = if (t == null) None else Some(t)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heh, parameter name t is part of public API in Kotlin, if you can come up with something more meaningful but short that'd be nice

I have obj on mind, but idk

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! What about value?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much better, let's use that!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@artem-zinnatullin artem-zinnatullin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noice! Let's wait for review from the others now :)

@Egorand Egorand changed the title Add JvmName to Optional Java interop version of toOptional() May 22, 2018
@artem-zinnatullin
Copy link
Contributor

Sorry for delay, had miscommunication with Artur and was waiting for his review while he was waiting for me to merge heh

@artem-zinnatullin artem-zinnatullin merged commit 6ce979d into gojuno:master May 31, 2018
@Egorand Egorand deleted the egorand/jvm-name branch May 31, 2018 02:50
@Egorand
Copy link
Contributor Author

Egorand commented May 31, 2018

Sweet! Are you planning a release any time soon?

@artem-zinnatullin
Copy link
Contributor

Yep, we can do a release with this PR only or wait for rest

@artem-zinnatullin
Copy link
Contributor

@Egorand 1.4.0 was just released with this change, guys at Juno have some workload backpressure, I'm gonna take care of processes for some time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants