Skip to content

feat(stdlib): add if-let and while-let macros#1543

Open
sqrew wants to merge 4 commits into
carp-lang:masterfrom
sqrew:stdlib-control-flow-ergonomics
Open

feat(stdlib): add if-let and while-let macros#1543
sqrew wants to merge 4 commits into
carp-lang:masterfrom
sqrew:stdlib-control-flow-ergonomics

Conversation

@sqrew
Copy link
Copy Markdown
Contributor

@sqrew sqrew commented Apr 20, 2026

Hey! I noticed that Carp was missing some basic Lisp staples for handling Maybe values more easily, so I've implemented if-let and while-let.

These macros make it much cleaner to unwrap an optional value without needing a full match block every time.

What's new:

  • if-let: Perfect for when you only want to do something if a Maybe is Just.
  • while-let: A handy way to loop through anything that returns a Maybe.
  • Tests: I've added a few tests to test/macros.carp to make sure they're solid.

Disclaimer: This PR was built with the assistance of Gemini (an AI assistant) working in collaboration with @sqrew to improve the Carp standard library ergonomics.

@sqrew
Copy link
Copy Markdown
Contributor Author

sqrew commented Apr 20, 2026

Just realized that perhaps this probably should have gone into controlmacros.carp instead. Oopsies. Thoughts?

@sqrew sqrew force-pushed the stdlib-control-flow-ergonomics branch from 8aad9c0 to be35d05 Compare April 22, 2026 17:39
@sqrew
Copy link
Copy Markdown
Contributor Author

sqrew commented Apr 22, 2026

I went ahead and moved them to Controlmacros.carp and also added when-let as well since we were here anyways

Comment thread core/Macros.carp
(cond-internal xs))

(doc refstr "stringifies `x` and takes the reference of that string.")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we get rid of that empty line change to get the diff a bit cleaner?

Comment thread test/macros.carp Outdated
Comment on lines +404 to +425
(assert-equal test
"found 10"
&(if-let [x (Maybe.Just 10)]
(fmt "found %d" x)
@"not found")
"if-let works with Just")
(assert-equal test
"not found"
&(if-let [x (the (Maybe Int) (Maybe.Nothing))]
(fmt "found %d" x)
@"not found")
"if-let works with Nothing")
(let-do [counter 0
res []]
(while-let [x (if (< counter 3) (Maybe.Just counter) (the (Maybe Int) (Maybe.Nothing)))]
(do
(set! res (Array.push-back res x))
(set! counter (inc counter))))
(assert-equal test
&[0 1 2]
&res
"while-let works as expected"))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this can be removed since you moved the tests also?

Comment thread core/ControlMacros.carp Outdated
(cons 'do (expand (apply ignore* forms))))

(doc if-let "Conditional execution based on a `Maybe` value.
If `expr` evaluates to `(Just x)`, the `then` branch is executed with `var` bound to `x`.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The user can’t see expr and var, so this explanation might be confusing.

Comment thread core/ControlMacros.carp Outdated
(Maybe.Nothing) %else)))

(doc while-let "Looping execution based on a `Maybe` value.
Repeatedly evaluates `expr`. As long as it returns `(Just x)`, the `body` is executed with `var` bound to `x`.")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same as above.

Comment thread core/ControlMacros.carp Outdated
(Maybe.Nothing) (break)))))

(doc when-let "Conditional execution based on a `Maybe` value.
If `expr` evaluates to `(Just x)`, the `body` is executed with `var` bound to `x`.")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same as above.

Comment thread test/macros.carp Outdated
Comment on lines +410 to +415
(assert-equal test
"not found"
&(if-let [x (the (Maybe Int) (Maybe.Nothing))]
(fmt "found %d" x)
@"not found")
"if-let works with Nothing")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was wrong! This one needs to move over.

Copy link
Copy Markdown
Member

@hellerve hellerve left a comment

Choose a reason for hiding this comment

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

Two of my comments are seemingly still unaddressed.

Comment thread core/ControlMacros.carp Outdated
Comment on lines +129 to +130
;; type safety. In Carp, a single match block cannot handle both Maybe.Just
;; and Result.Success constructors simultaneously.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

"a single match block cannot handle both Maybe.Just and Result.Success constructors simultaneously" is tautological, since they're different types, no?

sqrew added 2 commits April 29, 2026 11:46
- Added if-let for conditional execution on Maybe values.
- Added while-let for looping execution on Maybe values.
- Added when-let for simpler conditional unwrapping.
- Moved these macros to core/ControlMacros.carp for better organization.
- Integrated comprehensive tests into test/control_macros.carp.
…and add Result counterparts

- Enhanced if-let, when-let, and while-let to support multiple bindings for Maybe types.\n- Added if-let-ok, when-let-ok, and while-let-ok for Result types with multiple binding support.\n- Improved macro implementation using internal dynamic functions for robust scoping.\n- Added comprehensive test cases in test/control_macros.carp.
@sqrew sqrew force-pushed the stdlib-control-flow-ergonomics branch from 195aafe to 22dfdc9 Compare April 29, 2026 15:47
@sqrew sqrew force-pushed the stdlib-control-flow-ergonomics branch from 22dfdc9 to 8c84e2e Compare April 29, 2026 15:55
@sqrew
Copy link
Copy Markdown
Contributor Author

sqrew commented Apr 29, 2026

oh god guys git is hard and I made a very ugly commit history by accident. sorry. unsure how to make it prettier.

@eriksvedang
Copy link
Copy Markdown
Collaborator

eriksvedang commented May 3, 2026

oh god guys git is hard and I made a very ugly commit history by accident. sorry. unsure how to make it prettier.

that's fine, it'll get squashed

@hellerve
Copy link
Copy Markdown
Member

hellerve commented May 3, 2026

It looks like a bunch of my comments are still relevant here.

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