Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Shining a Light into Kotlin's Dark Corners (Kot...

Shining a Light into Kotlin's Dark Corners (Kotlin London, May 2016)

Our experience going from Java to Kotlin

Nat Pryce

May 12, 2016
Tweet

More Decks by Nat Pryce

Other Decks in Programming

Transcript

  1. We use null much more than in Java data class

    AuthorDetails( val name: String, val email: String?, val averageImpactFactor: Double ) data class Pagination(val count: Int, val offset: Int = 0) { fun next() = copy(offset = offset + count) } interface AuthorSearch { fun search(impactFactor: Double?, keywords: List<String> = emptyList(), title: String? = null, coauthors: List<String> = emptyList(), abstract: String? = null, pagination: Pagination? = null): List<AuthorDetails> }
  2. “Nullable” is a special case val maybeName: String? = "alice";

    val nameList = listOf("bob", "carol", "dave") Mapping maybeName?.let { it.toUpperCase() } nameList.map { it.toUpperCase() } Filtering maybeName?.let { if ("a" in it) it else null } nameList.filter { "a" in it }
  3. Kotlin has limited polymorphism fun <T : AutoCloseable, U> T.use(block:

    (T) -> U) = try { block(this); } finally { close() } fun <T : Image, U> T.use(block: (T) -> U) = try { block(this); } finally { flush() } fun <U> SplashScreen.use(block: (SplashScreen)->U) = try { block(this); } finally { close() } fun <U> MembershipKey.use(block: (MembershipKey)->U) = try { block(this); } finally { drop() }
  4. No destructuring pattern match… but flow-sensitive typing makes up for

    it sealed class Shape { class Rect(val p1: Point, val p2: Point) : Shape() class Circle(val centre: Point, val radius: Double) : Shape() } Shape s = getTheShape() val area = when(s) { is Shape.Rect -> abs(s.p2.x - s.p1.x) * abs(s.p2.y - s.p1.y) is Shape.Circle -> PI * s.radius * s.radius } fun Shape.area(): Double = when (this) { is Shape.Rect -> abs(p2.x - p1.x) * abs(p2.y - p1.y) is Shape.Circle -> PI * radius * radius }
  5. Subtyping, Nullability & Generics assertThat(optionalText, isNotNullAnd(equalTo("hello"))) fun <T> isNotNullAnd(valueMatcher: Matcher<T>):

    Matcher<T?> = object:Matcher<T?> { override fun invoke(actual: T?): MatchResult { return when (actual) { null -> MatchResult.Mismatch("was null") else -> valueMatcher(actual) } } override val description: String get() { return "is not null & " + valueMatcher.description } }
  6. Touching the Void @Test public void callable_returning_nothing_is_a_bit_of_a_pain() throws Exception {

    Callable<Void> callable = new Callable<Void>() { @Override public Void call() throws Exception { System.out.println("Doing a thing"); return null; } }; assertNull(callable.call()); } @Test public void even_with_lambdas() throws Exception { Callable<Void> callable = () -> { System.out.println("Doing a thing"); return null; }; assertNull(callable.call()); }
  7. Touching the Void fun `implicitly returns Unit`() : Unit {}

    fun `explicitly returns Unit`() : Unit { return Unit }
  8. Touching the Void @Test fun `Callable of Unit is OK`()

    { val callable = object: Callable<Unit> { override fun call() { println("Doing a thing") } } assertEquals(Unit, callable.call()) } @Test fun `and even better as a lambda`() { val callable = Callable<Unit> { println("Doing a thing") } assertEquals(Unit, callable.call()) }
  9. Touching the Void @Test fun `can extend Unit to give

    fluent API`() { returnAThing().shouldEqual(3) doAThing().shouldEqual(Unit) null.shouldEqual(null) } fun returnAThing() = 3 fun doAThing() { println("Doing a thing") } fun <T:Any?> T.shouldEqual(x: T) = assertEquals(x, this)
  10. Language support for the Singleton pattern object alice { val

    name = "Alice" } val bob = object { val name = "Bob" } fun example() { val carol = object { val name = "Carol" } assert( alice.name == "Alice" ) assert( bob.name == "Bob" ) assert( carol.name == "Carol" ) } Error: Unresolved reference: name
  11. Spaces in Identifiers private val wordBoundaryRegex = Regex("(\\p{Ll})(\\p{Lu})") private fun

    String.`space Out Camel Case`() = this.replace(wordBoundaryRegex, "$1 $2")
  12. Spaces in Identifiers class SmokeTests : AcceptanceTest( asA = "user

    named James", want = "to know things are running") { val james = actorNamed("James") @Test fun `The server is running`() { given (james) { loadsTheHomePage() } then (james) { shouldSee(::`the page title`, equalTo("Journal")) } } @Test fun `Search returns some results`() { given (james) { loadsTheHomePage() } then (james) { shouldSee(::`the results table`, isntThere) } wheN (james) { searchesForTitle("fred") } then (james) { shouldSee(::`the results table`, hasSomeContent) } } }
  13. Infixation val config = ConfigurationProperties.systemProperties() overriding EnvironmentVariables() overriding ConfigurationProperties.fromResource("default.conf") infix

    fun Configuration.overriding(defaults: Configuration) = Override(this, defaults) (n) An unhealthy obsession with infix functions.
  14. Wish List Checked exceptions • sadly will never happen Sealed

    data classes • scheduled for v1.1 Persistent data structures • planned for the stdlib Maybe one day • Type classes • Higher-kinded types