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 Second
CLOVIS
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 | SpecificError2
CLOVIS
05/27/2025, 3:13 PMfoo(SpecificError1, SpecificError1)
to be legal and have the return type SpecificError1
mikhail.zarechenskiy
05/27/2025, 3:30 PMfoo(SpecificError1 | SpecificError2, SpecificError1)
the return type becomes SpecificError1 | SpecificError2
, while some may expect it to be just SpecificError2
CLOVIS
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
.