-
Notifications
You must be signed in to change notification settings - Fork 21
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
Figuring out custom DSLs #161
Comments
All the other raise builders in Builders.kt seem to be using the error types directly, only exception being |
Hey @CLOVIS-AI, What @kyay10 mentions is exactly right. I meant to update the example from the documentation to use the example from Quiver, because So the last snippet you shared is perfect!
The reason I choose DialogResult<out T>
|- Positive<out T>(value: T) : DialogResult<T>
|- Neutral : DialogResult<Nothing>
|- Negative : DialogResult<Nothing>
\- Cancelled: DialogResult<Nothing> We can now not really conveniently provide DialogResult<out T>
|- Positive<out T>(value: T) : DialogResult<T>
\- Error : DialogResult<Nothing>
|- Neutral : Error
|- Negative : Error
\- Cancelled: Error We can again benefit from dialogResult {
val x: DialogResult.Positive(1).bind()
val y: Int = DialogResult.Error.left().bind()
x + y
} That can be useful if you need to for example want to accumulate errors, you can now benefit from the default behavior in Kotlin. fun dialog(int: Int): DialogResult<Int> =
if(int % 2 == 0) DialogResult.Positive(it) else Dialog.Neutral
val res: Either<NonEmptList<DialogResult.Error>, NonEmptyList<Int>> =
listOf(1, 2, 3).mapOrAccumulate { i: Int ->
dialog(it).getOrElse { raise(it) }
}
dialogResult {
res.mapLeft { ... }.bind()
} But I went a bit of topic. The initial documentation was kind-of brief, but I was hoping for feedback like this, so thank you @CLOVIS-AI! I'm looking for feedback to improve this section in the documentation. Hopefully soon your example will be simplified to: context(Raise<F>)
fun <F : Failure, T> Outcome<F, T>.bind(): T = when (this) {
is OutcomeSuccess -> value
is OutcomeFailure -> raise.raise(failure)
}
@OptIn(ExperimentalTypeInference::class)
inline fun <F : Failure, T> out(@BuilderInference block: OutcomeDsl<F>.() -> T) : Outcome<F, T> =
recover(
block = { OutcomeSuccess(block(OutcomeDsl(this))) },
recover = { e: F -> OutcomeFailure(e) },
) And more complex ADTs could consist of any different numbers of Raise, for example the one in Quiver: sealed class Outcome<out E, out A> constructor(val inner: Either<E, Option<A>>) {
data class Present<A>(val value: A) : Outcome<Nothing, A>(value.some().right())
data class Failure<E>(val error: E) : Outcome<E, Nothing>(error.left())
object Absent : Outcome<Nothing, Nothing>(None.right())
}
context(Raise<None>, Raise<E>)
fun <E, A> Outcome<E, A>.bind(): A = when(this) {
Absent -> option.raise(None)
is Failure -> raise(error)
is Present -> value
}
inline fun <E, A> outcome(block: context(Raise<None>, Raise<E>) () -> A): Outcome<E, A> =
either {
option { block() }
}.toOutcome() That'll allow much more flexibility for more advanced ADTs, and different intersection types to combine I hope that helps, and looking forward on how you think we can improve the documentation. Perhaps if warranted we can turn it into its own page so that we can have a more extended explanation with more details. PS: This is the perfect place for such questions, and feedbacks in the documentation. If you find anything missing, or lacking please open a ticket so we can work together on improving the website and documentation further! We at this point don't consider it complete but rather 1.x.x |
Thanks a lot for your answer, it does clear everything up. The ability to create custom DSLs is really amazing, I'm going through all my projects to upgrade them to this new style. I'm not sure what your issue workflow is here, but as far as I'm concerned, all my questions have been answered, so feel free to close this :) |
@CLOVIS-AI I’d like to keep this open until we fix this.
This issue kind-of represents a short-coming in the docs, so I’d like to improve it. |
This PR introduces a few improvements to the _typed errors_ section, based on discussions I had with attendees to KotlinConf'23. - Separates the _Create your own errors_ into its own page, and includes the discussion in #161. - Makes it more clear what we mean with each term (logical failure, success...). - More slowly builds the differences between Either/Result/Raise... --------- Co-authored-by: Simon Vergauwen <[email protected]>
First, thanks a lot for this update! The possibility to create custom DSLs is amazing. That said, I'm having some issues using them.
I have a library which has its own
Either
-like type, calledOutcome
, except the left side must implement theFailure
interface. I'm trying to create a custom DSL for it so Arrow users can use it as if it wasEither
:Declaring the DSL itself was quite easy following the documentation:
In simple cases, it seems to work fine, however it quickly gets stuck on variance errors:
It seemed weird to me that it was expecting the
OutcomeFailure
type instead (why is it aRaise<Outcome<F, Nothing>>
and not aRaise<F>
?), so I tried to rewrite it as follows:Note how:
OutcomeDsl.raise
is aRaise<F>
instead of aRaise<Outcome<F, Nothing>>
recover
'srecover
properly instantiates anOutcomeFailure
value, thus having a symmetry betweenblock
which instantiates a successful variant andrecover
which instantiates a failure variantWas there an error in the documentation, or did I completely misunderstand the given example?
I was originally going to ask this on the Slack channel, but as the message grew I thought it would be better here… Is this the right place to ask such questions?
The text was updated successfully, but these errors were encountered: