CLOVIS
05/26/2025, 5:03 PMerror object First
error object Second
val a: Int|First = TODO()
val b: Int|First|Second = foo(a)
fun <E : Error> foo(e: Int|E): Int|E|Second // ?
Will this be legal?Alejandro Serrano.Mena
05/26/2025, 5:19 PMCLOVIS
05/26/2025, 5:23 PMval logIn by post("/login")
.request<LogInInfo>()
.response<LogInResponse>()
which gives a Endpoint<LogInInfo, LogInResponse> .
I wonder if in the future it could be extended to:
val logIn by post("/login")
.request<LogInInfo>()
.response<LogInResponse>()
.failure<InvalidCredentials>()
.failure<AccountBlocked>()
which would give a Endpoint<LogInInfo, LogInResponse, InvalidCredentials|AccountBlocked> (where InvalidCredentials and AccountBlocked are both marked error , which I understand to be allowed?).
However, this requires the ability to combine errors generically together, hence my question.mikhail.zarechenskiy
05/27/2025, 9:58 AMfoo won't be supported, but the case is clear, it appears in a few of our functions as well. In general, here we need some sort of negative types to declare the type parameter E here doesn't contain SecondCLOVIS
05/27/2025, 10:05 AME does already contain Second ?
I would expect that
val a: Int|First|Second = TODO()
val b: Int|First|Second = foo(a)
In my mind, the signature
fun <E : Error> foo(e: Int|E): Int|E|Second // ?
describes that the function can fail in all ways e can fail + Second . The only things foo can do then are to ① handle the error or ② propagage it in the return type. I don't think either cause a problem?mikhail.zarechenskiy
05/27/2025, 10:25 AMInt | E in the function foo doesn't contain the error Second, because it appears as a separate type in the return position. However, from the type system perspective, it's not the case and E can be instantiated with Second as well. And we either should have some general rules for inference or special syntax. It might work for a function that has a signature like foo, but I don't believe it's a good idea to make it work only for some special functions when in general case it will work differently:
fun <E : Error> foo(e1: E | SpecificError1, e2: E): E = e
// call
foo(SpecificError1 | SpecificError2, SpecificError1)
// Constraint system for the type inference
SpecificError1 | SpecificError2 <: E | SpecificError1
SpecificError1 <: E
=>
SpecificErro1 <: E | SpecificError1 AND SpecificError2 <: E | SpecificErro1
SpecificError1 <: E
=>
SpecificError1 <: E OR trivial
SpecificError2 <: E OR trivial
SpecificError1 <: E
=>
E := CST(SpecificErro1, SpecificErro2) := SpecificErro1 | SpecificError2CLOVIS
05/27/2025, 3:13 PMfoo(SpecificError1, SpecificError1)
to be legal and have the return type SpecificError1mikhail.zarechenskiy
05/27/2025, 3:30 PMfoo(SpecificError1 | SpecificError2, SpecificError1) the return type becomes SpecificError1 | SpecificError2, while some may expect it to be just SpecificError2CLOVIS
05/27/2025, 3:59 PMSpecificError2 ? The second parameter e2: E has the value SpecificError1 , so E must include SpecificError1 (or maybe the code could not compile)mikhail.zarechenskiy
05/27/2025, 6:37 PMCLOVIS
05/27/2025, 7:00 PMSpecificError1 | SpecificError2 , which is what your message explains and the output I expected. However, you mention that 'some may expect it to be just SpecificError2 ', and that I don't understand. If E was SpecificError2 , then the second argument in your example would not compile, as the provided value is SpecificError1 .