Skip to content
This repository has been archived by the owner on Feb 24, 2021. It is now read-only.

Suspend Validated.fx implementation #131

Merged
merged 21 commits into from
Jun 5, 2020
Merged

Conversation

aballano
Copy link
Member

Done in this PR

  • Add suspend fx for Validated
  • Add Validated.fromNullable
  • Fix an issue on Validated docs for error accumulation
  • Rework Validated docs to include an fx example

Details

As per #116 this PR adds fx to validated, so one simple example can be found in the docs of validated itself, where we have the following code:

data class Config(val map: Map<String, String>) {
 fun <A> parse(read: Read<A>, key: String): Validated<ConfigError, A> {
  val v = Option.fromNullable(map[key])
  return when (v) {
   is Some ->
    when (val s = read.read(v.t)) {
     is Some -> s.t.valid()
     is None -> ConfigError.ParseConfig(key).invalid()
    }
   is None -> Validated.Invalid(ConfigError.MissingConfig(key))
  }
 }
}

So, instead of dealing with option, I added a Validated.fromNullable to match fromOption already present and simply deal with validates in an fx block:

data class Config(val map: Map<String, String>) {
  suspend fun <A> parse(read: Read<A>, key: String) = Validated.fx<ConfigError, A> {
    val value = Validated.fromNullable(map[key]) {
      ConfigError.MissingConfig(key)
    }.bind()

    val readVal = Validated.fromOption(read.read(value)) {
      ConfigError.ParseConfig(key)
    }.bind()

    readVal
  }
}

The only difference is that we force parse to be suspended because of this version of fx being suspended (shall we wait for #124?).

@aballano aballano requested review from nomisRev and pakoito May 27, 2020 15:28
* v1 is Validated.Valid && v2 is Validated.Invalid -> v2.toValidatedNel()
* v1 is Validated.Invalid && v2 is Validated.Valid -> v1.toValidatedNel()
* v1 is Validated.Invalid && v2 is Validated.Invalid -> Validated.Invalid(NonEmptyList(v1.e, listOf(v2.e)))
* else -> throw IllegalStateException("Not possible value")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of Throw, we could return an Invalid with it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But that's the impossible case, does it really matter? Actually I was looking for if there was an operator that does this already and doesn't leave the burden of writing this to the dev but couldn't see any

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it exhaustive then?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It cannot be because we're checking 2 elements

Copy link
Member

@pakoito pakoito May 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need pattern matching weep. I'd still use Invalid for the sake of not promoting exceptions. Not a blocker tho.

Copy link
Member

@nomisRev nomisRev May 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you can use Applicative for this.

Validated.applicative(NonEmptyList.semigroup<String>()).tupledN(
  1.valid(),
  "2".invalidNel(),
  "3".invalidNel()
) // Validated.Invalid(NonEmptyList("2", "3"))

You can always make these exhaustive by nesting when, which after a lot of back and forth have gone back to...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I just realized that we're in core-data and we don't have access to the extensions :/

What I did is the following:

  • Added a convenient nelApplicative function in Validated extensions
  • Suggest to use this approach instead of the custom function in docs, but without changing the examples code due to the import problem

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just meh in general. It could be possible to rewrite it as two nested folds and some manual formatting.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of showing parallelValidate at all.. It promotes/shows code that no-one should ever write using Arrow.

We have a bunch of helpers in the docs module, any way we can provide instances there to make the desired snippets compile?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only reason I like it is for the explanation vs a Validated Nel, which is also explained in there. I changed it to try to make it clear that this is just for the shake of the example

Copy link
Member

@nomisRev nomisRev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!
We should refactor those snippets to use mapN or tupledN instead instead of parallelValidate before we merge.

@aballano aballano requested review from nomisRev and pakoito May 29, 2020 15:44
@aballano
Copy link
Member Author

aballano commented Jun 1, 2020

@pakoito Would you take a second look at the docs when you have time, please? 🙏😁

* }
* is None -> Validated.Invalid(ConfigError.MissingConfig(key))
* suspend fun <A> parse(read: Read<A>, key: String) = Validated.fx<ConfigError, A> {
* val value = Validated.fromNullable(map[key]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat

* val value = Validated.fromNullable(map[key]) {
* ConfigError.MissingConfig(key)
* }.bind()
* val readVal = Validated.fromOption(read.read(value)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Good use of both APIs

Copy link
Member

@pakoito pakoito left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was being extra nitpicky on the review. Nothing's a blocker really.

@aballano
Copy link
Member Author

aballano commented Jun 5, 2020

@nomisRev If you have any more comments later please let me know so I can open another PR :)

@aballano aballano merged commit caa7e75 into master Jun 5, 2020
@aballano aballano deleted the ab/validated-fx-suspend branch June 5, 2020 09:00
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants