Skip to content

Conversation

dizinfector
Copy link
Contributor

No description provided.

Copy link
Contributor

@kyri-petrou kyri-petrou left a comment

Choose a reason for hiding this comment

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

Great job! Just some super minor comments but otherwise looks good to me!

PS: I also benchmarked this approach against the default toString implementation and it's ~3x faster. Well done!

/**
* Stack-safe toString for Cause
*/
def causeToString[E](cause: Cause[E]): String = {
Copy link
Contributor

Choose a reason for hiding this comment

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

WDUT about adding this within the abstract class and make it final private (and removing the cause: Cause[E] argument ofc)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The idea moving the method to companion Object was to avoid using Cause successors within abstract class. IMO it's more correct. But in fact successors are already mentioned within Cause so let me move it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

dizinfector and others added 2 commits May 12, 2025 16:38
Co-authored-by: kyri-petrou <67301607+kyri-petrou@users.noreply.github.com>
@dizinfector dizinfector requested a review from kyri-petrou May 13, 2025 15:01
if (visitStack.isEmpty) {
results.pop()
} else {
visitStack.pop() match {
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can actually avoid the either entirely by just matching on Function1 or Cause - though I am not if it matters since these seem unlikely to retain.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

visitRecursive()
}

final override def toString = causeToString
Copy link
Collaborator

Choose a reason for hiding this comment

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

@kyri-petrou What do you think about avoiding the stack allocations if we just have Die or Fail?

Copy link
Contributor

Choose a reason for hiding this comment

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

Good point (also for Empty and Interrupt)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've remade it: moved the function to trait and applied it to all "composite" causes

visitRecursive()
}

final override def toString = causeToString
Copy link
Contributor

Choose a reason for hiding this comment

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

Good point (also for Empty and Interrupt)

visitStack.push(Left(() => results.push(s"Stackless(${results.pop()},$stackless)")), Right(cause))
case Die(value, trace) =>
results.push(s"Die($value,$trace)")
case Empty =>
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I missed this previously. Avoid using case Empty because it uses the equals method of Cause which can be expensive. This is much more performant as it relies on type-checking alone:

Suggested change
case Empty =>
case _: Empty.type =>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Remade all checks

/**
* A Cause that contains one or more sub-causes
*/
sealed trait CompositeCause[+E] { self: Cause[E] =>
Copy link
Collaborator

Choose a reason for hiding this comment

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

the problem with this approach is that users can now match on this - increasing the API we have to support for binary compatibility. I think we should unfortunately write:

def tostring = self match {
  case _: Empty => "Empty"
  case _:Die | _:Fail _ => super.toString // not sure if this one is correct
  case default => compositToString

@kyri-petrou what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think super.toString will work because the compiler implements the toString method of case classes, it's not due to some super class.

In either way, I think we should go with the simplest solution possible. The toString method of Cause shouldn't be getting used normally (it's not used in logging, only in ZIO#debug which shouldn't be used in production code).

I know I was initially on-board with this change, but if this optimization requires code duplication we can revert the last commit and take the allocation hit because it should only really be used during development / debugging.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@hearnadam if you agree I can make as propsed: revert last commit and apply only change that avoids using Either in stack

Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we make the trait private[Cause]? that should be okay?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@dizinfector dizinfector requested a review from hearnadam May 22, 2025 13:47
@dizinfector
Copy link
Contributor Author

@kyri-petrou could you please review/merge PR?

Copy link
Contributor

@kyri-petrou kyri-petrou left a comment

Choose a reason for hiding this comment

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

Thanks!

@kyri-petrou kyri-petrou merged commit ee67258 into zio:series/2.x May 26, 2025
18 checks passed
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

Successfully merging this pull request may close these issues.

3 participants