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

Add Lazy function #156

Closed
wants to merge 4 commits into from
Closed

Add Lazy function #156

wants to merge 4 commits into from

Conversation

markuswustenberg
Copy link
Member

Lazy provides lazy evaluation of node rendering. Useful together with If.

Fixes #152

`Lazy` provides lazy evaluation of node rendering. Useful together with `If`.

Fixes #152
Copy link

codecov bot commented Dec 12, 2023

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (d81de83) 100.00% compared to head (f83f6cd) 100.00%.

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #156   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            9         9           
  Lines          505       519   +14     
=========================================
+ Hits           505       519   +14     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@egeozcan
Copy link

Hi, thanks for asking for feedback.

I like the idea. It's simple (both usage and the implementation), it works. I looked in detail and can't find anything to improve, implementation-wise.

Perhaps docs need to be expanded (with a caution to prefer this when using If). If it weren't a breaking change, I'd even suggest to allow only lazy functions as the second argument to If, but maybe too much of a hassle for medium+ projects using the lib at this point.

@markuswustenberg
Copy link
Member Author

@egeozcan thank you for the quick feedback!

Good idea with the docs, I'll add that to If as well.

I considered a breaking change, but since I'd hate to break existing consumers and If works well for simple cases, I've chosen to keep it. Plus, Lazy can be used in other places where lazy evaluation is needed, for whatever reason. (I explored an IfFunc in #99.)

The only thing I'm not entirely happy with is the variadic NodeType to support attributes. It's kind of a hack, but I can't think of anything better. Alternatives considered:

  • Adding an option struct to the callback. Discarded because: Seems messy for the default case (element type)
  • Making a struct instead of a function. Discarded because: More verbose. This is basically Add Lazy implementation #153.

I'll leave this open for a while to see if anyone has feedback.

@markuswustenberg markuswustenberg added the help wanted Extra attention is needed label Dec 12, 2023
@egeozcan
Copy link

@markuswustenberg you are very much welcome and thank you for such attention to details.

About the variadic arguments & panic hack... Again I witness things getting complicated caused by go allowing neither overloads nor default values. Many choose the option you chose, and while I personally would go for 2 functions (Lazy for elements and LazyAttribute for attributes), it comes down to personal preference. No strong opinions.

And I agree with removing Stringer. It feels out of place.

@markuswustenberg
Copy link
Member Author

@egeozcan thank you for your response. Yeah, I considered two functions as well, but I think I'll go with my current one even though it's a bit more "magic" (in a bad way). Maybe I'll think of something better later.

Thank you again for your input!

@hastebrot
Copy link

hastebrot commented Dec 13, 2023

I wrote this today:

func Iff(condition bool, node func() g.Node) g.Node {
	if condition {
		return node()
	}
	return nil
}

Usage

Iff(props.subtitle != nil, func() g.Node {
	return Span(g.Text(*props.subtitle))
}),

I thought If() might be a problem, since it builds the document tree for all nodes, even when the condition fails, as far as I understand. only the Render() stops at the node.

but Lazy() even looks better than my attempt:

g.If(u != nil, g.Lazy(func() g.Node {
	return g.El("span", g.Text(u.Name))
})),

update: and it was even a previous exploration here: #99

@markuswustenberg
Copy link
Member Author

@hastebrot yeah, your Iff is maybe how I should have implemented it in the first place. But in the end, I guess both have their usage, and I'm hoping I've found a sweet spot with Lazy. 😊

// Used with If, it enables lazy evaluation of the node to return, depending on the condition passed to If.
// It defaults to being an ElementType, but can be set to AttributeType by passing a NodeType.
// Even though nodeType is variadic, only the first NodeType is used.
func Lazy(cb func() Node, nodeType ...NodeType) Node {

Choose a reason for hiding this comment

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

Two thoughts:

  1. This seems to be complicated by implementing the optional nodeTypeDescriber interface, but I've been reading through the code and it seems like the only use of nodeTypeDescriber is to omit the output if the type is wrong here
    if p, ok := c.(nodeTypeDescriber); !ok || p.Type() == ElementType {
    so maybe Lazy can just not implement it without much being lost
  2. If NodeType is important, and there are only two node types, maybe create a LazyAttr function and a LazyEl function?

Copy link
Member Author

Choose a reason for hiding this comment

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

@vikstrous2 thank you for the review! Sorry for the delay in replying, the winter and its various sicknesses has not been kind to me and my family this year… 🤒

Yes, nodeTypeDescriber is only used for checking whether a node should be rendered as an element or attribute. I think it's pretty useful in e.g. form checkboxes and the checked attribute, and it feels wrong to leave out support for attributes, even though it would simplify the API quite a bit here.

LazyAttr would also solve this, arguably more elegantly, but I would really like there to not be two functions for this already slightly edge-case functionality.

Hmm. I'm seriously considering dropping this whole Lazy stuff, it doesn't quite feel right.

@markuswustenberg markuswustenberg mentioned this pull request Jun 6, 2024
@markuswustenberg
Copy link
Member Author

See #172 as a solution instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Inline if with nil pointer
4 participants