Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

["Request"] Bind unary values for Nel #3048

Open
CLOVIS-AI opened this issue Apr 29, 2023 · 2 comments
Open

["Request"] Bind unary values for Nel #3048

CLOVIS-AI opened this issue Apr 29, 2023 · 2 comments

Comments

@CLOVIS-AI
Copy link
Contributor

What version are you currently using?

1.2.0-RC

What would you like to see?

Sometimes, inside Raise<Nel<E>>, a failed precondition stops the computation from going further, and short-circuiting is necessary.

Let's image a recursive tree. A node is valid if it satisfies the predicate a(), and all its children satisfy it as well. I'm writing this example with context receivers for readability, but it is already the case now.

sealed class Tree {
    class A : Tree() {
    
        context(Raise<Nel<Failure, Unit>>)
        override fun validate() = either {
            ensure(a()) { nonEmptyListOf(Failure.A()) } // ← here
            
            children.mapOrAccumulate {
                it.validate().bind()
            }.mapLeft { it.flatten() }
                .bind()
        }

    }
    
    class B : Tree() { … }
}

When there are multiple short-circuiting operations, writing ensure() { nonEmptyListOf() } is inconvenient and makes the code more complicated. Instead, the library could provide a variant of ensure and ensureNotNull that encapsulates this behavior:

@RaiseDSL
public inline fun <Error> Raise<Nel<Error>>.ensure(condition: Boolean, raise: () -> Error) {
  contract {
    callsInPlace(raise, AT_MOST_ONCE)
    returns() implies condition
  }
  return if (condition) Unit else raise(nonEmptyListOf(raise()))
}

@RaiseDSL
public inline fun <Error, B : Any> Raise<Nel<Error>>.ensureNotNull(value: B?, raise: () -> Error): B {
  contract {
    callsInPlace(raise, AT_MOST_ONCE)
    returns() implies (value != null)
  }
  return value ?: raise(nonEmptyListOf(raise()))
}

As a bonus, what about a RaiseNel type alias?

@CLOVIS-AI
Copy link
Contributor Author

It would be convenient to do the same with .bind.

@myuwono
Copy link
Collaborator

myuwono commented Apr 29, 2023

@CLOVIS-AI what if we use the extension function instead? i.e.T.nel(): NonEmptyList<T> would that improve readability?
something like

ensure(...) {
  Failure.A().nel()
}

Just my opinion, I think I would prefer to make Raise<E> contextual operations ensure / bind / raise just operate on the E type.. If we add another bind for the inner E in Raise<Nel<E>>, I'm afraid that would make it very confusing for early adopters…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants