Arrow: Coproducts type inference issue

Created on 4 Feb 2019  路  9Comments  路  Source: arrow-kt/arrow

Coproducts created by @abergfeld are awesome, but they have a problem of type inference. Let's say I have this network service call:

fun getUserVerified(userId: UserId): IO<Coproduct4<Boolean, UnauthorizedError, UnenrolledError, UnknownError>> =
    userApi.getUserVerified(userId)
        .map { response ->
            when (response.code) {
                200 -> with(response.body<GetUserVerifiedResponse>()) {
                    this.enrolled.cop<Boolean, UnauthorizedError, UnenrolledError, UnknownError>()
                }
                else -> Unknown.cop<Boolean, UnauthorizedError, UnenrolledError, UnknownError>()
            }
        }

I would expect all the cop() call types to be inferred, but it's not able to, so Coproducts become a bit of a pain to use everywhere.

deprecation enhancement help wanted

All 9 comments

For added context, this appears to be directly related to the defaulted Unit parameters as part of the cop functions.

I think what's going on is that it's unable to infer because all 4 of the cop functions effectively have the same type signature without specifying generics because of the defaulted params.

You can make it work how you want if we change First etc to public and you do instead:

import arrow.generic.coproduct3.*

fun getUserVerified(userId: UserId): IO<Coproduct4<Boolean, UnauthorizedError, UnenrolledError, UnknownError>> =
    userApi.getUserVerified(userId)
        .map { response ->
            when (response.code) {
                200 -> with(response.body<GetUserVerifiedResponse>()) {
                    First(enrolled)
                }
                else -> Fourth(Unknown)
            }
        }

I'd be in favor of removing the cop and other similar methods as they clearly have inference issues with the dummy args as @abergfeld pointed out. The ADT constructors are much friendlier to use than the extensions.

The immediate task if you agree would be to deprecate cop to be removed in 0.9.1 and make the ADT nodes public instead of internal which is what they are now.

Sounds fine to me. Could we create smart constructors a(), b(), c(), d() for those?

Wanna take this one @abergfeld ?

You can create smart constructors but given the verbosity of Coproduct I think it's a better strategy to just keep the ADTs and name them by position like they are now. Note the imported First and Fourth are that of Coproduct3. You need versions of those for each Coproduct type and on top all the syntax which would spike the size of arrow-generic.

Yeah I can take this issue.

I was wondering if we would be okay with migrating by deprecating the cop methods as the type inference is not working and replacing it with something like this:

fun <A, B, C, D> A.first(): Coproduct4<A, B, C, D> = First(this)
fun <A, B, C, D> B.second(): Coproduct4<A, B, C, D> = Second(this)
fun <A, B, C, D> C.third(): Coproduct4<A, B, C, D> = Third(this)
fun <A, B, C, D> D.fourth(): Coproduct4<A, B, C, D> = Fourth(this)

We could then separately make the data classes not internal if we still want to but we'd keep the simple extension way of creating a Coproduct

That's what I was suggesting. I'd be fine with that. Maybe we want to have a separate artifact or something? arrow-generic-syntax ? that'd be an option if the problem is artifact size @raulraja .

Looks good and fine to place in just arrow-generic, no need for more modules

Was this page helpful?
0 / 5 - 0 ratings

Related issues

1Jajen1 picture 1Jajen1  路  3Comments

pakoito picture pakoito  路  4Comments

raulraja picture raulraja  路  3Comments

pakoito picture pakoito  路  4Comments

raulraja picture raulraja  路  4Comments