##### August 15th, 2025, ca. 22:00 -0400
## Today's Topics: Array `@zip` in C3
> [!info]- Article PGP Signature
>
> To verify this signature, use the public key on the [[Hello, World!|Home Page]] and follow the instructions linked there.
>
> ```
> -----BEGIN PGP SIGNATURE-----
>
> iHUEABYKAB0WIQT2vFo+jLf+FWIQ2T8xrKfG/dldbgUCaJ/2rgAKCRAxrKfG/dld
> bn0kAQDU+0r5iy0Tz9A77EJQl2G0tmGXBdTBtc4ZbQ4SFM31NQEA4jg0vXOylKuB
> lYiXhN6gjjEwQNszcNrWNknxeDtTOQY=
> =kVRt
> -----END PGP SIGNATURE-----
> ```
>
### Today's Contributions
- Continued PR in progress: [c3lang#2370](https://github.com/c3lang/c3c/pull/2370)
### Array Zipping
For today's discussion, I want to write about a change I have pending in the C3 standard library, which is adding some more native functionality to the core arrays library.
#### What do you mean 'zip'?
Obviously we all know what a zipper mechanism is and what it does, but to further illustrate the point of this log entry, I want to drop this in here:
![[zipper.gif]]
Imagine the purple side on the left is the `left` array of elements, and the red side is the `right` array of elements. The two elements, when zipped, still retain their individual colors, but they are bound together into a single span.
When you `@zip` the data in C3, you are either creating a resulting array that contains either `Pair { $typeof(left), $typeof(right) }` or `$typeof(fn $ResultType ($typeof(left), $typeof(right)))` elements, depending on which "flavor" you choose to use.
#### How's that work in-code?
There are many reasons you'd want to zip data, but most of the time it's either...
1. The two elements of the resulting `Pair` are completely *unrelated*, and only need to be correlated as one unit for a *brief function call* or other process, or
2. You want to [apply a function](https://en.wikipedia.org/wiki/Apply) with two parameters, sourcing each parameter from two disparate lists at the same indices.
In non-nerd speak, this means I want to (1) package some data fields together or (2) do some kind of calculation combining two lists.
#### Enough talking, code time!
Here is the function signature and **brief** explanation of each new macro (as of writing; it's been changing a tad). To see the full explanations and reasoning, you should read the [pull request](https://github.com/c3lang/c3c/pull/2370).
> [!faq] Regular `@zip`
>
> ```swift
> <*
> ...
>
> @param [&inout] allocator : "The allocator to use; default is the heap allocator."
> @param [in] left : "The left-side array. These items will be placed as the First in each Pair"
> @param [in] right : "The right-side array. These items will be placed as the Second in each Pair"
> @param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
> @param fill_with : "The value used to fill or pad the shorter iterable to the length of the longer one while zipping."
> ...
> *>
> macro @zip(Allocator allocator, left, right, #operation = EMPTY_MACRO_SLOT, fill_with = EMPTY_MACRO_SLOT) @nodiscard
> ```
> This function looks intimidating, but it's use is not that complicated. Provide two arrays, `left` and `right`, to zip. If that's all you gave, you'll get back a list of tuples containing elements from each position of each array. Easy.
>
> If `fill_with` is not explicitly provided, then the result is the length of the ***shortest*** array; otherwise, the shorter array is _filled_ with `fill_with` up until the length of the ***longer*** array.
>
> Finally, if `#operation` is a valid and conforming function pointer, the function is applied to each input from each array (after padding from `fill_with`, if given). The result is a single array, with a single type (not tuples) derived from the applied function.
> [!faq] No-allocator `@zip_into`
>
> ```swift
> <*
> ...
> @param [inout] left : "Slice to store results of applied functor/operation."
> @param [in] right : "Slice to apply in the functor/operation."
> @param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
> ...
> *>
> macro @zip_into(left, right, #operation)
> ```
> As succinctly as possible: do the same as `@zip`, but store the results of the `#operation` into the `left` slice/array. This is convenient because it doesn't require any memory allocations; however, it can be lacking because (1) it overwrites original data from `left`, and (2) it is limited to the length of `left` _only_.
>
> By the way, the above means that `#operation` **must** return the same type as `$typeof(left)`.
#### Have an example?
I sure do. Here's a simple use of each type, with all parameters being utilized from each signature, so you can grok the full utility and magnificence of `zip`.
> [!example]- `@zip` with a `fill_with` and a function to apply
>
> Zip the elements of `right` into `left` via the function `fn TestStructZip (a, b) => a * b`, where `@operator(*)` _is defined_ as `{ l.a * r.a, l.b * r.b }` for the `TestStructZip` type.
>
> So, for the first iteration, `a = {1, 2}` and `b = {-1, -1}`. The output from the lambda is `{-1, -2}`.
>
> The next iteration is run because, even though `right` doesn't have a value, `fill_with` supplies a *default input* for `b` of `{2, 3}`.
>
> Thus, the next iteration is `a = {300, 400}` and `b = {2, 3}`, which yields and stores `{600, 1200}`. That's where the final value comes from. This defaulting of `b` would keep applying for all remaining elements of `left`, if there were more to match with and consume.
>
> ```cpp
> fn void zip_with_fill_with_struct() => @pool()
> {
> TestStructZip[] left = { {1, 2}, {300, 400} };
> TestStructZip[] right = { {-1, -1} };
>
> TestStructZip[] expected = { {-1, -2}, {600, 1200} };
>
> TestStructZip[] zipped = array::@tzip(left, right, fn TestStructZip (TestStructZip a, TestStructZip b) => a * b, (TestStructZip){2, 3});
>
> test::eq(zipped.len, 2);
> foreach (i, c : zipped) test::@check(c == expected[i], "Mismatch on index %d: %s (actual) != %s (expected)", i, c, expected[i]);
> }
> ```
> [!example]- `@zip` returning `Pair` values
>
> This is the same as the above example, except no function is applied to each parameter, and no `fill_with` value is supplied.
>
> Thus, the result is a simple pairing of each array's elements, up to the length of the ***shorter*** input.
>
> ```cpp
> fn void zip() => @pool()
> {
> char[] left = "abcde";
> long[] right = { -1, 0x8000, 0 };
>
> Pair{char, long}[] expected = { {'a', -1}, {'b', 0x8000}, {'c', 0} };
>
> Pair{char, long}[] zipped = array::@tzip(left, right);
>
> test::eq(zipped.len, 3);
> foreach (i, c : zipped) assert(c == expected[i], "Mismatch on index %d: %s (actual) != %s (expected)", i, c, expected[i]);
> }
> ```
> [!example]- `@zip_into`, simple as
>
> Zip the elements of `right` into `left` via the function `fn (a, b) => a + (char)b.len`.
>
> So, for the first iteration, `a = 1` and `b = "one"`. Running that through the lambda gives: `1 + "one".len`, which equals `4` since the length of the string "one" is `3`.
>
> This is repeated up until the length of the ***shorter*** input array, because a `fill_with` value was _not_ provided, hence the length of the `expected` slice.
>
> ```cpp
> fn void zip_into()
> {
> char[] left = { '1', '2', '3', '4' };
> String[6] right = { "one", "two", "three", "four", "five", "six" };
>
> char[] expected = { '4', '5', '8', '8' };
>
> array::@zip_into(left, right, fn (a, b) => a + (char)b.len);
>
> test::eq(left.len, 4);
> foreach (i, c : left) test::@check(c == expected[i], "Mismatch on index %d: %s (actual) != %s (expected)", i, c, expected[i]);
> }
> ```
### Summary
Wow, what a week! And what a great time I had being able to collect my thoughts at the end of each day. I really hope to continue this journey, especially when I get into what it means to build my OS from scratch.
==Until Monday==, bye!