Public review of the Standard Library APIs [Closed]

NOTE: This call is closed. Thanks for your feedback!

Hello everyone,

Kotlin 1.0 is approaching, and we are preparing to freeze the API of the standard library. Here’s the plan for how the standard library will be evolved later:​

  • The API released as 1.0 will stay a long time. We don’t plan to remove anything. If new, better APIs are introduced, the old ones will be kept around and marked as @Deprecated with some severity (WARNING, ERROR, HIDDEN). In any case, binary compatibility will be preserved.

  • We will do our best to avoid adding public methods and fields to existing classes, at least until the next major version (e.g. Kotlin 2.0). The only thing we can do is add new classes and new extension functions (which themselves will be located in new .class files).

So, we are very limited in what we can change/fix after 1.0, and this is why we would like to involve the community into the process of final API review of the standard library.

Update: please note that reflection APIs are not part of the standard library and will likely be changed in the future.

It would be great if you told us your motivated opinion about what parts of the standard library​

  • should not be there
  • has questionable API/contracts/behavior
  • could be named better
  • and what is missing from the APIs of public classes:
    • Lazy
    • Charsets, Typography, CharCategory, CharDirectionality
    • Regex, MatchGroupCollection, MatchGroup, MatchResult
    • FileTreeWalk
    • Sequence
    • AbstractIterator
    • ClosedRange, (Int/Long/Char)Range, (Int/Long/Char)Progression
    • Delegates, ObservableProperty

The sources are located here. The generated docs are here.

Please share your feedback in the comments below.

NOTE: your feedback is valuable to us as input for discussions, but we can’t guarantee that all requests will be satisfied )

Thanks!

3 Likes

I think the “no new methods or properties” restriction is too severe and will cause you to want to do backwards-incompatible changes, which I’d hope Kotlin will avoid for a very long time (it pretty much has to, to be a real competitor to C# and Java).

The goal of avoiding dependency graph conflicts is laudable, but as long as the standard library is itself fully backwards compatible there is no problem with someone just pinning the std lib to the newest version, and hopefully Jigsaw will eventually provide a more automated solution to such issues anyway.

The only time package version conflicts get really painful is when there are two incompatible versions on the same classpath and you can’t upgrade one without breaking the user of the other. That’s a situation that definitely needs to be avoided.

1 Like

I see your point. Backwards compatibility is a total requirement, of course, but we’ll try to minimize other kinds of issues as well. No guarantee of success there, but we’ll try.

Another thought: some stuff should be moved into kotlin.text seeing as classes are on the move:

  • Typography
  • CharCategory
  • CharDirectionality
  • Charsets

Possibly also the extension properties and functions for Char* stuff and String extensions.

I think kotlin has to remove imports with stars (import foo.*), it causes problems with compatibility, checkstyle plugin even blocks such imports by default.

Asymmetry: Extension methods File.reader and File.writer have buffered variants (File.bufferedReader and File.bufferedWriter), whereas File.inputStream and File.outputStream don’t. (file.inputStream().buffered() isn’t bad, but neither is file.reader().buffered().)

Assertion APIs that fail fast (dominant at least on JVM), and assertion APIs that collect errors, should be separated. E.g., when using fail-fast it doesn’t feel right, and causes pain, that kotlin.test.assertFails returns Throwable? rather than Throwable, and that kotlin.test.fail returns Unit rather than Nothing. We quickly ended up writing our own replacements, but it’s proving difficult not to accidentally use the original methods.

By the way, you might want to think about scrapping the idea of ever doing a Kotlin 2.0 - rather, if you build up a big enough collection of changes that can’t be done with an acceptable compatibility cost, invent a new brand name and new website. Then implement a Kotlin-to-SparklyLang converter as you did with Java-to-Kotlin to give people a migration path … but leave Kotlin still in existence so the open source community (or you) can still do new releases with bug fixes or further backwards-compatible improvements.

The reasons being:

  1. Kotlin can be a great competitor to Java or C# (commercially supported, not too radical, good IDE support etc). But not if people are immediately told that they’re writing code that will eventually stop compiling (but not told when).
  2. Experience from Python shows that even apparently “easy” backwards compat breaks can fracture the community forever and cause a de-facto fork anyway, so at that point having a separate brand name just simplifies things anyway.

It will also encourage you to evaluate the costs of doing that better. If you have to pick a new brand name, it will mean you have to advertise the project and make the case for why Kotlin users should switch to the new language. That in turn means you have to be sure yourselves … keeping the same brand name means the costs might seem lower because “oh it’s just another upgrade to what we already have”. A little bit of friction is no bad thing, here.

In my opinion there too much functions in the kotlin package. I would prefer some grouping (e. g. collections related functions, string related functions and so on).

there is a “Java Puzzlers” behaviour in case of
file.deleteRecursively()

it returns:

  1. true if can delete smth
  2. false if can’t delete smth
  3. and the puzzler → false when there is no such folder structure

so if one needs to check if deletion didn’t happen,
because of some blocking, or it was partial, or was already deleted:
the code would be:
!file.exists() || !file.deleteRecursively()

Thanks everyone for feedback provided so far. Do not stop. While we’re analyzing it, I’ll post here some things we have already spotted during our review and plan to fix before release.

###Operations on maps and null values

We have several extensions for Map and MutableMap in the standard library, namely getOrElse, getOrPut, getOrImplicitDefault. They all try to get a value mapped to the specified key, or execute Or-branch in case if the key is missing from the Map.

This behavior differs from the one of similar functions like putIfAbsent or computeIfAbsent in java.util.concurrent.ConcurrentMap or java.util.Map from JDK8. These functions treat a key mapped to a null value the same way as a key missing in the map.

Provided these JDK8 operations would be eventually available for kotlin.Map it would not only lead to inconsistency in null handling, but also make us unable to use these function to implement our own extensions. One notable example is getOrPut for ConcurrentMap which we were unable neither to implement according to its contract in a concurrent-safe manner, nor to provide an overload of getOrPut for a subset of ConcurrentMaps with non-nullable values. That time we had to introduce an instantly deprecated overload for ConcurrentMaps and provide an extension with new name concurrentGetOrPut.

As a result we’re tending now to change the contract of our operations to treat nulls mapped to keys the same way as if those keys were missing. For example getOrElse would call defaultValue function when it encounter null value, and getOrPut would put new value over the null value.
Note that the behavior would change only for the maps that could contain nullable values.

Note: I updated the post to note that reflection APIs are not part of the standard library, and we’ll probably change something there.

We keep having a hard time with T.run vs. with vs. let. These three “global” functions sound very different, but actually only differ in “cosmetics”. Almost always any of them can be used, and it’s unclear which to prefer. This regularly leads to debates in code reviews. Furthermore, it’s not intuitive why it’s with(foo) vs. foo.run. (In fact, this seems to be the only difference between the two!)

On the other hand, we sometimes miss the ability to access the receiver of apply as a function argument. For example, val foo = Foo().apply { configure(it) } feels more natural than val foo = Foo().apply { configure(this) }. We use apply more often than any of the other methods.

I hope there is a way to unify these methods, or make their similarity and distinctions more apparent, that removes a lot of this guesswork. I made one proposal in Let vs. with vs. run - #2 by ilya.gorbunov. Another idea that comes to mind is to remove one of T.run and with, although this still leaves with vs. let.

If no other solution is found, damage could be reduced by having KDoc explain the rationale and intended use cases for each method, rather than just paraphrasing the code.

3 Likes

@abreslav Would that be possible to update the docs to beta 4 on the weekend? The API is changing rapidly, and the docs are coming outdated. Maybe the solution would be to regenerate them every night as a part of CI?

+1. “with” should die

apply - is sort of “object initializer” from C#.
C#:

    var o = new MyObject {
      Prop1 = "Value1",
      Prop2 = "Value2"
    }

kotlin:

    var o = MyObject().apply {
       prop1 = "Value1"
       prop2 = "Value1"
    }

I understand (and wonder if configure would have been a better name). Just saying that if some of the initialization is factored out into a separate method, separateMethod(this) looks a bit odd.

Extension method File.inputStream should have return type FileInputStream rather than InputStream.

Extension method File.outputStream should have return type FileOutputStream rather than OutputStream.

1 Like

I would like the extension methods defined for collections and iterables to be made type safe by returning nullable types rather than throwing exceptions. For example, first() throws if there is no first element; it should instead return T?.

We currently have only one place for the docs of the latest public version (which is Beta 3). I see the problem with regard to this review, but it will take some time to set up a place for nightly regenerated docs. We’ll think about it. Thanks

1 Like

Could you please add function for calculating max and min values to Math class using varargs? It will be so suitable!

2 Likes