Functional style main function argument parsing for Kotlin - kotlin

Please let me know if this way I write a Q&A is inappropriate. Also, I am expecting some better answer, too. The both solutions I provided are not perfect.
There are some Kotlin argument parser on the Internet now, for example GitHub: xenomachina/kotlin-argparser, GitHub: Kotlin/kotlinx.cli or GitHub: ajalt/clikt. However I don't want to add such a huge folder into my (maybe) small project. What I want is a simple and clean solution, for example just a function, with a "fluent" stream-style implementation. Instead, those projects are all containing several files.
What I am thinking is, just need to resolve the command line parameter into a Map<String, List<String>>, use map.containsKey() to get no_argument parameter, and use map[key] to get required_argument parameter.
For example, a command line parameter list
-a -b c -d e f g -h --ignore --join k --link m n o -p "q r s"
will be parsed as:
{-a=[], -b=[c], -d=[e, f, g], -h=[], --ignore=[], --join=[k], --link=[m, n, o], -p=[q r s]}
or we say
mapOf(
"-a" to listOf(), // POSIX style, no argument
"-b" to listOf("c"), // POSIX style, with single argument
"-d" to listOf("e", "f", "g"), // POSIX style, with multiple argument
"-h" to listOf(), // POSIX style, no argument
"--ignore" to listOf(), // GNU style, no argument
"--join" to listOf("k"), // GNU style, with single argument
"--link" to listOf("m", "n", "o"), // GNU style, with multiple argument
"-p" to listOf("q r s") // POSIX style, with single argument containing whitespaces
)

Well, my solution involves immutability and folding with last parameter as well.
fun main(args: Array<String>) {
val map = args.fold(Pair(emptyMap<String, List<String>>(), "")) { (map, lastKey), elem ->
if (elem.startsWith("-")) Pair(map + (elem to emptyList()), elem)
else Pair(map + (lastKey to map.getOrDefault(lastKey, emptyList()) + elem), lastKey)
}.first
println(map)
val expected = mapOf(
"-a" to emptyList(),
"-b" to listOf("c"),
"-d" to listOf("e", "f", "g"),
"-h" to emptyList(),
"--ignore" to emptyList(),
"--join" to listOf("k"),
"--link" to listOf("m", "n", "o"),
"-p" to listOf("q r s"))
check(map == expected)
}
Output
{-a=[], -b=[c], -d=[e, f, g], -h=[], --ignore=[], --join=[k], --link=[m, n, o], -p=[q r s]}
It also handles the case where the first arguments are parameters, and you can access them in map[""]

Here is another implementation:
fun getopt(args: Array<String>): Map<String, List<String>>
{
var last = ""
return args.fold(mutableMapOf()) {
acc: MutableMap<String, MutableList<String>>, s: String ->
acc.apply {
if (s.startsWith('-'))
{
this[s] = mutableListOf()
last = s
}
else this[last]?.add(s)
}
}
}
Directly construct the map structure, but a reference for the last parameter should be kept for adding next arguments. This function is not so streaming, but just need 1 pass of data. And it just simply discard the leading arguments without a previous parameter.

Here is my implementation.
fun getopt(args: Array<String>): Map<String, List<String>> = args.fold(mutableListOf()) {
acc: MutableList<MutableList<String>>, s: String ->
acc.apply {
if (s.startsWith('-')) add(mutableListOf(s))
else last().add(s)
}
}.associate { it[0] to it.drop(1) }
Use fold to group parameters with their corresponding arguments (that is, convert [-p0 arg0 arg1 -p1 arg2] into [[-p0, arg0, arg1], [-p1, arg2]]), then associate into a Map. This function is streaming, but needs 2 pass of data. Also, if there is some leading arguments without previous parameter, it will cause an exception.

Related

Why does mapOf() with multiple types of keys return a Map with an `out` key parameter?

In Kotlin 1.7.0,
mapOf("s" to "s2", 0 to 5)
gives this response in the ki REPL:
res1: Map<out Any, Any> = {s=s2, 0=5}
My question is, why is the key parameter out (covariant)? Normally I might not care, but I'm trying to understand Kotlin generics, and specifically how variance works.
According to the docs, the signature for mapOf() is
fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V>
I can understand that in order to cover both String and Int types, the K type parameter has to be Any. But why out? And why out for K and not for V?
If we try a mutable map, we get out for both:
mutableMapOf("s" to "s2", 0 to 5)
gives
MutableMap<out Any, out Any>
This makes even less sense to me. At least K is consistent with V in both being out. But now we can't add or change any of the mappings in our mutable map, because keys and values are both of a "producer" type:
val m2 = mutableMapOf("s" to "s2", 0 to 5)
[10] m2.put(3 to 6)
ERROR Type mismatch: inferred type is Pair<Int, Int> but Nothing was expected (Line_11.kts:1:8)
ERROR No value passed for parameter 'value' (Line_11.kts:1:14)
[15] m2[3] = "fi"
ERROR The integer literal does not conform to the expected type CapturedType(out Any) (Line_16.kts:1:4)
ERROR Type mismatch: inferred type is String but CapturedType(out Any) was expected (Line_16.kts:1:9)
We can fixed this by explicitly specifying the type parameters of our mutable map:
val m3: MutableMap<Any, Any> = mutableMapOf(3 to 4, "a" to "b")
But I'm wondering why these parameters in the inferred type are out. Is this intentional, or is it an unfortunate side effect of the generics system?
Update:
When I try something similar in the IDE, the out annotation is apparently not generated:
val myMap = mutableMapOf("s" to "s2", 0 to 5)
myMap[3] = 7
The IDE says the type of myMap is MutableMap<{Comparable*> & java.io.Serializable}, {Comparable*> & java.io.Serializable}>, as Tenfour04 also said in the comments. The ability to assign myMap[3] = 7 shows that the key is not annotated out. So I'm guessing that the out comes from something unique to the ki shell, or from Kotlin 1.7.0 (whereas the IDE seems to be running Kotlin 1.7.20).
Update 2:
I've now run this in a Kotlin scratch file in Android Studio (under Kotlin 1.7.20, on JVM). The following are local variables in main():
val m = mapOf("s" to "s2", 0 to 5)
printType(m)
val mm = mutableMapOf("s" to "s2", 0 to 5)
mm[3] = 7
printType(mm)
I'm using the printType() function that #broot linked to in comments:
import kotlin.reflect.typeOf
inline fun <reified T> printType(obj: T) {
println(typeOf<T>())
}
The results:
kotlin.collections.Map<out kotlin.Any, kotlin.Any>
kotlin.collections.MutableMap<out kotlin.Any, out kotlin.Any>
These are the same results I described at the beginning of the question.
This seems to me to show that the out annotation is not coming from something specific to the ki shell, nor from Kotlin 1.7.0.

Error when trying to convert a list of objects in a string using reduce function

I am playing with kotlin language and I tried the following code:
data class D( val n: Int, val s: String )
val lst = listOf( D(1,"one"), D(2, "two" ) )
val res = lst.reduce { acc:String, d:D -> acc + ", " + d.toString() }
The last statement causes the following errors:
Expected parameter of type String
Expected parameter of type String
Type mismatch: inferred type is D but String was expected
while this version of the last statement works:
val res = lst.map { e -> e.toString() }.reduce { acc, el -> acc + ", " + el }
I do not understand why the first version does not work. The formal definition of the reduce function, found here, is the following:
inline fun <S, T : S> Iterable<T>.reduce(
operation: (acc: S, T) -> S
): S
But this seems in contrast with the following sentence, on the same page:
Accumulates value starting with the first element and applying
operation from left to right to current accumulator value and each
element.
That is, as explained here:
The difference between the two functions is that fold() takes an
initial value and uses it as the accumulated value on the first step,
whereas the first step of reduce() uses the first and the second
elements as operation arguments on the first step.
But, to be able to apply the operation on first and second element, and so on, it seems to me tha the operation shall have both arguments of the base type of the Iterable.
So, what am I missing ?
Reduce is not the right tool here. The best function in this case is joinToString:
listOf(D(1, "one"), D(2, "two"))
.joinToString(", ")
.let { println(it) }
This prints:
D(n=1, s=one), D(n=2, s=two)
reduce is not designed for converting types, it's designed for reducing a collection of elements to a single element of the same type. You don't want to reduce to a single D, you want a string. You could try implementing it with fold, which is like reduce but takes an initial element you want to fold into:
listOf(D(1, "one"), D(2, "two"))
.fold("") { acc, d -> "$acc, $d" }
.let { println(it) }
However, this will add an extra comma:
, D(n=1, s=one), D(n=2, s=two)
Which is exactly why joinToString exists.
You can see the definition to understand why its not working
To make it work, you can simply create an extension function:
fun List<D>.reduce(operation: (acc: String, D) -> String): String {
if (isEmpty())
throw UnsupportedOperationException("Empty list can't be reduced.")
var accumulator = this[0].toString()
for (index in 1..lastIndex) {
accumulator = operation(accumulator, this[index])
}
return accumulator
}
you can use it as:
val res = lst.reduce { acc:String, d:D -> acc + ", " + d.toString() }
or simply:
val res = lst.reduce { acc, d -> "$acc, $d" }
You can modify the function to be more generic if you want to.
TL;DR
Your code acc:String is already a false statement inside this line:
val res = lst.reduce { acc:String, d:D -> acc + ", " + d.toString() }
Because acc can only be D, never a String! Reduce returns the same type as the Iterable it is performed on and lst is Iterable<D>.
Explanation
You already looked up the definition of reduce
inline fun <S, T : S> Iterable<T>.reduce(
operation: (acc: S, T) -> S
): S
so lets try to put your code inside:
lst is of type List<D>
since List extends Iterable, we can write lst : Iterable<D>
reduce will look like this now:
inline fun <D, T : D> Iterable<T>.reduce(
operation: (acc: D, T) -> D //String is literally impossible here, because D is not a String
): S
and written out:
lst<D>.reduce { acc:D, d:D -> }

Kotlin: maxBy{} with optimum-value

Let's say I have the following code in Kotlin:
val min = listOf("hello", "", "teeeeeest").minBy { it.length }
What I understand from the implementation of minBy is that it tracks minValue in a variable and iterates through the whole collection and updates it once it finds an even smaller element.
In the case of Strings though, we know that no element can have a value smaller than 0, therefore the empty String "" is optimal and the iteration can be stopped.
Is there a way I can tell minBy (or maxBy) the optimal value so it can stop once that is reached? If not, how can I implement this most easily?
There's no function in the stdlib that can do this, but you can implement it as an extension function yourself.
By using the non-local return feature of inline lambda functions in Kotlin, you can implement it like this:
fun <T, E : Comparable<E>> Iterable<T>.minBy(theoreticalMinimum: E, keySelector: (T) -> E): T? =
minBy {
val key = keySelector(it)
if (key <= theoreticalMinimum) return it // Non-local return.
else key
}
Now you can use it like this, and it will never visit "teeeeeest":
val min = listOf("hello", "", "teeeeeest").minBy(theoreticalMinimum = 0) { it.length }

What's the point of destructuring declarations in Kotlin?

I have come across the concept called destructuring declarations - when you can return multiple values from a function at once. It seems very convenient, but at the same time it looks like a tricky workaround. Each time when I think about that feature in Java, I understand that it's a hole in my architecture - there should probably be a class then, not just a couple of variables.
What do you think?
The concept allows having classes that clearly identify a few of their primary properties, the components.
Then you can access these components by using a destructuring declaration, without syntactic noise of accessing the properties.
Compare:
val point = clickEvent.getPointOnScreen()
val x = point.xCoordinate
val y = point.yCoordinate
// Use `x` and `y` in some calculations
and, assuming that the type has component1 and component2, just:
val (x, y) = clickEvent.getPointOnScreen()
Basically, it is not necessary to use this sort of syntactic sugar, and the concept itself does not harm any of the abstractions, it only provides a convenient way to access properties of a class instance in some cases when you don't need the instance itself.
Another example is working with map entries, e.g:
for ((key, value) in myMap) { /* ... */ }
There's still a Map.Entry<K, V> behind the (key, value) destructuring, and you can replace it by for (entry in myMap) ..., but usually it's the two properties that you need. This is where destructuring saves you from a little syntactic noise.
You can also define componentN function as extension for non data classes like this:
operator fun Location.component1() = latitude
operator fun Location.component2() = longitude
and when you want to process on list of locations, you can write this:
for ((lat, lon) in locations) {
......
}
What's the point of destructuring declarations in Kotlin?
Structuring, or construction, is creating an object from values in different variables. Destructuring is the opposite, to extract values into variables from within an existing object.
Part of the Kotlin philosophy is to be concise since the simpler and more concise the code is, the faster you’ll understand what’s going on. Destructuring improves readability which is part of being concise. Compare the following two snippets (let's consider the class Triple)
Without using destructuring
fun getFullName() = Triple("Thomas", "Alva", "Edison")
val result = getFullName()
val first = result.first
val middle = result.second
val last = result.third
Using destructuring
fun getFullName() = Triple("Thomas", "Alva", "Edison")
val (first, middle, last) = getFullName()
It is also possible to take advantage of destructuring to extract key and value from Map's entries.
for ((key, value) in aMap) {
/* ... */
}
Destructuring is the most useful when dealing with built-in data structures. Their fields have names making sense in the context of a data structure (handy when you're writing your own hashmap), but completely cryptic when you're dealing with the data contained there (which is 100% of the time, nobody writes their own hashmaps). Eg. Pair with it's first and second or Map.Entry with key and value.
Consider transforming Map values:
val myMap = mapOf("apples" to 0, "oranges" to 1, "bananas" to 2)
myMap
.asIterable()
.filter { it.value > 0 }
.sortedBy { it.key.length }
.joinToString(prefix = "We have ", postfix = " in the warehouse") {
"{$it.value} of ${it.key}"
}
To make it readable, you'd have to define intermediate variables:
myMap
.asIterable()
.filter {
val count = it.value
count > 0
}
.sortedBy {
val fruit = it.key
fruit.length
}
.joinToString(prefix = "We have ", postfix = " in the warehouse") {
val count = it.value
val fruit = it.key
"$count of $fruit"
}
Now it's readable, but at what cost?!?
Destructuring makes this cost more beareable:
myMap
.asIterable()
.filter { (fruit, count) -> count > 0 }
.sortedBy { (fruit, count) -> fruit.length }
.joinToString(prefix = "We have ", postfix = " in the warehouse") { (fruit, count) ->
"$count of $fruit"
}
That's the point.

Kotlin methods with Vararg as First Parameter

Note I've looked at the following questions/answers to solve the problem without any luck. Call Java Varargs Method from Kotlin - this one has the varargs parmeter at the end of the parameter list, but my question deals with varargs at the start of the parameters list. Kotlin: Convert List to Java Varargs - the same. Other searches yield the same thing. These were the closest I could find.
I am calling the Kotlin String.split method with a single character delimiter.
This is a vararg method where the vararg parameter is first of multiple parameters. The method is defined like so:
public fun CharSequence.split(vararg delimiters: Char,
ignoreCase: Boolean = false,
limit: Int = 0): List<String>
When I call the method as below, it compiles fine:
fun String.splitRuleSymbol() : String = this.split(':') //ok
But when I try to add the ignoreCase and limit parameters, I get a problem:
fun String.splitRuleSymbol() : String = this.split(':', true, 2) //compiler error
The error I get is...
None of the following functions can be called with the arguments supplied:
public fun CharSequence.split(vararg delimiters: String, ignoreCase: Boolean = ..., limit: Int = ...): List defined in kotlin.text
public fun CharSequence.split(vararg delimiters: Char, ignoreCase: Boolean = ..., limit: Int = ...): List defined in kotlin.text
To me, having a vararg parameter followed by other parameters is somewhat odd, but that's beside the point. If I call it as below, it works fine:
// both of the following compile
fun String.splitRuleSymbol() : String =
this.split(delimiters = ':', ignoreCase = true, limit = 2)
fun String.splitRuleSymbol2() : String =
this.split(';', ignoreCase = true, limit = 2)
Is there a way to pass a vararg Char in to this method without having to qualify my other two parameters with parameter names ignoreCase and limit? Can the compiler not tell that the remaining parameters are not Char?
I have tried the spread operator and a few other ways below , none of which work:
//compiler errors on all these
this.split(*':', true, 2) //using the "spread" operator
this.split(*charArrayOf(':'), true, 2)
this.split(*mutableListOf(':'), true, 2)
this.split(*Array<Char>(1) { ':' }, true, 2)
Yes, some of these look ridiculous, I know. But, is there no way to avoid the verbose alternative?
PS As I was formulating my question, I found another expression that compiled.
this.split(':', limit = 2)
This is less verbose and since I don't need to change the default ignoreCase parameter, it's closer to what I am looking for.
Your observations are correct. Arguments that are after a vararg parameter can only ever be passed in by using named arguments, otherwise you'd run into ambiguity issues (for a trivial example, let's say when all arguments are of type Any).
The best source I can find for this right now is this book.
The vararg parameter is usually the last parameter, but it does not always have to be. If there are other parameters after vararg, then arguments must be passed in using named parameters
Edit: #Les found a good source on it, see their answer.
Thanks to zsmb13, I was able to find the following paragraph in the Kotlin Specification (under "Functions and Lambdas")
Only one parameter may be marked as vararg . If a vararg parameter is
not the last one in the list, values for the following parameters can
be passed using the named argument syntax, or, if the parameter has a
function type, by passing a lambda outside parentheses.
I would venture to add that "can be passed" should be changed to "must be passed" since the compiler won't allow otherwise.
Note The lambda part is interesting in that the spec normally only allows a lambda to be moved outside the parenthesis when it is the last parameter. The wording of the spec implies the lambda could be anywhere after the vararg parameter, but experimentation shows that it cannont, i.e., it must be the last parameter in order to be eligible to move outside of the parenthesis.
fun main(args: Array<String>) {
test("hello", limit = 1, ic = false, delims = ';') { } //ok
//test2("world", limit = 1, ic = false, delims = ';') { } //error
test2("world", f = {}, limit = 1, ic = false, delims = ';') //ok
test("hello world", ';', limit = 1, ic = false) {} //ok
}
fun test(vararg delims: Char, ic: Boolean, limit: Int, f: () -> Unit) {}
fun test2(vararg delims: Char, f: () -> Unit, ic: Boolean, limit: Int) {}
Variable number of arguments (vararg) can be passed in the named form by using the spread operator:
fun foo(vararg strings: String) { /* ... */ }
foo(strings = *arrayOf("a", "b", "c"))
foo(strings = "a") // Not required for a single value
Note that the named argument syntax cannot be used when calling Java functions, because Java bytecode does not always preserve names of function parameters.