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

Fix fromListWithDef divergence #42

Merged
merged 2 commits into from
Apr 12, 2024
Merged

Fix fromListWithDef divergence #42

merged 2 commits into from
Apr 12, 2024

Conversation

pgujjula
Copy link
Contributor

@pgujjula pgujjula commented Apr 12, 2024

fromListWithDef diverges when passed an infinite list, for the following reasons:

Reason 1:

  • fromListWithDef calls Data.Primitive.Array.fromListN (bits + 1). fromListN requires that the length of the list that is passed to it is exactly bits + 1. To ensure this, it forces the spine of the list that is passed to it.
  • The list that is passed to fromListN is generated by go0, which calls go. When xs is infinite, go k xs will be infinite. Therefore, fromListN will throw an error since go0 yields a list longer than bits + 1.

Even if we take the first bits + 1 elements of go0 before passing to fromListN, there is another problem.

Reason 2:

  • As fromListN forces the spine, it will compute go 0 xs0, go 1 xs1, ..., go 63 xs63, for some xs0, xs1, ..., xs63.

  • This in turn requires computing measureOff (2^0) xs0, measureOff (2^1) x1, ..., measureOff (2^63) x63, which is essentially divergent.

To avoid these problems, we implement fromListWithDef in terms of fromInfinite. Note that the implementation of fromInfinite is similar to the old implementation of fromListWithDef, except that it doesn't call measureOff, which is the source of the divergence.

Updated solution: To avoid these problems, we

  • Add a check in go to break recursion and ensure that the list that is passed to fromListN has length bits + 1.

  • Rearrange how we compute go, so that measureOff is not forced while forcing the spine of go.


This PR depends on PR #41, which should be merged first.

Fixes issue #39

@pgujjula pgujjula marked this pull request as ready for review April 12, 2024 07:31
@pgujjula pgujjula mentioned this pull request Apr 12, 2024
@Bodigrim Bodigrim linked an issue Apr 12, 2024 that may be closed by this pull request
@Bodigrim
Copy link
Owner

I'm not quite sure about this approach. The existing implementation is better for finite lists, because G.replicate is very efficient, much faster than iterating Inf.repeat a :: Infinite a.

Could it be possible to rewrite go to be lazier, not forcing measureOff prematurely? After all, both branches in go return (:)...

@pgujjula
Copy link
Contributor Author

Could it be possible to rewrite go to be lazier, not forcing measureOff prematurely? After all, both branches in go return (:)...

Sounds good, it will take me a little while to figure out. I'll try to finish it by the end of the day.

@pgujjula
Copy link
Contributor Author

pgujjula commented Apr 12, 2024

@Bodigrim I've updated the PR accordingly.

Actually I think I can optimize it a little more. I'll fix it shortly, my apologies.

@pgujjula
Copy link
Contributor Author

@Bodigrim Ok, it's ready now!

Copy link
Owner

@Bodigrim Bodigrim left a comment

Choose a reason for hiding this comment

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

Looks good, could you please add a test?

Left l ->
if l == kk
then (G.replicate kk a, [])
else (G.fromListN kk (xs ++ replicate l a), [])
Copy link
Owner

Choose a reason for hiding this comment

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

I'd probably write it as (if l == kk then ... else ..., []), but up to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I rewrote it that way

fromListWithDef diverges when passed an infinite list, for the following
reasons:

Reason 1:

  * fromListWithDef calls Data.Primitive.Array.fromListN (bits + 1).
    fromListN requires that the length of the list that is passed to it
    is exactly bits + 1. To ensure this, it forces the spine of the list
    that is passed to it.

  * The list that is passed to fromListN is generated by go0, which
    calls go. When xs is infinite, go k xs will be infinite. Therefore,
    fromListN will throw an error since go0 yields a list longer than
    bits + 1.

Even if we take the first bits + 1 elements of go0 before passing to
fromListN, there is another problem.

Reason 2:

  * As fromListN forces the spine, it will compute go 0 xs0, go 1 xs1,
    ..., go 63 xs63, for some xs0, xs1, ..., xs63.

  * This in turn requires computing measureOff (2^0) xs0,
    measureOff (2^1) x1, ..., measureOff (2^63) x63, which is
    essentially divergent.

To avoid these problems, we

  * Add a check in go to break recursion and ensure that the list that
    is passed to fromListN has length (bits + 1).

  * Rearrange how we compute go, so that measureOff is not forced while
    forcing the spine of go.
@pgujjula
Copy link
Contributor Author

I added a test for infinite inputs to fromListWithDef

@Bodigrim Bodigrim merged commit 14581e6 into Bodigrim:master Apr 12, 2024
13 checks passed
@Bodigrim
Copy link
Owner

Thanks a ton!

@Bodigrim
Copy link
Owner

Released in https://hackage.haskell.org/package/chimera-0.4.1.0

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.

fromListWithDef diverges
2 participants