Archive

Posts Tagged ‘API’
  • Kotlin collections

    :page-liquid: :experimental: :imagesdir: /assets/resources/kotlin-collections


    This post should have been the 5th in the link:{% post_url 2016-07-10-scala-vs-kotlin-pimp-my-library %}[Scala^] link:{% post_url 2016-07-24-scala-vs-kotlin-operator-overloading %}[vs^] link:{% post_url 2016-08-21-scala-vs-kotlin-inline-infix %}[Kotlin^] link:{% post_url 2017-07-09-scala-vs-kotlin-multiple-inheritance %}[serie^].

    Unfortunately, I must admit I have a hard time reading the documentation of Scala collections e.g.:

    trait LinearSeq [+A] extends Seq[A] with collection.LinearSeq[A] with GenericTraversableTemplate[A, LinearSeq] with LinearSeqLike[A, LinearSeq[A]]

    Hence, I will only describe collections from the Kotlin side. **

    == Iterator

    At the root of Kotlin’s Collection API lies the Iterator interface, similar to Java’s. But the similitude stops after that. java.util.ListIterator features are broken down into different contracts:

    1. ListIterator to move the iterator index forward and backward
    2. MutableIterator to remove content from the iterator
    3. MutableListIterator inherits from the 2 interfaces above to mimic the entire contract of java.util.ListIterator

    image::iterator.svg[Iterator API,align=”center”]

    == Collection, List and Set

    The hierarchy of collections in Kotlin are very similar as in Java: Collection, List and Set. (I won’t detail maps, but they follow the same design). The only, but huge, difference is that it’s divided between mutable and immutable types. Mutable types have methods to change their contents (e.g. add() and set()`), while immutable types don’t.

    Of course, the hierarchy is a bit more detailed compared to Java, but that’s expected from a language that benefits from its parent’s experience.

    image::collection.svg[“Collection, List and Set”,align=”center”]

    == Implementations

    IMHO, the important bit about Kotlin collections is not their hierarchy

    • though it’s important to understand about the difference between mutable and immutable.

    As Java developers know, there’s no such things as out-of-the-box immutable collection types in Java. When an immutable collection is required, the mutable collection must be wrapped into an unmodifiable type via a call to the relevant Collections.unmodifiableXXX(). But unmodifiable types are not public, they are private in Collections: types returned are generic ones (List or Set interfaces). It means they implement all methods of the standard collections. Immutability comes from the mutable-related methods throwing exceptions: at compile time, there’s no way to differentiate between a mutable and an immutable collection.

    On the opposite, Kotlin offers a clean separation between mutable and immutable types. It also provides dedicated functions to create objects of the relevant type:

    image::collectionskt.svg[Collections-creating functions,align=”center”]

    As opposed to Scala, Kotlin doesn’t implement its own collection types, it reuses those from Java. That means that even when the compile-time type is immutable, the runtime type is always mutable. The downside is that it’s possible to change the collection elements by casting it to the correct runtime type. IMHO, this is no more severe than link:{% post_url 2016-01-17-java-security-manager %}[what allows standard reflection^]. There are several advantages, though:

    1. Faster time-to-market
    2. Java collection types benefited from years of improvement
    3. The underlying implementation can be changed in the future with full backward compatibility
    Categories: Development Tags: API
  • ElasticSearch API cheatsheet

    :revdate: 2017-02-19 16:00:00 +0100 :page-liquid: :experimental:

    ElasticSearch documentation is exhaustive, but the way it’s structured has some room for improvement. This post is meant as a cheat-sheet entry point into ElasticSearch APIs.

    [options=header,footer] |=== 2.+| Category | Description | Call examples

    .9+| https://www.elastic.co/guide/en/elasticsearch/reference/current/docs.html[Document API^] .4+| Single Document API | https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html[Adds a new document^] l| PUT /my_index/my_type/1 { “my_field” : “my_value” }

    POST /my_index/my_type { … }

    PUT /my_index/my_type/1/_create { … }

    https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-get.html[Gets an existing document^]  
    l GET /my_index/my_type/0
    https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete.html[Deletes a document^]  
    l DELETE /my_index/my_type/0

    | https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html[Updates a document^] l| PUT /my_index/my_type/1 { … }

    .5+| Multi-Document API | https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-multi-get.html[Multi-get^] l| GET /_mget { “docs” : [ { “_index” : “my_index”, “_type” : “my_type”, “_id” : “1” } ] }

    GET /my_index/_mget { “docs” : [ { “_type” : “my_type”, “_id” : “1” } ] }

    GET /my_index/my_type/_mget { “docs” : [ { “_id” : “1” } ] }

    https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html[Performs many index/delete operations in one call^]
     

    | icon:exclamation-triangle[] https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html[Deletes by query^] l| POST /my_index/_delete_by_query { “query”: { “match”: { … } } }

    | icon:exclamation-triangle[] https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update-by-query.html[Updates by query^] l| POST /my_index/_update_by_query?conflicts=proceed POST /my_index/_update_by_query?conflicts=proceed { “query”: { “term”: { “my_field”: “my_value” } } }

    POST /my_index1,my_index2/my_type1,my_type2/_update_by_query

    | icon:exclamation-triangle[] https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html[Reindexes^] l| POST /_reindex { “source”: { “index”: “old_index” }, “dest”: { “index”: “new_index” } }

    .7+| https://www.elastic.co/guide/en/elasticsearch/reference/current/search.html[Search API^] | URI Search | https://www.elastic.co/guide/en/elasticsearch/reference/current/search-uri-request.html[Executes a search with query parameters on the URL^] l| GET /my_index/my_type/_search?q=my_field:my_value GET /my_index/my_type/_search { “query” : { “term” : { “my_field” : “my_value” } } }

    Search Shards API  
    https://www.elastic.co/guide/en/elasticsearch/reference/current/search-shards.html[Gets indices/shards of a search would be executed against^]  
    l GET /my_index/_search_shards

    | Count API | https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html[Executes a count query^] l| GET /my_index/my_type/_count?q=my_field:my_value GET /my_index/my_type/_count { “query” : { “term” : { “my_field” : “my_value” } } }

    | Validate API | https://www.elastic.co/guide/en/elasticsearch/reference/current/search-validate.html[Validates a search query^] l| GET /my_index/my_type/_validate?q=my_field:my_value GET /my_index/my_type/_validate { “query” : { “term” : { “my_field” : “my_value” } } }

    | Explain API | https://www.elastic.co/guide/en/elasticsearch/reference/current/search-explain.html[Provides feedback on computation of a search^] l| GET /my_index/my_type/0/_explain { “query” : { “match” : { “message” : “elasticsearch” } } } GET /my_index/my_type/0/_explain?q=message:elasticsearch

    | Profile API | icon:exclamation-triangle[] https://www.elastic.co/guide/en/elasticsearch/reference/current/search-profile.html[Provides timing information on individual components during a search^] l| GET /_search { “profile”: true, “query” : { … } }

    | Field Stats API | icon:exclamation-triangle[] https://www.elastic.co/guide/en/elasticsearch/reference/current/search-field-stats.html[Finds statistical properties of fields without executing a search^] l| GET /_field_stats?fields=my_field GET /my_index/_field_stats?fields=my_field GET /my_index1,my_index2/_field_stats?fields=my_field

    .28+| https://www.elastic.co/guide/en/elasticsearch/reference/current/indices.html[Indices API^] .7+| https://www.elastic.co/guide/en/elasticsearch/reference/current/indices.html#index-management[Index management] | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html[Instantiates a new index^] l| PUT /my_index { “settings” : { … } }

    | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-delete-index.html[Deletes existing indices^] l| DELETE /my_index DELETE /my_index1,my_index2 DELETE /my_index* DELETE /_all

    | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-index.html[Retrieves information about indices^] l| GET /my_index GET /my_index* GET my_index/_settings,_mappings

    https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-exists.html[Checks whether an index exists^]  
    l HEAD /my_index

    | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html[Closes/opens an index^] l| POST /my_index/_close POST /my_index/_open

    https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-shrink-index.html[Shrinks an index to a new index with fewer primary shards^]
     

    | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-rollover-index.html[Rolls over an alias to a new index if conditions are met^] l| POST /my_index/_rollover { “conditions”: { … } }

    .3+| https://www.elastic.co/guide/en/elasticsearch/reference/current/indices.html#mapping-management[Mapping management^] | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html[Adds a new type to an existing index] l| PUT /my_index/_mapping/new_type { “properties”: { “my_field”: { “type”: “text” } } }

    | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-field-mapping.html[Retrieves mapping definition for fields^] l| GET /my_index/_mapping/my_type/field/my_field GET /my_index1,my_index2/_mapping/my_type/field/my_field GET /_all/_mapping/my_type1,my_type2/field/my_field1,my_field2 GET /_all/_mapping/my_type1/field/my_field

    https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-types-exists.html[Checks whether a type exists^]  
    l HEAD /my_index/_mapping/my_type

    .2+| https://www.elastic.co/guide/en/elasticsearch/reference/current/indices.html#alias-management[Alias management^] | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html[Creates an alias over an index^] l| POST /_aliases { “actions” : [ { “add” : { “index” : “my_index”, “alias” : “my_alias” } } ] }

    POST /_aliases { “actions” : [ { “add” : { “index” : [“index1”, “index2”] , “alias” : “another_alias” } } ] }

    | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html[Removes an alias^] l| POST /_aliases { “actions” : [ { “remove” : { “index” : “my_index”, “alias” : “my_old_alias” } } ] }

    .7+| https://www.elastic.co/guide/en/elasticsearch/reference/current/indices.html#index-settings[Index settings^] | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-update-settings.html[Updates settings of indices^] l| PUT /my_index/_settings { … }

    https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-settings.html[Retrieves settings of indices^]  
    l GET /my_index/_settings

    | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-analyze.html[Performs an analysis process of a text and return the tokens^] l| GET /_analyze { “analyzer” : “standard”, “text” : “this is a test” }

    | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates.html[Creates a new template^] l| PUT /_template/my_template { … }

    https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates.html#delete[Deletes an existing template^]  
    l DELETE /_template/my_template
    https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates.html#getting[Gets info about an existing template^]  
    l GET /_template/my_template
    https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates.html#indices-templates-exists[Checks whether a template exists^]  
    l HEAD /_template/my_template
    Replica configuration
    icon:exclamation-triangle[] https://www.elastic.co/guide/en/elasticsearch/reference/current/indices.html#shadow-replicas[Sets index data location on a disk^]
     

    .4+| https://www.elastic.co/guide/en/elasticsearch/reference/current/indices.html#monitoring[Monitoring^] | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-stats.html[Provides statistics on indices^] l| GET /_stats GET /my_index1/_stats GET /my_index1,my_index2/_stats GET /my_index1/_stats/flush,merge

    | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-segments.html[Provides info on Lucene segments^] l| GET /_segments GET /my_index1/_segments GET /my_index1,my_index2/_segments

    | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-recovery.html[Provide recovery info on indices^] l| GET /_recovery GET /my_index1/_recovery GET /my_index1,my_index2/_recovery

    | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-shards-stores.html[Provide store info on shard copies of indices^] l| GET /_shard_stores GET /my_index1/_shard_stores GET /my_index1,my_index2/_shard_stores

    .4+| https://www.elastic.co/guide/en/elasticsearch/reference/current/indices.html#status-management[Status management^] | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-clearcache.html[Clears the cache of indices^] l| POST /_cache/clear POST /my_index/_cache/clear POST /my_index1,my_index2/_cache/clear

    | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-refresh.html[Explicitly refreshes indices^] l| POST /_refresh POST /my_index/_refresh POST /my_index1,my_index2/_refresh

    | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-flush.html[Flushes in-memory transaction log on disk^] l| POST /_flush POST /my_index/_flush POST /my_index1,my_index2/_flush

    | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html[Merge Lucene segments^] l| POST /_forcemerge?max_num_segments=1 POST /my_index/_forcemerge?max_num_segments=1 POST /my_index1,my_index2/_forcemerge?max_num_segments=1

    .18+| https://www.elastic.co/guide/en/elasticsearch/reference/current/cat.html[cat API^] | cat aliases | https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-alias.html[Shows information about aliases, including filter and routing infos^] l| GET /_cat/aliases?v GET /_cat/aliases/my_alias?v

    cat allocations  
    https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-allocation.html[Provides a snapshot on how many shards are allocated and how much disk space is used for each node ^]  
    l GET /_cat/allocation?v

    | cat count | https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-count.html[Provides quick access to the document count^] l| GET /_cat/count?v GET /_cat/count/my_index?v

    | cat fielddata | https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-fielddata.html[Shows heap memory currently being used by fielddata^] l| GET /_cat/fielddata?v GET /_cat/fielddata/my_field1,my_field2?v

    | cat health | https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-health.html[One-line representation of the same information from^] /_cluster/health l| GET /_cat/health?v GET /_cat/health?v&ts=0

    | cat indices | https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-indices.html[Provides a node-spanning cross-section of each index^] l| GET /_cat/indices?v GET /_cat/indices?v&s=index GET /_cat/indices?v&health=yellow GET /_cat/indices/my_index*?v&health=yellow

    cat master  
    https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-master.html[Displays the master’s node ID, bound IP address, and node name^]  
    l GET /_cat/master?v

    | cat nodeattrs | https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-nodeattrs.html[Shows custom node attributes^] l| GET /_cat/nodeattrs?v GET /_cat/nodeattrs?v&h=name,id,pid,ip

    | cat nodes | https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-nodes.html[Shows cluster topology^] l| GET /_cat/nodes?v GET /_cat/nodes?v&h=name,id,pid,ip

    cat pending tasks  
    https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-pending-tasks.html[Provides the same information as^] /_cluster/pending_tasks  
    l GET /_cat/pending_tasks?v

    | cat plugins | https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-plugins.html[Provides a node-spanning view of running plugins per node^] l| GET /_cat/plugins?v GET /_cat/plugins?v&h=name,id,pid,ip

    | cat recovery | https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-recovery.html[Shows on-going and completed index shard recoveries^] l| GET /_cat/recovery?v GET /_cat/recovery?v&h=name,id,pid,ip

    cat repositories  
    https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-repositories.html[Shows snapshot repositories registered in the cluster^]  
    l GET /_cat/repositories?v

    | cat thread pool | https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-thread-pool.html[Shows cluster-wide thread pool statistics per node^] l| GET /_cat/thread_pool?v GET /_cat/thread_pool?v&h=id,pid,ip

    | cat shards | https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-shards.html[Displays shards to nodes relationships^] l| GET /_cat/shards?v GET /_cat/shards/my_index?v GET /_cat/shards/my_ind*?v

    | cat segments | https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-segments.html[Provides information similar to^] _segments l| GET /_cat/segments?v GET /_cat/segments/my_index?v GET /_cat/segments/my_index1,my_index2?v

    cat snapshots  
    https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-snapshots.html[Shows snapshots belonging to a repository^]  
    l /_cat/snapshots/my_repo?v

    | cat templates | https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-templates.html[Provides information about existing templates^] l| GET /_cat/templates?v GET /_cat/templates/my_template GET /_cat/templates/my_template*

    .11+| https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster.html[Cluster API^] | Cluster Health | https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-health.html[Gets the status of a cluster’s health^] l| GET /_cluster/health GET /_cluster/health?wait_for_status=yellow&timeout=50s GET /_cluster/health/my_index1 GET /_cluster/health/my_index1,my_index2

    | Cluster State | https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-state.html[Gets state information about a cluster^] l| GET /_cluster/state GET /_cluster/state/version,nodes/my_index1 GET /_cluster/state/version,nodes/my_index1,my_index2 GET /_cluster/state/version,nodes/_all

    | Cluster Stats | https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-stats.html[Retrieves statistics from a cluster^] l| GET /_cluster/stats GET /_cluster/stats?human&pretty

    Pending cluster tasks  
    https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-pending.html[Returns a list of any cluster-level changes^]  
    l GET /_cluster/pending_tasks

    | Cluster Reroute | https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-reroute.html[Executes a cluster reroute allocation^] l| GET /_cluster/reroute { … }

    | Cluster Update Settings | https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-update-settings.html[Update cluster-wide specific settings^] l| GET /_cluster/settings { “persistent” : { … }, “transient” : { … } }

    | Node Stats | https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-stats.html[Retrieves cluster nodes statistics^] l| GET /_nodes/stats GET /_nodes/my_node1,my_node2/stats GET /_nodes/127.0.0.1/stats GET /_nodes/stats/indices,os,process

    | Node Info | https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-info.html[Retrieves cluster nodes information^] l| GET /_nodes GET /_nodes/my_node1,my_node2 GET /_nodes/_all/indices,os,process GET /_nodes/indices,os,process GET /_nodes/my_node1,my_node2/_all

    | Task Management API | icon:exclamation-triangle[] https://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html[Retrieve information about tasks currently executing on nodes in the cluster^] l| GET /_tasks GET /_tasks?nodes=my_node1,my_node2 GET /_tasks?nodes=my_node1,my_node2&actions=cluster:*

    | Nodes Hot Threads | https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-hot-threads.html[Gets current hot threads on nodes in the cluster^] l| GET /_nodes/hot_threads GET /_nodes/hot_threads/my_node GET /_nodes/my_node1,my_node2/hot_threads

    | Cluster Allocation Explain API | icon:exclamation-triangle[] https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-allocation-explain.html[Answers the question “why is this shard unnassigned?”^] l| GET /_cluster/allocation/explain GET /_cluster/allocation/explain { “index”: “myindex”, “shard”: 0, “primary”: false }

    4.+ icon:exclamation-triangle[] marks an experimental (respectively new) API that is subject to removal (resp. change) in future versions

    |===

    NOTE: Last updated on Feb. 22^th^

    Categories: Development Tags: ElasticElasticSearchAPI
  • Extension functions for more consistent APIs

    Hair extensions

    // https://commons.wikimedia.org/wiki/File:Bonding-and-sealing-extensions.jpg

    Kotlin’s https://kotlinlang.org/docs/reference/extensions.html=extension-functions[extension functions^] are a great way to add behavior to a type sitting outside one’s control - the JDK or a third-party library.

    For example, the JDK’s String class offers the toLowerCase() and toUpperCase() methods but nothing to capitalize the string. In Kotlin, this can be helped by adding the desired behavior to the String class through an extension function:

    [source,java]

    fun String.capitalize() = when { length < 2 -> toUpperCase() else -> Character.toUpperCase(toCharArray()[0]) + substring(1).toLowerCase() }

    println(“hello”.capitalize())

    Extension functions usage is not limited to external types, though. It can also improve one’s own codebase, to handle null values more elegantly.

    This is a way one would define a class and a function in Kotlin:

    [source,java]

    class Foo { fun foo() = println(“foo”) } —-

    Then, it can be used on respectively non-nullable and nullable types like that:

    [source,java]

    val foo1 = Foo() val foo2: Foo? = Foo() foo1.foo() foo2?.foo() —-

    Notice the compiler enforces the usage of the null-safe ?. operator on the nullable type instance to prevent NullPointerException.

    Instead of defining the foo() method directly on the type, let’s define it as an extension function, but with a twist. Let’s make the ?. operator part of the function definition:

    [source,java]

    class Foo

    fun Foo?.safeFoo() = println(“null-safe foo”)

    Usage is now slightly modified:

    [source,java]

    val foo1 = Foo() val foo2: Foo? = Foo() val foo3: Foo? = null foo1.safeFoo() foo2.safeFoo() foo3.safeFoo() —-

    Whether the type is non-nullable or not, the calling syntax is consistent. Interestingly enough, the output is the following:


    null-safe foo null-safe foo null-safe foo —-

    Yes, it’s possible to call a method on a https://kotlinlang.org/docs/reference/extensions.html=nullable-receiver[null instances^]! Now, let’s update the Foo class slightly:

    [source,java]

    class Foo { val foo = 1 } —-

    What if the foo() extension function should print the foo property instead of a constant string?

    [source,java]

    fun Foo?.safeFoo() = println(foo) —-

    The compiler complains:


    Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Foo? —-

    The extension function needs to be modified according to the compiler’s error:

    [source,java] fun Foo?.safeFoo() = println(this?.foo)

    The output becomes:


    1 1 null —-

    Extension functions are a great way to make API more consistent and to handle null elegantly instead of dropping the burden on caller code.

    Categories: Kotlin Tags: designAPIextension function