Hmm, this deprecation message is wrong?: ```@Suppr...
# arrow
m
Hmm, this deprecation message is wrong?:
Copy code
@Suppress("ClassName")
@Deprecated(
  "The `either` computation block supports validated with the right short-circuiting semantics",
  ReplaceWith("either", "arrow.core.computations.either")
)
object validated {
  inline fun <E, A> eager(crossinline c: suspend RestrictedValidatedEffect<E, *>.() -> A): Validated<E, A> =
    Effect.restricted(eff = { RestrictedValidatedEffect { it } }, f = c, just = { it.valid() })

  suspend inline operator fun <E, A> invoke(crossinline c: suspend ValidatedEffect<E, *>.() -> A): Validated<E, A> =
    Effect.suspended(eff = { ValidatedEffect { it } }, f = c, just = { it.valid() })
}
As far as I can see .bind() short circuit on first validation error instead of accumulating?:
Copy code
suspend fun <B> Validated<E, B>.bind(): B =
    when (this) {
      is Validated.Valid -> a
      is Validated.Invalid -> control().shift(this@bind)
    }
s
Well, that was the behavior of the
validated { }
block which was confusing because it didn’t accumulate errors but rather behaved like
either { }
that’s why we deprecated it. If you want to
bind
Validated like an
either
, you can do it inside the
either { }
block.
m
Yes, I see the old was just doing the same, so confusing yes.
What I would like is an cleaner alternative to validated.zip(...) without need to keep parameter order correct.
s
Only way to do that is in an “untyped” way, or when all have the same type using
sequence
or
traverse
.
m
Hmm japp, I'm thinking same error type and then accumulating to Nel<E>. Success types should be able to be different?
s
If the success types are different, you need to somehow converge them ==>
zip
. Sine when you combine all errors, you need to somehow be able to combine all values as well. ollect cIf the values are all of the same type, they can safely be combined into a collection type.
m
Yes, I'm thinking that can be done in the block (as the last argument to zip):
Copy code
validated<Nel<Error>, Int> {
  val a = 5.valid().bind()
  val b = "10".valid().bind()
  Error("test").invalid().bind()
  a + b.toInt()
}
Or... Last line will be executed also when there is an error and not all values available.
s
What happens in this case:
Copy code
validated<Nel<Error>, Int> {
  val a: Int = Error("test").invalid().bind()
  val b = "$a".valid().bind()
  Error("test2").invalid().bind()
  a + b.toInt()
}
You can never run
"$a"
because you never receive
a
.
m
@simon.vergauwen Spying on this thread because this is remotely relevant to what I'm trying to achieve in kotest 👀
Am i right to say that due to the nature of continuation being flatmaps, we can't currently do accumulation?
As well, since validated isn't a monad, then the comprehension wouldn't make sense would it?
s
Mm, that depends on the implementation of
flatMap
I’d say. In the case of
Validated
it doesn’t exist, and we can only implement it with the same behavior as
Either
which is “give me the value, or short-circuit if you don’t have it”. So in that case, the continuation cannot accumulate because it doesn’t have a value to continue its computation and exists.
m
indeed
s
In the case of
Arb
this isn’t the case though, but rather you extract values from the
Arb
for a given
RandomSource
right?
m
yeah. that’s right. I’ve got
Arb<A>.bind()
implemented as basically flatMaps through
arbitrary { }
syntax. This is going to be available in 5.x. https://github.com/kotest/kotest/blob/master/kotest-property/src/commonMain/kotlin/io/kotest/property/arbitrary/builders.kt#L355-L410
that’s fine and all but right now i’m basically stuck with shrinking, because essentially what i wanted to do is to know all the arbs upfront - i.e. if we see
checkAll(arbA, arbB, arbC) { }
that’s like zip / applicative so it’s trivial to compute shrinks. however with flatmaps, we may have
arbC
dependent on
arbB
, dependent on
arbA
s
Right, I think the only way to do the shrinker is by calculating it on the fly
If that’s even possible 🤔
Oh, but you don’t know which values
arbC
depend on when
bind()
is called on it 🤔
m
halp 😭
s
Let me just share what I have in my head, but no idea if that’s a valid implement 😅
Copy code
fun Arb.Companion.sum(x: Int, y: Int): Int = Arb.constant(x + y)

checkAll {
  val x = <http://Arb.int|Arb.int>().bind()
  val y = Arb.long().bind().toInt()
  val sum = Arb.sum(x, y).bind()
}
At the point of
bind()
you know: 1. We’re binding
<http://Arb.int|Arb.int>()
and we’ve never bound another
Arb
before. We can keep track of the values we’ve seen here, and we know the
IntShrinker
. 2.
Arb.long()
we know that it’s the second
Arb
and we can again keep track of all the values +
LongShrinker
. 3. Custom Arb, we know the values that come by but there is not
Shrinker
. ==> If you want to shrink this prop-test, you want to re-run the lambda by binding all the values for
x
and
y
based on the last values we saw + `IntShrinker`/`LongShrinker`, right? So we have all information available to do that, no?
It’ll require some state-machiney code though 😅
I’m unsure if you can properly get values out of
<http://Arb.int|Arb.int>()
though, it’s based on a
RandomSource
right?
m
haha that’s really interesting! that’s exactly what i have right now - and where i’m currently stuck. The state machine does work, but it has one condition: we need to be able to construct a consistent mapping from an arb to the shrink. i.e.
Map<ArB<*>, RTree<*>>
must exist for all the arbs. On shrinking-mode we can then juxtapose random generated values with shrunk values. i.e.
Copy code
// this works, because these val arbs has a consistent reference
val arbInt = <http://Arb.int|Arb.int>()
val arbLong = Arb.long()
checkAll {
  val x = arbInt.bind()
  val y = arbLong.bind().toInt()
  val sum = Arb.sum(x, y).bind()
}

// this does not work, because each arb occupies different reference
checkAll {
  val x = <http://Arb.int|Arb.int>().bind()
  val y = Arb.long().bind().toInt()
  val sum = Arb.sum(x, y).bind()
}
I really wish there’s a way to define
Equal<Arb<*>>
I’m running out of ideas because essentially it’s a function holder…
s
Right.. I thought that was where you might’ve gotten blocked, because now there is no clear way to compare which
Arb
is which one.
I’m going to think about this, and maybe we can figure something out!
🙌 1
m
As it is not solvable "upstream" with Scala+Cats+for comprehension, it's probably just to accept it won't for Arrow.kt too.