[Repo Assist] feat: add LazyList.consLazy for constructing from Lazy(LazyList<'T)>#239
Conversation
Implements the enhancement requested in #134. consLazy takes a head value and a Lazy<LazyList<'T>> tail, providing a more efficient alternative to consDelayed when a Lazy value is already available. Example use: let rec ones: LazyList<int> = LazyList.consLazy 1 (lazy ones) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/repo-assist resolve conflicts |
There was a problem hiding this comment.
Pull request overview
Adds a new LazyList.consLazy constructor to build a LazyList<'T> from a head value and an existing Lazy<LazyList<'T>> tail, aligning the public API with the underlying representation discussed in #134.
Changes:
- Add
LazyList.consLazyimplementation inLazyList.fs. - Expose
consLazyin the public signature fileLazyList.fsiwith documentation. - Add new unit tests covering basic
consLazybehavior and an infinite-list scenario.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
src/FSharpx.Collections/LazyList.fs |
Introduces the new consLazy constructor implementation. |
src/FSharpx.Collections/LazyList.fsi |
Exposes consLazy publicly and updates signature formatting in this file. |
tests/FSharpx.Collections.Tests/LazyListTests.fs |
Adds tests for consLazy behavior (head, toList, divergence-on-construction, infinite list). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| ///O(1). Test if a list is empty. Forces the evaluation of | ||
| /// the first element of the stream if it is not already evaluated. | ||
| member IsEmpty : bool | ||
| member IsEmpty: bool | ||
|
|
||
| ///O(1). Return the first element of the list. Forces the evaluation of | ||
| /// the first cell of the list if it is not already evaluated. | ||
| member Head : 'T | ||
| member Head: 'T | ||
|
|
||
| ///O(n). Return the length of the list | ||
| member Length : unit -> int | ||
| member Length: unit -> int | ||
|
|
|
|
||
| val (|Cons|Nil|) : LazyList<'T> -> Choice<('T * LazyList<'T>),unit> | ||
|
|
||
| val (|Cons|Nil|): LazyList<'T> -> Choice<('T * LazyList<'T>), unit> |
src/FSharpx.Collections/LazyList.fs
Outdated
| lzy(fun () -> (consc x (lzy(fun () -> (force(l())))))) | ||
|
|
||
| let consLazy x (l: Lazy<LazyList<'T>>) = | ||
| lzy(fun () -> consc x l.Value) |
| // tail is not evaluated unless the tail is consumed | ||
| Expect.isTrue "consLazy divergence" (let _ = LazyList.consLazy 1 (lazy (failwith "diverge")) in true) |
|
@gdziadkiewicz I've opened a new pull request, #255, to work on those changes. Once the pull request is ready, I'll request review from you. |
There was a problem hiding this comment.
Pull request overview
This PR adds a new LazyList.consLazy API to construct a LazyList<'T> from a head value and an existing Lazy<LazyList<'T>>, addressing the allocation/caching drawbacks of wrapping Lazy via consDelayed.
Changes:
- Add
LazyList.consLazyimplementation inLazyList.fs. - Expose
consLazyinLazyList.fsiwith documentation. - Add unit tests covering basic behavior and an infinite-list usage pattern.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/FSharpx.Collections/LazyList.fs |
Implements consLazy construction logic. |
src/FSharpx.Collections/LazyList.fsi |
Adds public signature + doc comment for consLazy. |
tests/FSharpx.Collections.Tests/LazyListTests.fs |
Adds tests for consLazy behavior (head/toList/laziness/infinite). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
src/FSharpx.Collections/LazyList.fs
Outdated
| lzy(fun () -> (consc x (lzy(fun () -> (force(l())))))) | ||
|
|
||
| let consLazy x (l: Lazy<LazyList<'T>>) = | ||
| lzy(fun () -> consc x l.Value) |
|
|
||
| test "consLazy lazy divergence" { | ||
| // tail is not evaluated unless the tail is consumed | ||
| Expect.isTrue "consLazy divergence" (let _ = LazyList.consLazy 1 (lazy (failwith "diverge")) in true) |
Previously consLazy wrapped the entire cell construction in lzy, meaning forcing the head cell (e.g. via LazyList.head) also forced l.Value — defeating the laziness guarantee of consLazy. Fix: use notlazy for the outer CellCons so the head is immediately available, wrapping only the tail evaluation in lzy. Test: replace the failwith divergence test with an explicit mutable flag asserting that neither construction nor head access forces the tail. Incorporates fix from PR #255. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
🤖 This PR was created by Repo Assist, an automated AI assistant.
Implements the enhancement requested in #134.
Summary
LazyList.consDelayedacceptsunit -> LazyList<'T>(a thunk). When aLazy(LazyList<'T)>value is already available, wrapping it infun () -> tail.Valueworks but is unnecessarily wasteful — it allocates an extra closure and forgoes .NET's built-inLazycaching.consLazyprovides the direct construction:Usage example (infinite list of 1s, as shown in #134):
Implementation
The outer
lzykeeps the head cell lazy until the list is consumed.l.Valueis the standard .NETLazy(T)evaluation, which caches the result and is evaluated at most once.Changes
src/FSharpx.Collections/LazyList.fs— addconsLazysrc/FSharpx.Collections/LazyList.fsi— add signature + doc commenttests/FSharpx.Collections.Tests/LazyListTests.fs— 4 new tests: head access,toList, lazy divergence check (tail not evaluated on construction), and infinite listTest Status
✅ All 710 tests pass (
Passed: 710, Skipped: 6, Failed: 0). The 6 skipped tests are pre-existing skips unrelated to this change.Related to #134