Hi! I want to ask a non-arrow related question, ma...
# arrow
d
Hi! I want to ask a non-arrow related question, mainly because I think that arrow core developers have a lot of experience with kotlinc's type inference and maybe you'll be able to help me in finding a workaround/solution.
I have a simply looking composition with generics and have even the new type inference fail on it. Is this a bug?
Copy code
interface Validator<T> {
  fun validate(input: T)
  fun and(other: Validator<T>): Validator<T> = TODO()
}

fun <T> andExternal(v1: Validator<T>, v2: Validator<T>): Validator<T> = TODO()

fun <T> isNotEmpty(): Validator<T> = TODO()
fun <T> isNotBlank(): Validator<T> = TODO()

fun doStuff(validator: Validator<String>) { }

fun main() {
  // isNotEmpty is highlighted: "not enough information to infer type variable T"
  doStuff(isNotEmpty().and(isNotBlank()))
  // no error when using external variant of "and", inferred as String!
  doStuff(andExternal(isNotEmpty(), isNotBlank()))
}
Actually it may be an arrow related question because this came up when I tried to implement an applicative validation (without help of Arrow), failed, tried to simplify.
I wonder why is
andExternal
case is able to infer types and
and
case - doesn't
s
From what I can tell here the problem is how inferrence works. Both
isNotEmpty()
and
isNotBlank()
take a generic parameter.
doStuff
fixes to
String
, but that
String
has to reach
isNotEmpty
. In the compiling case, we can see that
andExternal
can easily pick up
T
from
doStuff
which is
String
. So the type argument
String
goes from
doStuff
straight to
andExternal
and then to both it's arguments
isNotEmpty
&
isNotBlank
. In the other case,
T
cannot be inferred by
add
since
add
doesn't take a generic param. So instead the generic param needs to be inferred from the
Validator
add
is called upon. So in the case that's not compiling you need to redundantly specify
String
for
isNotEmpty
before you can call
add
on it. Because it cannot be inferred from
doStuff
.
d
I am excited by this analysis, thank you! I didn't know how to wrap my head around this. I've seen that explicit type helps, but I wouldn't want to do this, because I intend this to be a public API and having the end user to specify the type is a) ugly b) breaks discoverability of an API: one cannot see all completions available for
isNotEmpty()
before they write
isNotEmpty<String>()
. So I guess I'll have to resort to something similar in form to
andExternal
, maybe also using infix notation. This is not very elegant especially if I will want to introduce monadic composition... I guess in this case I'd better port this to arrow one day)
s
if
and
doesn't need to be overloaded, then you can define it as an extension function which will result in the same API as
addExternal
. You can also use that approach to inline interface functions, or re-ify them.
d
I tried that. I mean defining
and
as an extension function. For some reason result is the same, it gives a compilation error, fails to infer. Maybe extension functions trigger different inference rules.
s
fun <T>   Validator<T>.and(other: Validator<T>): Validator<T> = TODO()
resulted in a compilation error? 😮
d
yep, exactly! )) actually the code above is copy-pasteable, you can try pasting it somewhere and see for yourself! 😄
I've seen that kotlin's
Flow
operators have
@BuilderInference
magic annotation, its documentation suggests that it helps with inference in extension functions/lambda arguments. I tried sticking it to a few places, but it didn't help.
Reported this issue with failing to infer when using extension, maybe it'll end up to be a bug: https://youtrack.jetbrains.com/issue/KT-40220
👌 1
s
actually the code above is copy-pasteable, you can try pasting it somewhere and see for yourself!
I definitely will and let you know!
👍 1