Type problems with fold


#1

I have a code fragment:

val maxWidth = results.fold(0){t, a -> (listOf(t) + a.counts.map{"${it}".length}).max()}

but this results in the error:

…: Type inference failed: Cannot infer type parameter R in inline fun <T, R> Iterable.fold(initial: R, operation: (R, T) -> R): R
None of the following substitutions
receiver: Iterable arguments: (Int?,(Int?, Result) -> Int?)
receiver: Iterable arguments: (Int,(Int, Result) -> Int)
can be applied to
receiver: List arguments: (Int,(Int, Result) -> Int?)

and I have clue where the problem is, why it is there or how to fix it. Why is Int not consistent with Int?


#2

max returns null if the collection is empty, therefore the type of the expression <...>.max() is Int? (nullable Int). You should handle the null case somehow, for example with !!, ?. or ?: operators.


#3

Clearly:

val maxWidth = results.fold(0){t, a -> (listOf(t) + a.counts.map{"${it}".length}).max() ?: 0}

gets around it, but this does feel like a hack.


#4

Why does it feel like a hack? Do you have different expectations about max function’s return type?

By the way you could also use flatMap operation:

val maxWidth = results.flatMap { a -> a.counts.map { "$it".length } }.max()

#5

Ah, I was thinking it was a hack because fold’s seed value would be the return value for max on an empty list, but the thing you’re calling max on is not the thing you’re calling fold on (obviously, else it would be pretty dumb line of code).

Yeah, @Russel Its worth mentioning that kotlin is making null useful again. When calling max on a generic list, what should the return value be for an empty list? null seems pretty fitting, especially since it can be represented in the type system. Thus the type system is demanding that you handle the case where counts is empty.

If you can assert from a domain-logic standpoint that counts will never be empty, then your best bet is to use the !! operator.

val maxWidth = results.fold(0){t, a -> (listOf(t) + a.counts.map{"${it}".length}).max()!!}

–Maybe those GoLang guys were on to something with embedding an arrays size in its type, given a sufficient type structure that might allow the type system to know if your collection can be empty or not.

Also, I think we can use a method reference for map so as to avoid the mountain of symbols that is {"${it}":

val maxWidth = results.fold(0){t, a -> (listOf(t) + a.counts.map(String::length)).max()!!}

#6

I’m still not entirely convinced null is a reasonable return value. Ceylon has strong support for union types allowing for extending one type with another so that you can get “out of band” information in the return. Go gets round it by returning a pair, a value of the type with a return code. In both case the idea is not to use values of the return type as return codes. Kotlin is more like Ceylon than Go, but has not got the same power in it’s type system. In effect the null value is being used as a blunt tool to cover the fact that Kotlin hasn’t got a type system as powerful as Ceylon’s.o

You now have me wondering why I ever went the fold route, the flatMap approach is much better for this case. Thanks for suggesting it.


#7

It is certainly the case that in the context out of which this code came, a.counts is guaranteed to be length 3. I am not sure about the !! though ?: 0 does seem more generally “readable”. This may though be because the !! of Kotlin is not as close to idiomatic things in other languages whereas the Elvis operator is.

Thanks for the comment about String::length. The question is though will it work on integer data? a.counts is a sequence of integers.