DEV Community: stdlib The latest articles on DEV Community by stdlib (@stdlib). https://dev.to/stdlib https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F7413%2F7a02665c-f45a-48f3-a587-67c3d34300ca.png DEV Community: stdlib https://dev.to/stdlib en Connect with the stdlib community on Zulip Athan Wed, 17 Dec 2025 05:45:04 +0000 https://dev.to/stdlib/connect-with-the-stdlib-community-on-zulip-487m https://dev.to/stdlib/connect-with-the-stdlib-community-on-zulip-487m <p>As the stdlib community continues to grow and evolve, so has our need for new ways to connect and collaborate (see, for example, our <a href="proxy.php?url=https://blog.stdlib.io/new-ways-to-engage-with-the-stdlib-community" rel="noopener noreferrer">announcement of office hours and a public events calendar</a>). While Gitter's simple, single-channel interface worked well in the early days, it no longer scales with the range of conversations happening around the project. Today we're excited to announce our new <a href="proxy.php?url=https://stdlib.zulipchat.com" rel="noopener noreferrer">Zulip chat</a>, which provides a more full-featured, structured, and searchable space for us to interact.</p> <h2> Why Zulip? </h2> <p><a href="proxy.php?url=https://zulip.com" rel="noopener noreferrer">Zulip</a> is open source and generously <a href="proxy.php?url=https://zulip.com/for/open-source" rel="noopener noreferrer">supports open-source projects</a> like ours with a free cloud plan. Its channel-and-topic model makes it easier to keep discussions focused, follow ongoing threads, and resurface past knowledge through powerful <a href="proxy.php?url=https://zulip.com/help/search-for-messages#search-filters" rel="noopener noreferrer">search features</a>. </p> <p>Anyone can browse the web-public channels of <a href="proxy.php?url=https://stdlib.zulipchat.com" rel="noopener noreferrer">stdlib's Zulip</a> without an account, and you can sign up at any time to join the conversation.</p> <h2> Join and get started </h2> <p>The <a href="proxy.php?url=https://stdlib.zulipchat.com" rel="noopener noreferrer"><strong>stdlib Zulip chat</strong></a> is open to all. A welcome bot will greet you when you first join and share some tips specific to stdlib about how to participate effectively. If you're new to Zulip, their <a href="proxy.php?url=https://zulip.com/help/getting-started-with-zulip" rel="noopener noreferrer">getting started guide</a> is an invaluable resource. If you're already familiar with applications such as Slack or Discord, much of the experience will be familiar.</p> <p>We encourage you to come say hello in the <a href="proxy.php?url=https://stdlib.zulipchat.com/#narrow/channel/546733-introductions" rel="noopener noreferrer"><strong>#introductions</strong></a> channel and take some time to explore other channels and topics that may be of interest to you. If you have any questions about Zulip itself, we've got a channel for that too (<a href="proxy.php?url=https://stdlib.zulipchat.com/#narrow/channel/546662-zulip" rel="noopener noreferrer"><strong>#zulip</strong></a>).</p> <p>The stdlib team is active in the chat, and public messages are the best way to get timely help—no need for routine <strong>@-mentions</strong>. Asking questions in public is the fastest way to get a response, as more people can help, <em>plus</em> it's likely that someone else will benefit from finding out the answer to your question. The stdlib <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/blob/develop/CODE_OF_CONDUCT.md" rel="noopener noreferrer">Code of Conduct</a> applies to all community spaces, including stdlib's Zulip. Should you encounter an issue, Zulip's <a href="proxy.php?url=https://zulip.com/help/report-a-message" rel="noopener noreferrer">reporting tools</a> and our moderation team are available.</p> <h2> See you there! </h2> <p>We're looking forward to seeing you in the stdlib Zulip instance! We welcome questions and suggestions as we continue shaping a space that is useful, inclusive, and genuinely supportive for everyone who wants to learn, build, or contribute.</p> <p><a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> is an open source software project dedicated to providing a comprehensive suite of robust, high-performance libraries to accelerate your project's development and give you peace of mind knowing that you're depending on expertly crafted, high-quality software.</p> <p>If you've enjoyed this post, give us a star 🌟 on <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">GitHub</a> and consider <a href="proxy.php?url=https://opencollective.com/stdlib" rel="noopener noreferrer">financially supporting</a> the project. Your contributions and continued support help ensure the project's long-term success and are greatly appreciated!</p> community news tooling Using AI in the development of stdlib Philipp Burckhardt Thu, 17 Jul 2025 19:18:41 +0000 https://dev.to/stdlib/using-ai-in-the-development-of-stdlib-48aa https://dev.to/stdlib/using-ai-in-the-development-of-stdlib-48aa <blockquote> <p>Feeling fast, but working slow? A reflection on stdlib's participation in the recent METR study on AI's impact on open-source developer productivity.</p> </blockquote> <p>I read the results of the recent METR study on <a href="proxy.php?url=https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/" rel="noopener noreferrer">"Impact of Early-2025 AI on Experienced Open-Source Developer Productivity"</a> with great interest for two reasons. Firstly, I have been an early adopter of LLM tools. In 2020, I was lucky enough to get access to the private beta of the OpenAI API from then CTO Greg Brockman and explored the use of AI for education at Carnegie Mellon University. Secondly, because <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> participated in the METR study, I was personally involved and contributed by working on randomized <a href="proxy.php?url=https://github.com/stdlib-js/metr-issue-tracker" rel="noopener noreferrer">issues</a> over several months, being allowed to use AI for some tasks and forbidden for others.</p> <p>Given that <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a>'s involvement is central to my perspective, it's worth providing some context on the project. stdlib is a comprehensive open-source standard library for JavaScript and Node.js, with a specific and ambitious goal: to be the fundamental library for numerical and scientific computing on the web. It is a large-scale project with well over 5 million source lines of JavaScript, C, Fortran, and WebAssembly, and composed of thousands of independently consumable packages, bringing the rigor of high-performance mathematics, statistics, and machine learning to the JavaScript ecosystem. Think of it as a foundational layer for data-intensive applications similar to the roles NumPy and SciPy serve in the Python ecosystem. In short, stdlib isn't your average JavaScript project.</p> <h2> A Word of Thanks </h2> <p>Before diving into my reflection, I want to take the opportunity to thank the METR team and especially Nate Rush for giving stdlib the chance to participate in this study with two core stdlib developers, <a href="proxy.php?url=https://github.com/headlessNode" rel="noopener noreferrer">Muhammad Haris</a> and <a href="proxy.php?url=https://github.com/Planeshifter" rel="noopener noreferrer">myself</a>. It was a great experience to work with the METR team, and I am eager to see any future studies they will conduct. It is my conviction that, with the entire tech industry being gripped by an AI gold rush, it is incredibly valuable to have a non-profit research institute like METR conduct studies that cut through the noise with actual data.</p> <h2> The Slowdown </h2> <p>The results of the METR study are surprising, clashing with some previously published and very optimistic study results on the impact of generative AI (e.g., see GitHub and Accenture's <a href="proxy.php?url=https://github.blog/news-insights/research/research-quantifying-github-copilots-impact-in-the-enterprise-with-accenture/#:~:text=Since%20bringing%20GitHub%20Copilot%20to,world%2C%20large%20engineering%20organizations" rel="noopener noreferrer">2023 study on the impact of Copilot on developer productivity</a>). Citing from the Core Result section of the <a href="proxy.php?url=https://METR.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/" rel="noopener noreferrer">METR study page</a>:</p> <blockquote> <p>When developers are allowed to use AI tools, they take 19% longer to complete issues—a significant slowdown that goes against developer beliefs and expert forecasts. This gap between perception and reality is striking: developers expected AI to speed them up by 24%, and even after experiencing the slowdown, they still believed AI had sped them up by 20%.</p> </blockquote> <p>Rather predictably, the results have led to a lot of discussion on <a href="proxy.php?url=https://news.ycombinator.com/item?id=44522772" rel="noopener noreferrer">Hacker News</a> and other social channels, with parties on both sides lining up with their pitchforks.</p> <h2> The Perception Gap </h2> <p>I am part of the group of developers who estimated that they were sped up 20%-30% during the study's exit interview. While I like to believe that my productivity didn't suffer while using AI for my tasks, it's not unlikely that it might not have helped me as much as I anticipated or maybe even hampered my efforts.</p> <p>But how can that be? Daily, we are reading about how AI is already revolutionizing the workplace or making software engineers redundant, with companies like Salesforce <a href="proxy.php?url=https://www.theregister.com/2025/02/27/salesforce_misses_revenue_guidance/" rel="noopener noreferrer">announcing</a> that they won't be hiring for software engineering positions anymore or online lender Klarna <a href="proxy.php?url=https://www.forbes.com/sites/quickerbettertech/2024/03/13/klarnas-new-ai-tool-does-the-work-of-700-customer-service-reps/" rel="noopener noreferrer">announcing</a> that they were shuttering their entire human customer support in favor of AI.</p> <p>Many of these stories have turned out to be more hyperbole than reality. Klarna <a href="proxy.php?url=https://www.independent.co.uk/news/business/klarna-ceo-sebastian-siemiatkowski-ai-job-cuts-hiring-b2755580.html" rel="noopener noreferrer">still has</a> human support, and Salesforce still has many engineering <a href="proxy.php?url=https://careers.salesforce.com/en/jobs/?search=engineer&amp;pagesize=20#results" rel="noopener noreferrer">job listings</a>. Sadly, some of these stories appear influenced by ulterior motives, such as Klarna's strategic positioning as an "AI-native" company to capture premium valuations ahead of its IPO amid the current AI wave.</p> <p>However, I have been using AI tools daily for the past three years, both at work and outside, and find them immensely useful. How do I square these benefits with the study results?</p> <h2> On Study Design </h2> <p>When confronted with results that go counter to one's expectations, it is a natural instinct to try to attack the study and identify holes to explain away the result. For example, one could point to the small sample size of 16 developers. There is also the argument that the study was conducted in a very specific context, with experienced developers working on projects they are intimately familiar with. </p> <p>There might also have been a subtle selection effect in the tasks themselves: since project maintainers proposed their own task lists, it is possible that those more experienced with AI subconsciously selected issues they believed were more amenable to an agentic workflow. One could also argue that the developers were subject to the <a href="proxy.php?url=https://en.wikipedia.org/wiki/Hawthorne_effect" rel="noopener noreferrer">Hawthorne effect</a>, altering their behavior simply because they knew they were being video-recorded, perhaps over-relying on the AI tools for the sake of the experiment. </p> <p>Finally, and perhaps most importantly, the experimental setup of requiring screen recordings and active time tracking for a single task enforced a synchronous workflow. This effectively locked developers into what I call "supervision mode", where they had to watch the agent work rather than being free to context-switch to another problem.</p> <p>Some of these critiques, particularly the enforced "supervision" workflow, could directly contribute to the observed slowdown. But others, such as selecting "AI-friendly" tasks or over-relying on the tool to impress researchers, should have biased the results toward a speedup. This makes the final outcome even more notable. The direction of various potential biases is ambiguous at best, which is why we must look at the study's core design.</p> <p>As a randomized control trial, the study follows the gold standard experimental design for detecting causality. By randomizing individual tasks to "AI-allowed" or "AI-disallowed", the study isolates the effect of AI tooling. Instead of comparing one group of developers against a control group (where differences in skill could skew the results), it compares each developer against themselves. This "within-subjects" design controls for individual characteristics, from typing speed to experience with the project. With such a study design, results are harder to write off as mere statistical noise, even with a smaller sample size.</p> <p>Crucially, the tasks were defined before this randomization. This avoids a common pitfall where AI might simply produce more verbose code or encourage developers to break tasks into smaller pull requests, which can inflate some productivity metrics without representing more work getting done. </p> <p>16 developers from several open-source projects might not sound like much, but, in total, we completed 246 tasks. To give a sense of the work <a href="proxy.php?url=https://github.com/stdlib-js/metr-issue-tracker/issues?q=sort%3Aupdated-desc%20is%3Aissue" rel="noopener noreferrer">involved</a>, the tasks Haris and I worked on were not trivial, while still being hand scoped to be completed in a few hours or less. They were a mix of core feature development (such as adding new array, string, and BLAS functions), creating custom ESLint rules to enforce project-specific coding standards, enhancing our CI/CD pipelines with new automation, and fixing bugs from our issue tracker.</p> <p>And while a single developer's performance on one task is likely correlated with their performance on another and the precision of the estimates thus larger than otherwise, it is quite notable that the effect was in the opposite direction from what economists, ML experts, and the developers themselves predicted (with the former two groups being more in the range of a 40% speedup). Moreover, the effect is quite large in magnitude. A quick back-of-the-envelope calculation reveals that if the true effect were a 40% speedup, the probability of observing a result this far in the opposite direction is astronomically low. </p> <p>In light of this, I have no reason to doubt the internal validity of the study and would venture that the effect measured is real within the context of the experiment. If one believed the chatter on social media and the hype merchants who two years ago were all shilling cryptocurrency (and maybe still are!) but have meanwhile all switched over to extolling the amazing speedup AI offers, then increases of 100%, 5x, or even 10x should have been in the cards. But this is definitively not what the study observed.</p> <h2> Embracing Agentic Development </h2> <p>The more important consideration for squaring my own experience with these results is external validity: how generalizable are the study's findings? The paper is a great read and touches on many possible criticisms and threats to external validity, and I won't belabor any of the points raised therein.</p> <p>Instead, I will solely focus on my experience as a study participant and how I have been leveraging AI with success. I will also share my own hypotheses for why the performance of the developers in this sample was overall negatively affected by the use of AI. </p> <p>To give some context, my main way of incorporating LLMs into my work before participating in this study was twofold. As something of an early adopter, I had used GitHub Copilot for auto-completion and inline suggestions and made heavy use of ChatGPT and Anthropic Claude web apps by assembling relevant context, writing detailed prompts, and copying results back into my editor. Tools such as <a href="proxy.php?url=https://repomix.com/" rel="noopener noreferrer">Repomix</a> helped streamline the process of incorporating LLMs into my daily development workflow. This general approach allowed me to review changes quickly, iterate on them by asking questions, and have the LLM make follow-up edits directly in a chat interface.</p> <p>The METR study subsequently provided an excuse for me to delve into agentic programming and make Cursor an integral part of my workflow. I had used it briefly some time before but didn't find the AI-generated results compelling enough to let it loose on any codebase I was working on. But Claude Sonnet 3.7 had come out, which is still one of the most powerful models for coding tasks. Due to some very encouraging results during early testing, I was eager to put it to work on a backlog of tooling that we wanted to build for stdlib, alongside various refactoring and bug fixes.</p> <p>One of my first impressions with Cursor this time around was the underlying LLM's rather impressive ability to follow the very specific coding standards and conventions of the project and, when placed in agent mode, to automatically and reliably fix lint errors and attempt to iteratively resolve errors in unit tests. This felt like another step change in capabilities, just like when OpenAI released GPT-3 Davinci in June 2020, which made a lot of use cases suddenly feasible that before would break down in any realistic scenario.</p> <p>While I no longer use Cursor and have meanwhile switched to Claude Code (more on that later), I found Cursor straightforward to use, especially given that it is a fork of VSCode, which has been my IDE of choice for many years. I heavily doubt that inexperience with Cursor, which I shared with roughly a half of the developers in the study, played a major role in the results. While I didn't have an extensive <code>.cursorrules</code> setup (which has since been deprecated in favor of <a href="proxy.php?url=https://docs.cursor.com/context/rules#project-rules" rel="noopener noreferrer">project rules</a>), I did add basic instructions and context about the project and made sure to index the stdlib codebase. Aside from that, further customization was neither possible nor necessary, as the Cursor Agent was able to automatically pull in other files, look up function call signatures, and perform other operations for assembling context.</p> <p>My experience of Cursor was largely positive during the study. As an example, I ended up working on several Bash scripts for our CI/CD pipeline, and Cursor definitely sped up my development workflow by not having to look up the man page of <code>jq</code> for the eleventh time given that I only use this command-line tool for manipulating JSON once in a blue moon. With the AI agent's help, I could quickly generate a function like this one to check if a GitHub issue has a specific label:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="c"># Check if an issue has the "Tracking Issue" label.</span> <span class="c">#</span> <span class="c"># $1 - Issue number</span> is_tracking_issue<span class="o">()</span> <span class="o">{</span> <span class="nb">local </span><span class="nv">issue_number</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="nb">local </span>response debug_log <span class="s2">"Checking if issue #</span><span class="k">${</span><span class="nv">issue_number</span><span class="k">}</span><span class="s2"> is a tracking issue"</span> <span class="c"># Get the issue:</span> <span class="k">if</span> <span class="o">!</span> <span class="nv">response</span><span class="o">=</span><span class="si">$(</span>github_api <span class="s2">"GET"</span> <span class="s2">"/repos/</span><span class="k">${</span><span class="nv">repo_owner</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">repo_name</span><span class="k">}</span><span class="s2">/issues/</span><span class="k">${</span><span class="nv">issue_number</span><span class="k">}</span><span class="s2">"</span><span class="si">)</span><span class="p">;</span> <span class="k">then </span><span class="nb">echo</span> <span class="s2">"Warning: Failed to fetch issue #</span><span class="k">${</span><span class="nv">issue_number</span><span class="k">}</span><span class="s2">"</span> <span class="o">&gt;</span>&amp;2 <span class="k">return </span>1 <span class="k">fi</span> <span class="c"># ...</span> <span class="c"># Check if the issue has the "Tracking Issue" label:</span> <span class="k">if </span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$response</span><span class="s2">"</span> | jq <span class="nt">-r</span> <span class="s1">'.labels[].name'</span> 2&gt;/dev/null | <span class="nb">grep</span> <span class="nt">-q</span> <span class="s2">"Tracking Issue"</span><span class="p">;</span> <span class="k">then </span>debug_log <span class="s2">"Issue #</span><span class="k">${</span><span class="nv">issue_number</span><span class="k">}</span><span class="s2"> is a tracking issue"</span> <span class="k">return </span>0 <span class="k">else </span>debug_log <span class="s2">"Issue #</span><span class="k">${</span><span class="nv">issue_number</span><span class="k">}</span><span class="s2"> is not a tracking issue"</span> <span class="k">return </span>1 <span class="k">fi</span> <span class="o">}</span> </code></pre> </div> <p>The agent correctly assembled the <code>jq -r '.labels[].name'</code> filter to extract the label names from the JSON response—something that would have sent me to a documentation page for a few minutes. While a small speed bump, these moments add up. The AI handled the rote task of recalling obscure syntax, letting me focus on the actual logic.</p> <p>My first takeaway is this: current LLMs are very powerful for tasks in domains that you are not intimately familiar with, allowing you to move much more quickly. Agentic tools such as Cursor and Claude Code are also very helpful to quickly navigate and learn your way around a large codebase, allowing you to ask questions and explore the codebase in a natural way. Leveraging "deep research" provides another means to more exhaustively explore a problem space in a way that the search engines of old simply cannot match.</p> <p>On the other hand, some tasks were very frustrating. For example, the Cursor agent wrote one ESLint rule almost fully in one shot, but for another one, the Cursor agent was running in circles and unable to figure out the correct algorithm. Trying to prompt it to fix the bug was unsuccessful multiple times. It would have been better to not fall prey to the <a href="proxy.php?url=https://en.wikipedia.org/wiki/Sunk_cost" rel="noopener noreferrer">sunk cost fallacy</a> and instead throw away the code and then either give the agent another shot or write it myself.</p> <blockquote> <p>Cursor does have a neat feature of breakpoints which allow you to stop the agent at any time and revert to a prior state, something I wholeheartedly recommend using. It is a great way to avoid getting stuck in a loop of the agent trying to fix a bug that it cannot figure out.</p> </blockquote> <p>I freely admit that I may have been a bit overeager about using AI for all of the AI-enabled tasks, partly due to my desire to learn to use Cursor productively but also due to my general amazement of what these new technologies unlock. However, maybe the METR study suggests that the question of whether a task can be more efficiently completed by AI, or whether one would be better off completing it by hand, is far from settled.</p> <h2> The Blank Slate Problem </h2> <p>Aside from occasional inefficiencies and outright mistakes in the generated code, coding agents do not have access to all the implicit knowledge and conventions of a large, mature project, which often might not be written down. In <a href="proxy.php?url=https://johnwhiles.com/posts/mental-models-vs-ai-tools" rel="noopener noreferrer">his reflections</a> on the study, John Whiles identifies a core conflict: an expert engineer's primary value isn't just writing code; it's holding a complete, evolving mental model of the entire system in their head. The agent does not have such a mental model. Every interaction starts from a blank slate.</p> <p>It is possible that some of this can be mitigated with better, more targeted instructions. As usual, there is no free lunch. One has to actively invest in making one's codebase more accessible to coding agents. And more generally, memory and learning is an unsolved problem with transformer-based LLMs, and changing that will likely require fundamental architectural advancements. </p> <p>The necessity of auditing the agent's code for mistakes created two major sources of friction: the cognitive drain of 'babysitting' the AI and the time spent waiting for and reviewing its output. For every minute the agent spent running in circles on that ESLint rule, I was blocked, my attention monopolized by the need to supervise its flawed process. This synchronous, blocking workflow is exhausting and inefficient. It's the digital equivalent of shoulder-surfing an overconfident junior developer who has memorized everything there is to know about programming but cannot be trusted and who will make subtle mistakes that are hard to spot.</p> <p>My advice: stay in the driver's seat during such pair programming and use the AI as a sparring partner to bounce ideas back and forth instead of yielding agency.</p> <h2> Delegate, Don't Supervise </h2> <p>Partly based on my experiences in the study, my workflow has evolved, and I have subsequently switched to using Anthropic's Claude Code. This has changed my interaction model from synchronous supervision to asynchronous delegation. I can now define a complex task via Claude Code's planning mode and then have the agent work on the task in the background. I can then turn my full attention elsewhere, be it attending a meeting, reviewing a colleague's code, or simply thinking through the next problem without interruption. Claude's work happens in parallel and is not a blocker to my own. The cognitive cost of babysitting is replaced by the much lower cost of reviewing a completed proposal later; if it didn't work out, I might just throw away the code and have the model try again, instead of engaging in a fruitless back and forth.</p> <p>Claude Sonnet 4 and Opus 4 were not released at the time the METR study was conducted, and, while they mark another improvement, especially with regard to tool use by the model, the dynamics haven't fundamentally changed. The models still make mistakes and do not always implement things in an optimal or sound way, but they are now much better at following instructions and can work uninterrupted for longer periods of time.</p> <p>At least for me, in contrast to those who frame coding agents as mere "stochastic parrots", I find myself absolutely amazed that, despite its warts and hiccups, we have now a technology that, given a set of instructions, is able to generate a fully-formed pull request that correctly implements logic, adheres to style guidelines, and has a passing test suite. And, in the best cases, this can happen without any human intervention.</p> <h2> The First 80 Percent </h2> <p>We still need to reconcile the observed performance decrease with how many developers, including myself, have now been leveraging AI to get tasks done in a fraction of the time, tasks that would have taken them hours or days previously. I believe that the <a href="proxy.php?url=https://en.wikipedia.org/wiki/Pareto_principle" rel="noopener noreferrer">Pareto Principle</a> is a helpful yardstick. Named after Italian economist Vilfredo Pareto, it is commonly referred to as the 80/20 rule and posits that roughly 80% of effects come from 20% of the causes. Coding agents can now generate working code that mostly works but that might fall short if the goal is 100%.</p> <p>In many instances, coding agents can easily accomplish the first 80% of a programming task, generating boilerplate, scaffolding logic, implementing core functionality, and writing a test suite. However, the final 20% of the task, from handling tricky edge cases, adhering to unwritten architectural conventions, ensuring optimal performance, and avoiding code duplication by reusing existing utilities is where the complexity lies. This last mile still requires the developer's deep, stateful mental model of the project. The rub here is that, by using the AI agent, one may bypass all the little steps which are necessary in the process of building that mental model.</p> <p>But does it matter? When working on a crucial piece of a larger, complex system, it definitely does, and I would be hesitant with generative AI. But when working on a well-defined, isolated piece of code with expected behavior for inputs and outputs, why bother? The marginal cost of writing code (long recognized as only a small part of software engineering) is going to zero. In the event that there is a problem with the code, it can simply be thrown away and rewritten. The code that AI agents now generate is of decent quality, well-documented, and capable of adhering to one's coding conventions.</p> <p>This brings to mind the following quote by <a href="proxy.php?url=https://tidyfirst.substack.com/p/90-of-my-skills-are-now-worth-0" rel="noopener noreferrer">Kent Beck</a>.</p> <blockquote> <p>The value of 90% of my skills just dropped to $0. The leverage for the remaining 10% went up 1000x. I need to recalibrate.</p> </blockquote> <p>AI as a force multiplier is why I am long on AI, even though the METR study is a good reminder that we all can easily fall prey to cognitive biases. </p> <p>In <a href="proxy.php?url=https://en.wikipedia.org/wiki/Thinking,_Fast_and_Slow" rel="noopener noreferrer"><em>Thinking, Fast and Slow</em></a>, Daniel Kahneman gives a classic example for biases driven by the <a href="proxy.php?url=https://en.wikipedia.org/wiki/Availability_heuristic" rel="noopener noreferrer">availability heuristic</a>: people overestimate plane crash risks due to vivid media coverage, making such events more "available" to memory than statistically riskier, yet routine, car crashes. Our judgment is swayed not by data, but by the ease of recall. In the case of working with AI agents, observing them build fully-functioning tools in seconds is a very memorable and visceral experience. On the other hand, the slow, frustrating "death by a thousand cuts" experience of auditing, debugging, and correcting the AI's subtle mistakes is the equivalent of the mundane car crash. It's a distributed cost with no single dramatic moment.</p> <p>Nevertheless, I have no reason to believe that this technology will not continue to improve, and I, for one, am excited about the possibilities. For any big and ambitious project, the amount of tickets to be completed, features to be implemented, and bugs to fix vastly outstrips the available amount of time and human bandwidth to work on them.</p> <h2> What Future Studies Should Tell Us </h2> <p>It remains to be seen whether the results of the METR study can be replicated. However, the study clearly demonstrated that experts and developers were overly optimistic about the impact of AI on productivity. This is an important insight that should inform future research. </p> <p>In some ways, the study raises more new questions than it answers. It looked at a very particular situation: seasoned experts working in the familiar territory of their own large, mature projects. Future studies by METR and others could vary these conditions. What happens when we throw developers into unfamiliar codebases, where, at least per my anecdotal experience, AI agents shine? Or what about junior developers or new contributors to an established open-source codebase? Under what conditions can AI act as a great equalizer, compressing the skill gap and providing a speed boost rather than slowdown?</p> <p>Furthermore, the current study centered on completion time, but faster isn't always better. One possible follow-up would be a blinded study where human experts review pull requests without knowing if AI was involved. We could then measure things like the number of review cycles, the time spent in review, and the long-term maintainability of the code. This might shed light on when and how AI-assisted development may impact trading short-term speed for long-term technical debt. </p> <p>Finally, the field of AI is still evolving at a rapid pace. The synchronous workflow that the study's setup encouraged could be fundamentally suboptimal. Exploring different interaction models, such as the asynchronous delegation workflow that I've moved to, could yield very different results.</p> <h2> How to Work With AI Now </h2> <p>What follows are my current recommendations for using AI in your daily workflow based on my experiences and the METR study.</p> <h3> Adopt an Asynchronous Workflow </h3> <p>The biggest drain from using AI is the cognitive load of "babysitting" it. Instead of watching the agent work, adopt an asynchronous model:</p> <ul> <li> Define one or more tasks (e.g., running a set of commands to audit a codebase for lint errors and documentation mistakes) and then let AI agents work on them in the background (e.g., in separate Git worktrees of your repository), and turn your attention elsewhere.</li> <li> Review the completed task(s) later. If the output is flawed, it's often better to discard it and have the model try again with a better prompt rather than engaging in a frustrating back-and-forth.</li> </ul> <h3> Know What to Delegate </h3> <p>AI can now handle the first 80% of many programming tasks, but the final 20% often requires deep context. The key is to choose the right tasks for AI:</p> <ul> <li> <strong>"Vibe Code" and Prototypes:</strong> use AI for mock-ups or small, isolated tools that can be thrown away. This is where the technology's speed offers a distinct advantage. </li> <li> <strong>Verifiable Code:</strong> AI is excellent for tasks that can be fully verified against an existing, robust test suite. The tests act as a safety net to catch the subtle mistakes the AI might make. </li> <li> <strong>Boilerplate Code:</strong> AI can quickly generate boilerplate code, such as REST API endpoints or form validation, and can do so in a way that follows project conventions.</li> <li> <strong>Learning and Navigation:</strong> use AI to quickly learn your way around a large codebase, document previously undocumented code, or to get help with tools you use infrequently. Asking LLMs questions can be much faster than hunting through documentation, particularly if that documentation is split across multiple resources.</li> </ul> <h3> Use and Customize Claude Code </h3> <p>For tools such as Claude Code, customization is a helpful means of writing down any implicit knowledge about the project that is not readily accessible from the code alone.</p> <ul> <li> <strong>Provide Proper Context:</strong> drag and drop relevant files (this can include images!) into the Claude Code window for the model to use as context for the task at hand. One approach I have found useful is to add TODO comments in the codebase with the required changes, and then have Claude Code work on them. Use the planning mode to have the model think through the task and generate a plan that can be approved before immediately jumping into implementation.</li> <li> <strong>Use Project Memory:</strong> use <code>CLAUDE.md</code> files to give the model project-specific <a href="proxy.php?url=https://docs.anthropic.com/en/docs/claude-code/memory#how-claude-looks-up-memories" rel="noopener noreferrer">memory</a>, specifically on its architecture and unwritten knowledge. You can have multiple <code>CLAUDE.md</code> files in different project sub-directories, and the model will intelligently pick up the most relevant one based on your current context.</li> <li> <p><strong>Automate Repetitive Actions:</strong> create <a href="proxy.php?url=https://docs.anthropic.com/en/docs/claude-code/slash-commands#custom-slash-commands" rel="noopener noreferrer">custom slash commands</a> for frequent tasks performing routine work. Below is an example <code>stdlib:review-changed-packages</code> command that I run to flag any possible errors in PRs that were recently merged to our development branch:<br> </p> <pre class="highlight markdown"><code><span class="p">-</span> Pull down the latest changes from the develop branch of the stdlib repository. <span class="p">-</span> Get all commits from the past $ARGUMENTS day(s) that were merged to the develop branch <span class="p">-</span> Extract a list of @stdlib packages touched by those commits <span class="p">-</span> Review the packages for any typos, bugs, violations of the stdlib style guidelines, or inconsistencies introduced by the changes. <span class="p">-</span> Fix any issues found during the review. </code></pre> </li> <li><p><strong>Build Custom Tooling:</strong> use the Claude CLI to build small, automated tools, such as a review bot that flags typos as a daily CRON job. For fuzzy tasks such as pointing out typos or inconsistencies in a PR, it's best to let Claude generate output that can be verified by a human. For well-defined tasks that can be fully automated, it is better to have Claude produce code that deterministically runs and can be verified.</p></li> <li><p><strong>Set up Hooks to Automate Actions:</strong> <a href="proxy.php?url=https://docs.anthropic.com/en/docs/claude-code/hooks" rel="noopener noreferrer">hooks</a> are a powerful new feature of Claude Code that allows you to run scripts and commands at different points in Claude's agentic lifecycle.</p></li> </ul> <h2> Final Thoughts </h2> <p>It's natural to attack a study whose results you don't like. A better response is to ask what they might be telling you. For me, it tells me there is still a lot to learn about how to use this new, powerful, but often deeply weird and unpredictable technology. One mistake is treating it as the driver in a pair programming session that requires your constant attention. Instead, treat it like a batch process for grunt work, freeing you to focus on the problems that actually require a human brain.</p> <p><a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> is an open source software project dedicated to providing a comprehensive suite of robust, high-performance libraries to accelerate your project's development and give you peace of mind knowing that you're depending on expertly crafted, high-quality software.</p> <p>If you've enjoyed this post, give us a star 🌟 on <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">GitHub</a> and consider <a href="proxy.php?url=https://opencollective.com/stdlib" rel="noopener noreferrer">financially supporting</a> the project. Your contributions and continued support help ensure the project's long-term success and are greatly appreciated!</p> webdev programming javascript ai GSoC 2025 Projects Announced Philipp Burckhardt Fri, 09 May 2025 02:30:04 +0000 https://dev.to/stdlib/gsoc-2025-projects-announced-53al https://dev.to/stdlib/gsoc-2025-projects-announced-53al <p>Today, we are grateful to announce that stdlib, the fundamental numerical library for JavaScript, was awarded five slots in this year's Google's Summer of Code (GSoC). We participated in the program last year for the first time, and had four talented students working on a variety of projects. It was a resounding success, which we hope to surpass this year given all <a href="proxy.php?url=https://blog.stdlib.io/reflecting-on-gsoc-2024/" rel="noopener noreferrer">that we have learned</a> over the past year and a half.</p> <p>This achievement comes after a tremendously productive start to 2025. Since January 1st of this year, the stdlib community has:</p> <ul> <li> Opened two thousand PRs with 1,377 successfully merged.</li> <li> Welcomed contributions from 88 different contributors.</li> <li> Added 3,452 commits to the repository.</li> </ul> <p>For GSoC, we received 99 excellent applications from enthusiastic students. Ranking proposals was a tough decision, and we would have loved for a few more projects to be accepted. We are grateful to everyone who applied and encourage those not selected this year to stay connected, continue to contribute to the project, and to apply again next year! In fact, one of this year's accepted contributors was a repeat applicant, demonstrating how persistence and continued engagement can pay off.</p> <p>The accepted projects are listed below. Each project addresses key areas that will expand JavaScript's potential for technical and scientific applications.</p> <p><a href="proxy.php?url=https://summerofcode.withgoogle.com/programs/2025/projects/opJzlQTz" rel="noopener noreferrer"><strong>Add LAPACK bindings and implementations for linear algebra</strong></a><br> <strong>Contributor:</strong> <a href="proxy.php?url=https://github.com/aayush0325" rel="noopener noreferrer">Aayush Khanna</a></p> <p>The goal of Aayush's project is to develop JavaScript and C implementations of LAPACK (<strong>L</strong>inear <strong>A</strong>lgebra <strong>Pack</strong>age) routines. This project aims to extend conventional LAPACK APIs by borrowing ideas from BLIS, thus ensuring easy compatibility with stdlib ndarrays and adding support for both row-major (C-style) and column-major (Fortran-style) storage layouts. This work will help overcome the LAPACK's column-major limitation and thus make advanced linear algebra operations more accessible and efficient in JavaScript environments.</p> <p><a href="proxy.php?url=https://summerofcode.withgoogle.com/programs/2025/projects/JYSuqCBs" rel="noopener noreferrer"><strong>Expanding array-based statistical computation in stdlib</strong></a><br> <strong>Contributor:</strong> <a href="proxy.php?url=https://github.com/gururaj1512" rel="noopener noreferrer">Gururaj Gurram</a></p> <p>Gururaj will advance statistical operations in stdlib by introducing convenience array wrappers for all existing strided APIs, thus improving developer ergonomics for common use cases. Additionally, he will develop specialized ndarray statistical kernels with the aim of facilitating efficient statistical reductions across multi-dimensional data.</p> <p><a href="proxy.php?url=https://summerofcode.withgoogle.com/programs/2025/projects/Td3c9qv2" rel="noopener noreferrer"><strong>Implement base special mathematical functions in JavaScript and C</strong></a><br> <strong>Contributor:</strong> <a href="proxy.php?url=https://github.com/anandkaranubc" rel="noopener noreferrer">Karan Anand</a></p> <p>Karan will implement and enhance lower-level scalar kernels for special mathematical functions in stdlib. The goal is to complete missing C implementations for existing double-precision packages, develop new single-precision versions, and ensure consistency, accuracy, and IEEE 754 compliance. These enhancements will provide developers with the most comprehensive set of high-precision mathematical tools for scientific computing in JavaScript.</p> <p><a href="proxy.php?url=https://summerofcode.withgoogle.com/programs/2025/projects/lKDCoGBz" rel="noopener noreferrer"><strong>Achieve ndarray API parity with built-in JavaScript arrays</strong></a><br> <strong>Contributor:</strong> <a href="proxy.php?url=https://github.com/headlessNode" rel="noopener noreferrer">Muhammad Haris</a></p> <p>Haris will extend stdlib's ndarray capabilities by implementing familiar JavaScript array methods like <code>concat</code>, <code>find</code>, <code>flat</code>, <code>includes</code>, <code>indexOf</code>, <code>reduce</code>, and <code>sort</code> for multi-dimensional arrays. The project will develop high-performance C implementations with Node.js native add-ons for compute-intensive operations. These enhancements will allow JavaScript developers to work with multi-dimensional arrays as easily as built-in arrays, significantly expanding JavaScript's capabilities for scientific and numerical computing.</p> <p><a href="proxy.php?url=https://summerofcode.withgoogle.com/programs/2025/projects/NJC5LuLO" rel="noopener noreferrer"><strong>Add BLAS bindings and implementations for linear algebra</strong></a><br> <strong>Contributor:</strong> <a href="proxy.php?url=https://github.com/ShabiShett07" rel="noopener noreferrer">Shabareesh Shetty</a></p> <p>Shabareesh will expand stdlib's BLAS (<strong>B</strong>asic <strong>L</strong>inear <strong>A</strong>lgebra <strong>S</strong>ubprograms) support by implementing missing Level 2 (vector-matrix) and Level 3 (matrix-matrix) operations in JavaScript, C, Fortran, and WebAssembly. The project will focus on key dependencies for LAPACK routines and create performance-optimized APIs that work in both browser and server environments. These enhancements will provide essential building blocks for developing high-performance machine learning and statistical analysis applications on the web.</p> <p>We're excited to see these projects develop over the coming months. Each contribution will significantly enhance stdlib's capabilities and make advanced mathematical and statistical operations more accessible to the JavaScript community. The work done by these talented contributors will help bridge the gap between traditional scientific computing environments and JavaScript, furthering our mission to create a comprehensive, high-performance standard library for JavaScript.</p> <p>We'd like to extend thanks to Google for their continued support of open-source development through the Summer of Code program, and we look forward to sharing updates as the above projects progress over the course of this summer. In addition to watching for more posts on this blog, you can follow development by joining our <a href="proxy.php?url=https://app.gitter.im/#/room/#stdlib-js_stdlib:gitter.im" rel="noopener noreferrer">community chat</a>. We also hold regular <a href="proxy.php?url=https://github.com/stdlib-js/meetings/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20label%3A%22Office%20Hours%22" rel="noopener noreferrer">office hours</a> over video conferencing, which is a great opportunity to ask questions, share ideas, and engage directly with the stdlib team.</p> <p>We hope that you'll join us in our mission to advance cutting-edge scientific computation in JavaScript. Start by showing your support and starring the project on GitHub today: <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">https://github.com/stdlib-js/stdlib</a>.</p> <p><a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> is an open source software project dedicated to providing a comprehensive suite of robust, high-performance libraries to accelerate your project's development and give you peace of mind knowing that you're depending on expertly crafted, high-quality software.</p> <p>If you've enjoyed this post, give us a star 🌟 on <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">GitHub</a> and consider <a href="proxy.php?url=https://opencollective.com/stdlib" rel="noopener noreferrer">financially supporting</a> the project. Your contributions and continued support help ensure the project's long-term success and are greatly appreciated!</p> programming javascript beginners Google Summer of Code 2025 Athan Thu, 27 Feb 2025 18:22:14 +0000 https://dev.to/stdlib/google-summer-of-code-2025-13kh https://dev.to/stdlib/google-summer-of-code-2025-13kh <blockquote> <p>We're thrilled to announce that stdlib was accepted as a Google Summer of Code mentoring organization for 2025!</p> </blockquote> <p>We are beyond excited to share that <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> has once again been accepted as a mentoring organization for <a href="proxy.php?url=https://summerofcode.withgoogle.com" rel="noopener noreferrer">Google Summer of Code</a> 2025! This marks our second consecutive year participating in this incredible program, and we cannot wait to work alongside aspiring open source contributors to push the boundaries of scientific computing on the web.</p> <p>Google Summer of Code (GSoC) is a global initiative that introduces new contributors to open source software by offering mentorship and funding for meaningful, long-term projects. Over the years, GSoC has been instrumental in helping open source projects like stdlib grow, while also giving participants valuable real-world software development experience. With our acceptance into GSoC 2025, we are looking forward to welcoming a new wave of enthusiastic contributors who share our vision of making JavaScript and the extended ecosystem of TypeScript, Node.js, Deno, and other JavaScript runtimes first-class environments for numerical and scientific computing.</p> <h3> Reflecting on GSoC 2024: A Year of Growth </h3> <p>Last year marked our first time participating in GSoC, and we could not have asked for a better experience. We had the privilege of mentoring four incredibly talented contributors, each of whom made substantial contributions to the stdlib ecosystem.</p> <p>From integrating BLAS bindings and optimizing special mathematical functions to enhancing support for boolean arrays and improving our interactive REPL experience, their work strengthened the foundation of stdlib and paved the way for even greater advancements. Beyond just code, their contributions sparked deeper engagement within our community, leading to over <strong>2,000 pull requests from more than 100 contributors</strong> and <strong>3,000+ new commits</strong> to <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> since February 2024.</p> <p>If you missed our retrospective on last year's program, be sure to check out our blog post: <a href="proxy.php?url=https://blog.stdlib.io/reflecting-on-gsoc-2024/" rel="noopener noreferrer">Reflecting on GSoC 2024</a>.</p> <h3> What's in Store for GSoC 2025? </h3> <p>As we gear up for GSoC 2025, we have a range of exciting project ideas that we hope will inspire potential contributors. Whether you're passionate about numerical computing, statistical modeling, performance optimization, or developer tooling, there's something for you. Some areas we're particularly excited about include:</p> <ul> <li> <strong>BLAS/LAPACK</strong>: continuing to expand stdlib's coverage of BLAS and LAPACK operations to provide a robust foundation for linear algebra and machine learning in JavaScript and Node.js.</li> <li> <strong>WebAssembly</strong>: compiling BLAS and statistical kernels to WebAssembly with support for ergonomic inter-operation between WebAssembly and JavaScript.</li> <li> <strong>ndarray kernels</strong>: implementing lower-level ndarray kernels for efficient element-wise iteration and reduction to improve performance.</li> <li> <strong>Improving developer tooling</strong>: improving the stdlib development experience by creating better tools for automation, publishing, and managing the stdlib package ecosystem.</li> <li> <strong>Expanding statistical distributions</strong>: building on previous efforts to provide C implementations for special mathematical functions, thus unlocking a wider range of probability distributions and making stdlib a comparable alternative to SciPy for statistical computing in JavaScript.</li> </ul> <p>These ideas, however, are just the beginning. We believe that innovation comes from collaboration, and we welcome fresh ideas from prospective contributors. If you have a project concept that aligns with our mission and a clear plan for execution, we would love to hear about it. Our current list of ideas is available on our GSoC <a href="proxy.php?url=https://github.com/stdlib-js/google-summer-of-code/blob/main/ideas.md" rel="noopener noreferrer">repository</a>, but don't feel constrained by it—great ideas come from all directions!</p> <h3> How to Get Involved </h3> <p>If you're interested in contributing to stdlib for GSoC 2025, now is the perfect time to get started. Here's how you can begin your journey:</p> <ol> <li> <strong>Explore stdlib</strong>: familiarize yourself with the project by browsing the project's <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">GitHub repository</a> and reading our documentation.</li> <li> <strong>Join the conversation</strong>: engage with the stdlib community on <a href="proxy.php?url=https://gitter.im/stdlib-js/stdlib" rel="noopener noreferrer">Element</a> to discuss project ideas, ask questions, and connect with mentors.</li> <li> <strong>Review our guidelines</strong>: carefully read our <a href="proxy.php?url=https://github.com/stdlib-js/google-summer-of-code/tree/main" rel="noopener noreferrer">GSoC Application Guidelines</a> to understand what we're looking for in a proposal.</li> <li> <strong>Start contributing</strong>: we strongly encourage all applicants to contribute to stdlib before submitting their application. This can be in the form of a bug fix, new feature, performance improvement, or some other enhancement to stdlib's capabilities.</li> </ol> <p>The official GSoC timeline is as follows:</p> <ul> <li> <strong>February 27 – March 24</strong>: prospective contributors discuss project ideas with mentoring organizations.</li> <li> <strong>March 24 – April 8</strong>: application period (final deadline: April 8 at 18:00 UTC).</li> <li> <strong>May 8</strong>: accepted proposals announced.</li> <li> <strong>May 8 – June 1</strong>: community bonding period.</li> <li> <strong>June 2 – September 1</strong>: standard 12-week coding period.</li> </ul> <p>For the full timeline, visit the <a href="proxy.php?url=https://developers.google.com/open-source/gsoc/timeline" rel="noopener noreferrer">GSoC 2025 Timeline</a>.</p> <h3> Looking Ahead </h3> <p>As we embark on another exciting GSoC season, we want to extend our deepest gratitude to Google for this opportunity. We are incredibly excited to meet new contributors, explore new ideas, and continue building an open source ecosystem where JavaScript thrives as a language for scientific computing.</p> <p>If you're passionate about building high-quality software and eager to make an impact, we invite you to join us. We can't wait to see your ideas and begin working together to advance scientific computing in JavaScript. Let's make this year's GSoC program one to remember!</p> <p><a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> is an open source software project dedicated to providing a comprehensive suite of robust, high-performance libraries to accelerate your project's development and give you peace of mind knowing that you're depending on expertly crafted, high-quality software.</p> <p>If you've enjoyed this post, give us a star 🌟 on <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">GitHub</a> and consider <a href="proxy.php?url=https://opencollective.com/stdlib" rel="noopener noreferrer">financially supporting</a> the project. Your contributions and continued support help ensure the project's long-term success and are greatly appreciated!</p> webdev programming javascript node New ways to engage with the stdlib community! Athan Tue, 14 Jan 2025 00:47:48 +0000 https://dev.to/stdlib/new-ways-to-engage-with-the-stdlib-community-3in4 https://dev.to/stdlib/new-ways-to-engage-with-the-stdlib-community-3in4 <p>Fostering a vibrant and inclusive community is crucial for ensuring the long-term success of open-source software, and stdlib is no exception. We believe that collaboration and open communication are key to driving innovation and making scientific computing on the web accessible to everyone. To that end, we're thrilled to announce two new initiatives designed to make it easier than ever for contributors, users, and maintainers to connect, collaborate, and grow together!</p> <h2> Weekly Office Hours </h2> <p>As part of our efforts to enhance transparency and collaboration, we're proud to announce weekly office hours! We've been running these informally for the past few months, and they've been a wonderful success, providing high-bandwidth opportunities to connect with project maintainers, users, and new and existing stdlib contributors.</p> <p>To facilitate the coordination of office hours and other public project meetings, we've created a public GitHub <a href="proxy.php?url=https://github.com/stdlib-js/meetings" rel="noopener noreferrer">repository</a> to serve as a centralized hub where community members can propose agenda topics, review discussion points, and participate in shaping the direction of stdlib. Each week in advance of the next office hours, we'll create a new dedicated agenda <a href="proxy.php?url=https://github.com/stdlib-js/meetings/issues?q=sort%3Aupdated-desc+state%3Aopen+label%3A%22Office+Hours%22" rel="noopener noreferrer">issue</a>, where you can link issues and pull requests you want to discuss, post questions in advance, and share any pre-reads. Thus far, agendas have run the gamut, from project overviews to live code reviews to discussions about the project roadmap to upcoming events and community announcements. </p> <p>In short, if you have questions about stdlib or if you need help fixing a bug, figuring out what to do next, or are just looking for feedback, this is your time to shine! Please join our weekly office hours to connect with project maintainers, stay updated on the latest project news, and chat with other community members. This is a great opportunity to ask questions, share ideas, and engage directly with the stdlib team.</p> <p>Everyone is welcome—drop in and say hello!</p> <h2> Public Community Calendar </h2> <p>Second, we're excited to introduce our new public community <a href="proxy.php?url=https://calendar.google.com/calendar/u/0/embed?src=a72677fe2820c833714b8b9a2aa87393f742bcaf0d0f6c9499eee6661795eae0@group.calendar.google.com" rel="noopener noreferrer">calendar</a>, where you can stay up-to-date with all stdlib events, including office hours, project orientations, development meetings, and other important happenings.</p> <p>With this <a href="proxy.php?url=https://calendar.google.com/calendar/u/0/embed?src=a72677fe2820c833714b8b9a2aa87393f742bcaf0d0f6c9499eee6661795eae0@group.calendar.google.com" rel="noopener noreferrer">calendar</a>, you can:</p> <ul> <li> Find the dates and times of upcoming office hours and meetings.</li> <li> Add our events to your own calendar for easy reminders.</li> <li> Stay informed about new opportunities to engage with the stdlib team and community.</li> </ul> <h2> How You Can Get Involved </h2> <p>Here are a few ways you can make the most of these new resources:</p> <ul> <li> <strong>Bookmark the <a href="proxy.php?url=https://calendar.google.com/calendar/u/0/embed?src=a72677fe2820c833714b8b9a2aa87393f742bcaf0d0f6c9499eee6661795eae0@group.calendar.google.com" rel="noopener noreferrer">community calendar</a> or add it to your own.</strong> Be on the lookout for upcoming events, and mark your calendar to join us.</li> <li> <strong>Engage on GitHub.</strong> Visit our meetings <a href="proxy.php?url=https://github.com/stdlib-js/meetings" rel="noopener noreferrer">repository</a> to propose agenda topics or contribute to ongoing discussions.</li> <li> <strong>Attend Office Hours.</strong> Whether you're stuck on a problem or curious about the latest project updates, office hours are an excellent opportunity to connect and learn.</li> <li> <strong>Spread the Word.</strong> Help us grow the stdlib community by sharing these updates with anyone who might be interested.</li> </ul> <h2> Let's Build Together! </h2> <p>We're committed to creating a supportive and inspiring environment for everyone in the scientific computing ecosystem, and we're excited to see how these new initiatives will help our community thrive. Needless to say, we can't wait to connect with you at our next office hours!</p> <p>Together, we're building the future of scientific computing on the web! 🚀</p> <p><a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> is an open source software project dedicated to providing a comprehensive suite of robust, high-performance libraries to accelerate your project's development and give you peace of mind knowing that you're depending on expertly crafted, high-quality software.</p> <p>If you've enjoyed this post, give us a star 🌟 on <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">GitHub</a> and consider <a href="proxy.php?url=https://opencollective.com/stdlib" rel="noopener noreferrer">financially supporting</a> the project. Your contributions and continued support help ensure the project's long-term success and are greatly appreciated!</p> webdev javascript programming node 2024 Retrospective Athan Sat, 04 Jan 2025 21:16:29 +0000 https://dev.to/stdlib/2024-retrospective-pip https://dev.to/stdlib/2024-retrospective-pip <blockquote> <p>A look back at 2024 and a preview of the year ahead.</p> </blockquote> <p>2024 was a <strong>landmark year</strong> for <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a>, packed with progress, innovation, and community growth. Looking back, I am struck by the amount of time and effort members of the stdlib community spent refining existing APIs, crafting new functionality, and laying the groundwork for an exciting road ahead. I feel incredibly fortunate to be part of a community that is actively shaping the future of scientific computing on the web, and I am bullish on our continued success in the months to come.</p> <p>In this post, I'll provide a recap of some key highlights and foreshadow what's in store for 2025. While I'll be making various shoutouts to individual contributors, none of what we accomplished this year could have happened without the entire stdlib community. The community was instrumental in doing the hard work necessary to make stdlib a success, from finding and patching bugs to reviewing pull requests and triaging issues to diving deep into the weeds of numerical algorithms and software design. If I don't mention you by name, please be sure to know that your efforts are recognized and greatly appreciated. A big thank you to everyone involved and to everyone who's helped out along the way, in ways both big and small. ❤️</p> <h2> TL;DR </h2> <p>This past year was transformative for stdlib, marked by significant growth, innovation, and community contributions. Some key highlights include:</p> <ul> <li> <strong>Community Growth</strong>: 84 new contributors joined stdlib, tripling the size of our developer community and driving over 4,000 commits, 2,200 pull requests, and the release of 500+ new packages.</li> <li> <strong>Google Summer of Code</strong>: four exceptional contributors helped advance critical projects, including enhanced REPL capabilities, expanded BLAS support, and new mathematical APIs.</li> <li> <strong>Enhanced Developer Tools</strong>: major strides in automation included automated changelog generation, improved CI workflows, and better test coverage tracking.</li> <li> <strong>Technical Milestones</strong>: significant progress was made in linear algebra (BLAS and LAPACK), fancy indexing, WebAssembly integrations, and C implementations of mathematical functions, all aimed at making JavaScript a first-class language for scientific computing.</li> <li> <strong>Future Vision</strong>: looking ahead to 2025, we aim to expand our math libraries, improve REPL interactivity, explore WebGPU, and continue building tools to make scientific computing on the web more powerful and accessible.</li> </ul> <p>With stdlib’s rapid growth and the collective efforts of our global community, we're shaping the future of scientific computing on the web. Join us as we take the next steps in this exciting journey!</p> <h2> Stats </h2> <p>To kick things off, some high-level year-end statistics. This year,</p> <ul> <li> <strong>84</strong> new contributors from across the world joined stdlib, <strong>tripling</strong> our developer community size and bringing new life and fresh perspectives to the project.</li> <li>Together, we made over <strong>4000 commits</strong> to the main development branch.</li> <li>We opened nearly <strong>2200 pull requests</strong>, with over 1600 of those pull requests merged.</li> <li>And we shipped over <strong>500 new packages</strong> in the project, ranging from new linear algebra routines to specialized math functions to foundational infrastructure for multi-dimensional arrays to APIs supporting WebAssembly and other accelerated environments.</li> </ul> <p>These accomplishments reflect the hard work and dedication of our community. It was a busy year, and we were forced to think critically about how we can effectively scale the project and our community as both continue to grow. This meant investing in tooling and automation, improving our review and release processes, and figuring out ways to quickly identify and upskill new contributors.</p> <h2> Google Summer of Code </h2> <p>The one event which really set things in motion for stdlib in 2024 was our <a href="proxy.php?url=https://summerofcode.withgoogle.com/programs/2024/organizations/stdlib" rel="noopener noreferrer">acceptance</a> into Google Summer of Code (GSoC). We had previously applied in 2023, but were rejected. So when we applied in 2024, we didn't think we had much of a chance. Much to our surprise and delight, stdlib was accepted, thus setting off a mad dash to get our affairs together so that we could handle the influx of contributors to come. </p> <p>GSoC ended up being a transformative experience for stdlib, bringing in talented contributors and pushing forward critical projects. As we detailed in our GSoC <a href="proxy.php?url=https://blog.stdlib.io/reflecting-on-gsoc-2024/" rel="noopener noreferrer">reflection</a>, the road was bumpy, but we learned a lot and came out the other side. Needless to say, we were extremely lucky to have four truly excellent GSoC contributors: <a href="proxy.php?url=https://github.com/orgs/stdlib-js/people/aman-095" rel="noopener noreferrer">Aman Bhansali</a>, <a href="proxy.php?url=https://github.com/orgs/stdlib-js/people/gunjjoshi" rel="noopener noreferrer">Gunj Joshi</a>, <a href="proxy.php?url=https://github.com/orgs/stdlib-js/people/Jaysukh-409" rel="noopener noreferrer">Jaysukh Makvana</a>, and <a href="proxy.php?url=https://github.com/orgs/stdlib-js/people/Snehil-Shah" rel="noopener noreferrer">Snehil Shah</a>. I'll have a bit more to say about their work in the sections below.</p> <h2> REPL </h2> <p>The Node.js read-eval-print loop (REPL) is often something of an afterthought in the JavaScript world, both underutilized and underappreciated. From stdlib's earliest days, we wanted to create a better <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/repl" rel="noopener noreferrer">REPL</a> experience, with integrated support for stdlib's scientific computing and data processing functionality. Development of the stdlib REPL has come in fits and starts, but there's always been a goal of matching the power and feature set of Python's IPython in order to facilitate interactive exploratory data analysis in JavaScript. We were thus quite excited when <a href="proxy.php?url=https://github.com/orgs/stdlib-js/people/Snehil-Shah" rel="noopener noreferrer">Snehil Shah</a> expressed interest in working on the stdlib REPL as part of GSoC.</p> <p>Snehil already covered some of his work in a previous blog post on <a href="proxy.php?url=https://blog.stdlib.io/welcoming-colors-to-the-repl/" rel="noopener noreferrer">"Welcoming colors to the REPL!"</a>, but his and others' work covered so much more. A few highlights:</p> <ul> <li> <strong>Preview completions</strong>: when typing characters matching a known symbol in the REPL, a completion preview is now displayed, helping facilitate auto-completion and saving developers precious keystrokes. Shoutout to <a href="proxy.php?url=https://github.com/tudor-pagu" rel="noopener noreferrer">Tudor Pagu</a>, in particular, for adding this!</li> <li> <strong>Multi-line editing</strong>: prior to adding support for multi-line editing, the REPL supported multi-line inputs, but did not support editing previously entered lines, often leading to a frustrating user experience. Now, the REPL supports multi-line editing within the terminal similar to dedicated editor applications.</li> <li> <strong>Pagination of long outputs</strong>: a longstanding feature request has been to add support for something like <code>less</code>/<code>more</code> to the stdlib REPL. Previously, if a command generated a long output, a user could be confronted with a wall of text. This has now been addressed, with the hope of adding more advanced <code>less</code>-like search functionality in the months ahead.</li> <li> <strong>Bracketed-paste</strong>: pasting multi-line input into the REPL used to execute the input line-by-line, instead of pasting it as a single prompt. While useful in some cases, this is often not the desired intent, especially when a user wishes to paste and edit multi-line input before execution.</li> <li> <strong>Custom syntax-highlighting themes</strong>: developers who are used to developing in IDEs can often feel adrift when moving to a terminal lacking some of the niceties of their favorite editor. One of those niceties is syntax-highlighting. Accordingly, we worked to add support for custom theming, as detailed in Snehil's <a href="proxy.php?url=https://blog.stdlib.io/welcoming-colors-to-the-repl/" rel="noopener noreferrer">blog post</a>.</li> <li> <strong>Auto-pairing</strong>: another common IDE nicety is the automatic closing of brackets and quotation marks, helping save keystrokes and mitigate the dreaded missing bracket. Never one to shy away from a difficult task, Snehil implemented support for auto-pairing as one of his first <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/pull/1680" rel="noopener noreferrer">pull requests</a> leading up to GSoC.</li> </ul> <p>Largely thanks to Snehil's work, we moved much closer to IPython parity in 2024, thus transforming the JavaScript experience for scientific computing. And we're not done yet. We still have pull requests working their way through the queue, and one thing I am particularly excited about is that we've recently started exploring adding support for the Jupyter protocol. Stay tuned for additional REPL news in 2025!</p> <h2> BLAS </h2> <p>Another area of focus has been the continued development of stdlib's <a href="proxy.php?url=https://netlib.org/blas/" rel="noopener noreferrer">BLAS</a> (<strong>B</strong>asic <strong>L</strong>inear <strong>A</strong>lgebra <strong>S</strong>ubprograms) support, which provides fundamental APIs for common linear algebra operations, such as vector addition, scalar multiplication, dot products, linear combinations, and matrix multiplication. Coming into 2024, BLAS support in stdlib was rather incomplete, particularly in terms of its support for complex-valued floating-point data types. The tide began to change with <a href="proxy.php?url=https://github.com/orgs/stdlib-js/people/Jaysukh-409" rel="noopener noreferrer">Jaysukh Makvana</a>'s efforts to achieve feature parity of stdlib's <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/array/complex64" rel="noopener noreferrer"><code>Complex64Array</code></a> and <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/array/complex128" rel="noopener noreferrer"><code>Complex128Array</code></a> data structures with built-in JavaScript typed arrays.</p> <p>These efforts subsequently paved the way for adding Level 1 BLAS support for complex-valued typed array data types and the work of <a href="proxy.php?url=https://github.com/orgs/stdlib-js/people/aman-095" rel="noopener noreferrer">Aman Bhansali</a>, who set out to further Level 2 and Level 3 BLAS support in stdlib. After focusing initially on lower-level BLAS strided array interfaces, Aman expanded his scope by adding WebAssembly implementations and by adding support for applying BLAS operations to stacks of matrices and vectors via higher-level multi-dimensional array (a.k.a., <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/ndarray/ctor" rel="noopener noreferrer"><code>ndarray</code></a>) APIs.</p> <p>In addition to conventional BLAS routines, stdlib includes <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/blas/ext/base" rel="noopener noreferrer">BLAS-like routines</a> which are not a part of <a href="proxy.php?url=https://netlib.org/blas/" rel="noopener noreferrer">reference BLAS</a>. These routines include APIs for alternative scalar and cumulative summation algorithms, sorting strided arrays, filling and manipulating strided array elements, explicit handling of <code>NaN</code> values, and other operations which don't fall neatly under the banner of linear algebra, but are common when working with data.</p> <p>During Aman's BLAS work, we cleaned up and refactored BLAS implementations, and <a href="proxy.php?url=https://github.com/headlessNode" rel="noopener noreferrer">Muhammad Haris</a> volunteered to extend those efforts to our <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/blas/ext/base" rel="noopener noreferrer">extended BLAS</a> routines. His efforts entailed migrating Node.js native add-ons to pure C in order to reduce boilerplate and leverage our extensive collection of C <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/napi" rel="noopener noreferrer">macros</a> for authoring of native add-ons and further entailed adding dedicated C APIs to facilitate interfacing with stdlib's <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/ndarray/ctor" rel="noopener noreferrer"><code>ndarrays</code></a>.</p> <p>These developments ensure that stdlib continues to lead the way in linear algebra support for JavaScript developers, offering powerful tools for numerical computing. While much has been completed, more work remains, and BLAS will continue to be a focal point in 2025.</p> <h2> LAPACK </h2> <p>Building on the BLAS work as part of an internship at <a href="proxy.php?url=https://labs.quansight.org" rel="noopener noreferrer">Quansight Labs</a>, <a href="proxy.php?url=https://github.com/Pranavchiku" rel="noopener noreferrer">Pranav Goswami</a> worked to lay the foundations for <a href="proxy.php?url=https://www.netlib.org/lapack/index.html" rel="noopener noreferrer">LAPACK</a> (<strong>L</strong>inear <strong>A</strong>lgebra <strong>Pack</strong>age) support in stdlib in order to provide higher order linear algebra routines for solving systems of linear equations, eigenvalue problems, matrix factorization, and singular value decomposition. Detailed more fully in his post-internship <a href="proxy.php?url=https://blog.stdlib.io/lapack-in-stdlib/" rel="noopener noreferrer">blog post</a>, Pranav sought to establish an approach for testing and documentation of added implementations and to leverage the ideas of <a href="proxy.php?url=https://github.com/flame/blis" rel="noopener noreferrer">BLIS</a> to create LAPACK interfaces which facilitated interfacing with stdlib's <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/ndarray/ctor" rel="noopener noreferrer"><code>ndarrays</code></a> and thus minimize data movement and storage requirements. While a good chunk of time was spent working out the kinks and iterating on API design, Pranav made significant headway in adding various implementation utilities and nearly 30 commonly used LAPACK routines. Given the enormity of LAPACK (~1700 routines), this work will continue into the foreseeable future, so be on the lookout for more updates in the months ahead!</p> <blockquote> <p>As a quick aside, if you're interested in learning more about how stdlib approaches interfacing with Fortran libraries, many of which still form the bedrock of modern numerical computing, be sure to check out Pranav's blog post on <a href="proxy.php?url=https://blog.stdlib.io/how-to-call-fortran-routines-from-javascript-with-node-js/" rel="noopener noreferrer">calling Fortran routines from JavaScript using Node.js</a>.</p> </blockquote> <h2> C implementations of special math functions </h2> <p>One of stdlib's longstanding priorities is continued development of its vectorized routines for common mathematical and statistical operations. While all scalar mathematical kernels (e.g., transcendental functions, such as <code>sin</code>, <code>cos</code>, <code>erf</code>, <code>gamma</code>, etc, and statistical distribution density functions) have JavaScript implementations, many of the kernels lacked corresponding C implementations, which are needed for unlocking faster performance in Node.js and other server-side JavaScript runtimes supporting native bindings.</p> <p><a href="proxy.php?url=https://github.com/gunjjoshi/" rel="noopener noreferrer">Gunj Joshi</a> and others sought to fill this <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/issues/649" rel="noopener noreferrer">gap</a> and opened over <strong>160</strong> pull requests adding dedicated C implementations. At this point, only a few of the most heavily used double-precision transcendental functions remain (looking at you <code>betainc</code>!). Efforts have now turned to completing single-precision support and adding C implementations for statistical distribution functions. We expect this work to continue for the first half of 2025 before turning our attention to higher-level strided array and ndarray APIs, with implementations for both WebAssembly and Node.js native add-ons.</p> <h2> Fancy indexing </h2> <p>Another area where we made significant progress is in improving slicing and array manipulation ergonomics. Users of numerical programming languages, such as MATLAB and Julia, and dedicated numerical computing libraries, such as NumPy, have long enjoyed the benefit of concise syntax for expressing operations affecting only a subset of array elements. For example, the following snippet demonstrates setting every other element in an array to zero with NumPy.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight python"><code><span class="kn">import</span> <span class="n">numpy</span> <span class="k">as</span> <span class="n">np</span> <span class="c1"># Create an array of ones: </span><span class="n">x</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">ones</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="c1"># Set every other element to zero: </span><span class="n">x</span><span class="p">[::</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="mf">0.0</span> </code></pre> </div> <p>As a language, JavaScript does not provide such convenient syntax, forcing users to either use more verbose object methods or manual <code>for</code> loops. We thus sought to address this gap by leveraging <code>Proxy</code> objects to support "fancy indexing". While the use of <code>Proxy</code> objects does incur some performance overhead due to property indirection, you now need only install and import a single <a href="proxy.php?url=https://github.com/stdlib-js/array-to-fancy" rel="noopener noreferrer">package</a> to get all the benefits of Python-style slicing in JavaScript, thus obviating the need for verbose <code>for</code> loops and making array manipulation significantly more ergonomic.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">import</span> <span class="nx">array2fancy</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@stdlib/array-to-fancy</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// Create a plain array:</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">8</span> <span class="p">];</span> <span class="c1">// Turn the plain array into a "fancy" array:</span> <span class="kd">const</span> <span class="nx">y</span> <span class="o">=</span> <span class="nf">array2fancy</span><span class="p">(</span> <span class="nx">x</span> <span class="p">);</span> <span class="c1">// Select the first three elements:</span> <span class="kd">const</span> <span class="nx">v</span> <span class="o">=</span> <span class="nx">y</span><span class="p">[</span> <span class="dl">'</span><span class="s1">:3</span><span class="dl">'</span> <span class="p">];</span> <span class="c1">// returns [ 1, 2, 3 ]</span> <span class="c1">// Select every other element, starting from the second element:</span> <span class="nx">v</span> <span class="o">=</span> <span class="nx">y</span><span class="p">[</span> <span class="dl">'</span><span class="s1">1::2</span><span class="dl">'</span> <span class="p">];</span> <span class="c1">// returns [ 2, 4, 6, 8 ]</span> <span class="c1">// Select every other element, in reverse order, starting with the last element:</span> <span class="nx">v</span> <span class="o">=</span> <span class="nx">y</span><span class="p">[</span> <span class="dl">'</span><span class="s1">::-2</span><span class="dl">'</span> <span class="p">];</span> <span class="c1">// returns [ 8, 6, 4, 2 ]</span> <span class="c1">// Set all elements to the same value:</span> <span class="nx">y</span><span class="p">[</span> <span class="dl">'</span><span class="s1">:</span><span class="dl">'</span> <span class="p">]</span> <span class="o">=</span> <span class="mi">9</span><span class="p">;</span> <span class="c1">// Create a shallow copy by selecting all elements:</span> <span class="nx">v</span> <span class="o">=</span> <span class="nx">y</span><span class="p">[</span> <span class="dl">'</span><span class="s1">:</span><span class="dl">'</span> <span class="p">];</span> <span class="c1">// returns [ 9, 9, 9, 9, 9, 9, 9, 9 ]</span> </code></pre> </div> <p>In addition to slice semantics, Jaysukh added support to stdlib for <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/array/bool" rel="noopener noreferrer">boolean arrays</a>, thus laying the groundwork for boolean array masking.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">import</span> <span class="nx">BooleanArray</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@stdlib/array-bool</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="nx">array2fancy</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@stdlib/array-to-fancy</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// Create a plain array:</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">8</span> <span class="p">];</span> <span class="c1">// Turn the plain array into a "fancy" array:</span> <span class="kd">const</span> <span class="nx">y</span> <span class="o">=</span> <span class="nf">array2fancy</span><span class="p">(</span> <span class="nx">x</span> <span class="p">);</span> <span class="c1">// Create a shorthand alias for creating an array "index" object:</span> <span class="kd">const</span> <span class="nx">idx</span> <span class="o">=</span> <span class="nx">array2fancy</span><span class="p">.</span><span class="nx">idx</span><span class="p">;</span> <span class="c1">// Create a boolean mask array:</span> <span class="kd">const</span> <span class="nx">mask</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BooleanArray</span><span class="p">(</span> <span class="p">[</span> <span class="kc">true</span><span class="p">,</span> <span class="kc">false</span><span class="p">,</span> <span class="kc">false</span><span class="p">,</span> <span class="kc">true</span><span class="p">,</span> <span class="kc">true</span><span class="p">,</span> <span class="kc">true</span><span class="p">,</span> <span class="kc">false</span><span class="p">,</span> <span class="kc">false</span> <span class="p">]</span> <span class="p">);</span> <span class="c1">// Retrieve elements according to the mask:</span> <span class="kd">const</span> <span class="nx">z</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="nf">idx</span><span class="p">(</span> <span class="nx">mask</span> <span class="p">)</span> <span class="p">];</span> <span class="c1">// returns [ 1, 4, 5, 6 ]</span> </code></pre> </div> <p>We subsequently applied our learnings when adding support for boolean array masking to add support for integer array indexing.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">import</span> <span class="nb">Int32Array</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@stdlib/array-int32</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="nx">array2fancy</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@stdlib/array-to-fancy</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// Create a plain array:</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">8</span> <span class="p">];</span> <span class="c1">// Turn the plain array into a "fancy" array:</span> <span class="kd">const</span> <span class="nx">y</span> <span class="o">=</span> <span class="nf">array2fancy</span><span class="p">(</span> <span class="nx">x</span> <span class="p">);</span> <span class="c1">// Create a shorthand alias for creating an array "index" object:</span> <span class="kd">const</span> <span class="nx">idx</span> <span class="o">=</span> <span class="nx">array2fancy</span><span class="p">.</span><span class="nx">idx</span><span class="p">;</span> <span class="c1">// Create an integer array:</span> <span class="kd">const</span> <span class="nx">indices</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Int32Array</span><span class="p">(</span> <span class="p">[</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span> <span class="p">]</span> <span class="p">);</span> <span class="c1">// Retrieve selected elements:</span> <span class="kd">const</span> <span class="nx">z</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="nf">idx</span><span class="p">(</span> <span class="nx">indices</span> <span class="p">)</span> <span class="p">];</span> <span class="c1">// returns [ 1, 4, 5, 6 ]</span> </code></pre> </div> <p>While the above demonstrates fancy indexing with built-in JavaScript array objects, we've recently extended the concept of fancy indexing to stdlib <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/ndarray/ctor" rel="noopener noreferrer"><code>ndarrays</code></a>, a topic we'll have more to say about in a future blog post.</p> <p>Needless to say, we are particularly excited about these developments because we believe they will significantly improve the user experience of interactive computing and exploratory data analysis in JavaScript.</p> <h2> Test and build </h2> <p>Lastly, 2024 was a year of automation, and I would be remiss if I didn't mention the efforts of <a href="proxy.php?url=https://github.com/Planeshifter" rel="noopener noreferrer">Philipp Burckhardt</a>. Philipp was instrumental in improving our CI build and test infrastructure and improving the overall scalability of the project. His work was prolific, but there are a few key highlights I want to bring to the fore.</p> <ul> <li> <strong>Automatic changelog generation</strong>: Philipp shepherded the project toward using <a href="proxy.php?url=https://www.conventionalcommits.org/en/v1.0.0/" rel="noopener noreferrer">conventional commits</a>, which is a standardized way for adding human and machine readable meaning to commit messages, and subsequently built a robust set of tools for performing automatic releases, generating comprehensive changelogs, and coordinating the publishing of stdlib's ever-growing ecosystem of over <strong>4000</strong> standalone packages. What was once a manual release process can now be done by running a single GitHub workflow.</li> <li> <strong>stdlib bot</strong>: Philipp created a GitHub pull request bot for automating pull request review tasks, posting helpful messages, and improving the overall maintainer development experience. In the months ahead, we're particularly keen to extend the bot's functionality to help with new contributor onboarding and flagging common contribution issues.</li> <li> <strong>Test coverage automation</strong>: with a project of stdlib's size, running the entire test suite on each commit and for each pull request is simply not possible. It can thus be challenging to stitch together individual package test coverage reports in order to obtain a global view of overall test coverage. Philipp worked to address this problem by creating an automation pipeline for uploading individual test coverage reports to a dedicated <a href="proxy.php?url=https://github.com/stdlib-js/www-test-code-coverage" rel="noopener noreferrer">repository</a>, with support for tracking coverage metrics over time and creating expected test coverage changes for each submitted pull request. Needless to say, this has drastically improved our visibility into test coverage metrics and helped improve our confidence in tests accompanying submitted pull requests.</li> </ul> <p>While we've made considerable strides in our project automation tooling, we never seem to be short of ideas for further automation and tooling improvements. Expect more to come in 2025! 🤖</p> <h2> Look ahead </h2> <p>So what's in store for 2025?! Glad you asked!</p> <p>We've already alluded to various initiatives in the sections above, but, at a high level, here's where we're planning to focus our efforts in the year ahead:</p> <ul> <li> <strong>GSoC 2025</strong>: assuming Google runs its annual Google Summer of Code program and we're fortunate enough to be accepted again, we'd love to continue supporting the next generation of open source contributors.</li> <li> <strong>Math and stats C implementations</strong>: expanding our library of scalar math and statistics kernels and ensuring double- and single-precision parity.</li> <li> <strong>BLAS</strong>: completing our WebAssembly distribution and higher-level APIs for operating on stacks of matrices and vectors.</li> <li> <strong>LAPACK</strong>: continuing to chip away at the ~1700 LAPACK routines (!).</li> <li> <strong>FFTs</strong>: adding initial Fast Fourier Transform (FFT) support to stdlib to help unlock algorithms for signal processing.</li> <li> <strong>Vectorized operations</strong>: automating package creation for vectorized operations over scalar math and statistics kernels.</li> <li> <strong>ndarray API parity</strong>: expanding the usability and familiarity of <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/ndarray/ctor" rel="noopener noreferrer"><code>ndarrays</code></a> by achieving API parity with built-in JavaScript arrays and typed arrays.</li> <li> <strong>REPL</strong>: adding Jupyter-protocol support and various user-ergonomics improvements.</li> <li> <strong>WebGPU</strong>: while we haven't formally committed to any specific approach, we're keen on at least exploring support for <a href="proxy.php?url=https://en.wikipedia.org/wiki/WebGPU" rel="noopener noreferrer">WebGPU</a>, an emerging web standard that enables webpages to use a device's graphics processing unit (GPU) efficiently, including for general-purpose GPU computation, in order to provide APIs for accelerated scientific computing on the web.</li> <li> <strong>Project funding</strong>: exploring and hopefully securing project funding to accelerate development efforts and support the continued growth of the stdlib community.</li> </ul> <p>That's definitely a lot, and it's going to take a village—a community of people dedicated to our mission of making the web a first-class platform for numerical and scientific computing. If you're ready to join us in building the future of scientific computing on the web, we'd love for you to join us. Check out our <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/blob/develop/CONTRIBUTING.md" rel="noopener noreferrer">contributing guide</a> to see how you can get involved.</p> <h2> A personal note </h2> <p>As we look ahead, I'd like to share a personal reflection on what this year has meant to me. Given our growth this year, I often felt like I was drinking from a fire hose. And, honestly, it can be hard not to get burned out when you wake up day-after-day to over <em>100</em> new notifications and messages from folks wanting guidance, answers to questions, and pull requests reviewed. But, when reflecting on this past year, I am awfully proud of what we've accomplished, and I am especially heartened when I see contributors new to open source grow and flourish, sometimes using the lessons they've learned contributing as a springboard to dream jobs and opportunities. Having the fortune to see that is a driving motivation and a privilege within the greater world of open source that I do my best to not take for granted.</p> <p>And with that, this concludes the 2024 retrospective. Looking back on all we've achieved together, the future of scientific computing on the web has never been brighter! Thank you again to everyone involved who's helped out along the way. The road ahead is filled with exciting opportunities, and we can't wait to see what we will achieve together in 2025. Onward and upward! 🚀</p> <p><a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> is an open source software project dedicated to providing a comprehensive suite of robust, high-performance libraries to accelerate your project's development and give you peace of mind knowing that you're depending on expertly crafted, high-quality software.</p> <p>If you've enjoyed this post, give us a star 🌟 on <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">GitHub</a> and consider <a href="proxy.php?url=https://opencollective.com/stdlib" rel="noopener noreferrer">financially supporting</a> the project. Your contributions and continued support help ensure the project's long-term success and are greatly appreciated!</p> webdev javascript programming node LAPACK in your web browser Pranav Chiku Fri, 20 Dec 2024 23:06:26 +0000 https://dev.to/stdlib/lapack-in-your-web-browser-3mec https://dev.to/stdlib/lapack-in-your-web-browser-3mec <p><em>This post was originally published on the Quansight Labs <a href="proxy.php?url=https://labs.quansight.org/blog/lapack-in-stdlib" rel="noopener noreferrer">blog</a> and has been modified and republished here with Quansight's permission.</em></p> <p>Web applications are rapidly emerging as a new frontier for high-performance scientific computation and AI-enabled end-user experiences. Underpinning the ML/AI revolution is linear algebra, a branch of mathematics concerning linear equations and their representations in vector spaces and via matrices. <a href="proxy.php?url=https://netlib.org/lapack/" rel="noopener noreferrer">LAPACK</a> ("<strong>L</strong>inear <strong>A</strong>lgebra <strong>Pack</strong>age") is a fundamental software library for numerical linear algebra, providing robust, battle-tested implementations of common matrix operations. Despite LAPACK being a foundational component of most numerical computing programming languages and libraries, a comprehensive, high-quality LAPACK implementation tailored to the unique constraints of the web has yet to materialize. That is...until now.</p> <p>Earlier this year, I had the great fortune of being a summer intern at <a href="proxy.php?url=https://labs.quansight.org" rel="noopener noreferrer">Quansight Labs</a>, the public benefit division of <a href="proxy.php?url=https://quansight.com" rel="noopener noreferrer">Quansight</a> and a leader in the scientific Python ecosystem. During my internship, I worked to add initial LAPACK support to <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a>, a fundamental library for scientific computation written in C and JavaScript and optimized for use in web browsers and other web-native environments, such as Node.js and Deno. In this blog post, I'll discuss my journey, some expected and unexpected (!) challenges, and the road ahead. My hope is that this work, with a little bit of luck, provides a critical building block in making web browsers a first-class environment for numerical computation and machine learning and portends a future of more powerful AI-enabled web applications.</p> <p>Sound interesting? Let's go!</p> <h2> What is stdlib? </h2> <p>Readers of this blog who are familiar with LAPACK are likely to not be intimately familiar with the wild world of web technologies. For those coming from the world of numerical and scientific computation and have familiarity with the scientific Python ecosystem, the easiest way to think of <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> is as an open source scientific computing library in the mold of <a href="proxy.php?url=https://github.com/numpy/numpy" rel="noopener noreferrer">NumPy</a> and <a href="proxy.php?url=https://github.com/scipy/scipy" rel="noopener noreferrer">SciPy</a>. It provides multi-dimensional array data structures and associated routines for mathematics, statistics, and linear algebra, but uses JavaScript, rather than Python, as its primary scripting language. As such, stdlib is laser-focused on the web ecosystem and its application development paradigms. This focus necessitates some interesting design and project architecture decisions, which make stdlib rather unique when compared to more traditional libraries designed for numerical computation.</p> <p>To take NumPy as an example, NumPy is a single monolithic library, where all of its components, outside of optional third-party dependencies such as <a href="proxy.php?url=https://github.com/OpenMathLib/OpenBLAS" rel="noopener noreferrer">OpenBLAS</a>, form a single, indivisible unit. One cannot simply install NumPy routines for <a href="proxy.php?url=https://numpy.org/doc/stable/reference/routines.array-manipulation.html" rel="noopener noreferrer">array manipulation</a> without installing all of NumPy. If you are deploying an application which only needs NumPy's <code>ndarray</code> object and a couple of its manipulation routines, installing and bundling all of NumPy means including a considerable amount of <a href="proxy.php?url=https://en.wikipedia.org/wiki/Dead_code" rel="noopener noreferrer">"dead code"</a>. In web development parlance, we'd say that NumPy is not <a href="proxy.php?url=https://en.wikipedia.org/wiki/Tree_shaking" rel="noopener noreferrer">"tree shakeable"</a>. For a normal NumPy installation, this implies at least 30MB of disk space, and at least <a href="proxy.php?url=https://towardsdatascience.com/how-to-shrink-numpy-scipy-pandas-and-matplotlib-for-your-data-product-4ec8d7e86ee4" rel="noopener noreferrer">15MB of disk space</a> for a customized build which excludes all debug statements. For SciPy, those numbers can balloon to 130MB and 50MB, respectively. Needless to say, shipping a 15MB library in a web application for just a few functions is a non-starter, especially for developers needing to deploy web applications to devices with poor network connectivity or memory constraints.</p> <p>Given the unique constraints of web application development, stdlib takes a bottom-up approach to its design, where every unit of functionality can be installed and consumed independently of unrelated and unused parts of the codebase. By embracing a decomposable software architecture and <a href="proxy.php?url=https://aredridel.dinhe.net/2016/06/04/radical-modularity/" rel="noopener noreferrer">radical modularity</a>, stdlib offers users the ability to install and use exactly what they need, with little-to-no excess code beyond a desired set of APIs and their explicit dependencies, thus ensuring smaller memory footprints, bundle sizes, and faster deployment.</p> <p>As an example, suppose you are working with two stacks of matrices (i.e., two-dimensional slices of three-dimensional cubes), and you want to select every other slice and perform the common BLAS operation <code>y += a * x</code>, where <code>x</code> and <code>y</code> are <a href="proxy.php?url=https://stdlib.io/docs/api/latest/@stdlib/ndarray/ctor" rel="noopener noreferrer"><code>ndarrays</code></a> and <code>a</code> is a scalar constant. To do this with NumPy, you'd first install all of NumPy<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>pip <span class="nb">install </span>numpy </code></pre> </div> <p>and then perform the various operations<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight python"><code><span class="c1"># Import all of NumPy: </span><span class="kn">import</span> <span class="n">numpy</span> <span class="k">as</span> <span class="n">np</span> <span class="c1"># Define arrays: </span><span class="n">x</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">asarray</span><span class="p">(...)</span> <span class="n">y</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">asarray</span><span class="p">(...)</span> <span class="c1"># Perform operation: </span><span class="n">y</span><span class="p">[::</span><span class="mi">2</span><span class="p">,:,:]</span> <span class="o">+=</span> <span class="mf">5.0</span> <span class="o">*</span> <span class="n">x</span><span class="p">[::</span><span class="mi">2</span><span class="p">,:,:]</span> </code></pre> </div> <p>With stdlib, in addition to having the ability to install the project as a monolithic library, you can install the various units of functionality as separate packages<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>npm <span class="nb">install</span> @stdlib/ndarray-fancy @stdlib/blas-daxpy </code></pre> </div> <p>and then perform the various operations<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="c1">// Individually import desired functionality:</span> <span class="k">import</span> <span class="nx">FancyArray</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@stdlib/ndarray-fancy</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="nx">daxpy</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@stdlib/blas-daxpy</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// Define ndarray meta data:</span> <span class="kd">const</span> <span class="nx">shape</span> <span class="o">=</span> <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">4</span><span class="p">];</span> <span class="kd">const</span> <span class="nx">strides</span> <span class="o">=</span> <span class="p">[...];</span> <span class="kd">const</span> <span class="nx">offset</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// Define arrays using a "lower-level" fancy array constructor:</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FancyArray</span><span class="p">(</span><span class="dl">'</span><span class="s1">float64</span><span class="dl">'</span><span class="p">,</span> <span class="p">[...],</span> <span class="nx">shape</span><span class="p">,</span> <span class="nx">strides</span><span class="p">,</span> <span class="nx">offset</span><span class="p">,</span> <span class="dl">'</span><span class="s1">row-major</span><span class="dl">'</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">y</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FancyArray</span><span class="p">(</span><span class="dl">'</span><span class="s1">float64</span><span class="dl">'</span><span class="p">,</span> <span class="p">[...],</span> <span class="nx">shape</span><span class="p">,</span> <span class="nx">strides</span><span class="p">,</span> <span class="nx">offset</span><span class="p">,</span> <span class="dl">'</span><span class="s1">row-major</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// Perform operation:</span> <span class="nf">daxpy</span><span class="p">(</span><span class="mf">5.0</span><span class="p">,</span> <span class="nx">x</span><span class="p">[</span><span class="dl">'</span><span class="s1">::2,:,:</span><span class="dl">'</span><span class="p">],</span> <span class="nx">y</span><span class="p">[</span><span class="dl">'</span><span class="s1">::2,:,:</span><span class="dl">'</span><span class="p">]);</span> </code></pre> </div> <p>Importantly, not only can you independently install any one of stdlib's over <a href="proxy.php?url=https://github.com/stdlib-js" rel="noopener noreferrer">4,000 packages</a>, but you can also fix, improve, and remix any one of those packages by forking an associated GitHub repository (e.g., see <a href="proxy.php?url=https://github.com/stdlib-js/ndarray-fancy/tree/main" rel="noopener noreferrer"><code>@stdlib/ndarray-fancy</code></a>). By defining explicit layers of abstraction and dependency trees, stdlib offers you the freedom to choose the right layer of abstraction for your application. In some ways, it's a simple—and, if you're accustomed to conventional scientific software library design, perhaps unorthodox—idea, but, when tightly integrated with the web platform, it has powerful consequences and creates exciting new possibilities!</p> <h2> What about WebAssembly? </h2> <p>Okay, so maybe your interest has piqued; stdlib seems intriguing. But what does this have to do with LAPACK in web browsers? Well, one of our goals this past summer was to apply the stdlib ethos—small, narrowly scoped packages which do one thing and do one thing well—in bringing LAPACK to the web.</p> <p>But wait, you say! That is an extreme undertaking. LAPACK is vast, with approximately 1,700 routines, and implementing even 10% of them within a reasonable time frame is a significant challenge. Wouldn't it be better to just compile LAPACK to <a href="proxy.php?url=https://webassembly.org" rel="noopener noreferrer">WebAssembly</a>, a portable compilation target for programming languages such as C, Go, and Rust, which enables deployment on the web, and call it a day?</p> <p>Unfortunately, there are several issues with this approach.</p> <ol> <li>Compiling Fortran to WebAssembly is still an area of active development (see <a href="proxy.php?url=https://gws.phd/posts/fortran_wasm/" rel="noopener noreferrer">1</a>, <a href="proxy.php?url=https://pyodide.org/en/0.25.0/project/roadmap.html#find-a-better-way-to-compile-fortran" rel="noopener noreferrer">2</a>, <a href="proxy.php?url=https://github.com/scipy/scipy/issues/15290" rel="noopener noreferrer">3</a>, <a href="proxy.php?url=https://github.com/pyodide/pyodide/issues/184" rel="noopener noreferrer">4</a>, and <a href="proxy.php?url=https://lfortran.org/blog/2023/05/lfortran-breakthrough-now-building-legacy-and-modern-minpack/" rel="noopener noreferrer">5</a>). At the time of this post, a common approach is to use <a href="proxy.php?url=https://netlib.org/f2c/" rel="noopener noreferrer"><code>f2c</code></a> to compile Fortran to C and then to perform a separate compilation step to convert C to WebAssembly. However, this approach is problematic as <code>f2c</code> only fully supports Fortran 77, and the generated code requires extensive patching. Work is underway to develop an LLVM-based Fortran compiler, but gaps and complex toolchains remain.</li> <li>As alluded to above in the discussion concerning monolithic libraries in web applications, the vastness of LAPACK is part of the problem. Even if the compilation problem is solved, including a single WebAssembly binary containing all of LAPACK in a web application needing to use only one or two LAPACK routines means considerable dead code, resulting in slower loading times and increased memory consumption.</li> <li>While one could attempt to compile individual LAPACK routines to standalone WebAssembly binaries, doing so could result in binary bloat, as multiple standalone binaries may contain duplicated code from common dependencies. To mitigate binary bloat, one could attempt to perform module splitting. In this scenario, one first factors out common dependencies into a standalone binary containing shared code and then generates separate binaries for individual APIs. While suitable in some cases, this can quickly get unwieldy, as this approach requires linking individual WebAssembly modules at load-time by stitching together the exports of one or more modules with the imports of one or more other modules. Not only can this be tedious, but this approach also entails a performance penalty due to the fact that, when WebAssembly routines call imported exports, they now must cross over into JavaScript, rather than remaining within WebAssembly. Sound complex? It is!</li> <li>Apart from WebAssembly modules operating exclusively on scalar input arguments (e.g., computing the sine of a single number), every WebAssembly module instance must be associated with WebAssembly memory, which is allocated in fixed increments of 64KiB (i.e., a "page"). And importantly, as of this blog post, WebAssembly memory can only grow and <a href="proxy.php?url=https://github.com/WebAssembly/memory-control/blob/16dd6b93ab82d0b4b252e3da5451e9b5e452ee62/proposals/memory-control/Overview.md" rel="noopener noreferrer">never shrink</a>. As there is currently no mechanism for releasing memory to a host, a WebAssembly application's memory footprint can only increase. These two aspects combined increase the likelihood of allocating memory which is never used and the prevalence of memory leaks.</li> <li>Lastly, while powerful, WebAssembly entails a steeper learning curve and a more complex set of often rapidly evolving toolchains. In end-user applications, interfacing between JavaScript—a web-native dynamically-compiled programming language—and WebAssembly further brings increased complexity, especially when having to perform manual memory management.</li> </ol> <p>To help illustrate the last point, let's return to the BLAS routine <code>daxpy</code>, which performs the operation <code>y = a*x + y</code> and where <code>x</code> and <code>y</code> are strided vectors and <code>a</code> a scalar constant. If implemented in C, a basic implementation might look like the following code snippet.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight c"><code><span class="kt">void</span> <span class="nf">c_daxpy</span><span class="p">(</span><span class="k">const</span> <span class="kt">int</span> <span class="n">N</span><span class="p">,</span> <span class="k">const</span> <span class="kt">double</span> <span class="n">alpha</span><span class="p">,</span> <span class="k">const</span> <span class="kt">double</span> <span class="o">*</span><span class="n">X</span><span class="p">,</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">strideX</span><span class="p">,</span> <span class="kt">double</span> <span class="o">*</span><span class="n">Y</span><span class="p">,</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">strideY</span><span class="p">)</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">ix</span><span class="p">;</span> <span class="kt">int</span> <span class="n">iy</span><span class="p">;</span> <span class="kt">int</span> <span class="n">i</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="n">N</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">alpha</span> <span class="o">==</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">strideX</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">ix</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">N</span><span class="p">)</span> <span class="o">*</span> <span class="n">strideX</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">ix</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">strideY</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">iy</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">N</span><span class="p">)</span> <span class="o">*</span> <span class="n">strideY</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">iy</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">N</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="n">Y</span><span class="p">[</span><span class="n">iy</span><span class="p">]</span> <span class="o">+=</span> <span class="n">alpha</span> <span class="o">*</span> <span class="n">X</span><span class="p">[</span><span class="n">ix</span><span class="p">];</span> <span class="n">ix</span> <span class="o">+=</span> <span class="n">strideX</span><span class="p">;</span> <span class="n">iy</span> <span class="o">+=</span> <span class="n">strideY</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>After compilation to WebAssembly and loading the WebAssembly binary into our web application, we need to perform a series of steps before we can call the <code>c_daxpy</code> routine from JavaScript. First, we need to instantiate a new WebAssembly module.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">binary</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">UintArray</span><span class="p">([...]);</span> <span class="kd">const</span> <span class="nx">mod</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">WebAssembly</span><span class="p">.</span><span class="nc">Module</span><span class="p">(</span><span class="nx">binary</span><span class="p">);</span> </code></pre> </div> <p>Next, we need to define module memory and create a new WebAssembly module instance.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="c1">// Initialize 10 pages of memory and allow growth to 100 pages:</span> <span class="kd">const</span> <span class="nx">mem</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">WebAssembly</span><span class="p">.</span><span class="nc">Memory</span><span class="p">({</span> <span class="dl">'</span><span class="s1">initial</span><span class="dl">'</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span> <span class="c1">// 640KiB, where each page is 64KiB</span> <span class="dl">'</span><span class="s1">maximum</span><span class="dl">'</span><span class="p">:</span> <span class="mi">100</span> <span class="c1">// 6.4MiB</span> <span class="p">});</span> <span class="c1">// Create a new module instance:</span> <span class="kd">const</span> <span class="nx">instance</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">WebAssembly</span><span class="p">.</span><span class="nc">Instance</span><span class="p">(</span><span class="nx">mod</span><span class="p">,</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">env</span><span class="dl">'</span><span class="p">:</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">memory</span><span class="dl">'</span><span class="p">:</span> <span class="nx">mem</span> <span class="p">}</span> <span class="p">});</span> </code></pre> </div> <p>After creating a module instance, we can now invoke the exported BLAS routine. However, if data is defined outside of module memory, we first need to copy that data to the memory instance and always do so in little-endian byte order.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="c1">// External data:</span> <span class="kd">const</span> <span class="nx">xdata</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Float64Array</span><span class="p">([...]);</span> <span class="kd">const</span> <span class="nx">ydata</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Float64Array</span><span class="p">([...]);</span> <span class="c1">// Specify a vector length:</span> <span class="kd">const</span> <span class="nx">N</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span> <span class="c1">// Specify vector strides (in units of elements):</span> <span class="kd">const</span> <span class="nx">strideX</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">strideY</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span> <span class="c1">// Define pointers (i.e., byte offsets) for storing two vectors:</span> <span class="kd">const</span> <span class="nx">xptr</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">yptr</span> <span class="o">=</span> <span class="nx">N</span> <span class="o">*</span> <span class="mi">8</span><span class="p">;</span> <span class="c1">// 8 bytes per double</span> <span class="c1">// Create a DataView over module memory:</span> <span class="kd">const</span> <span class="nx">view</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DataView</span><span class="p">(</span><span class="nx">mem</span><span class="p">.</span><span class="nx">buffer</span><span class="p">);</span> <span class="c1">// Resolve the first indexed elements in both `xdata` and `ydata`:</span> <span class="kd">let</span> <span class="nx">offsetX</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">if </span><span class="p">(</span><span class="nx">strideX</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">offsetX</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="nx">N</span><span class="p">)</span> <span class="o">*</span> <span class="nx">strideX</span><span class="p">;</span> <span class="p">}</span> <span class="kd">let</span> <span class="nx">offsetY</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">if </span><span class="p">(</span><span class="nx">strideY</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">offsetY</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="nx">N</span><span class="p">)</span> <span class="o">*</span> <span class="nx">strideY</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// Write data to the memory instance:</span> <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">N</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="nx">view</span><span class="p">.</span><span class="nf">setFloat64</span><span class="p">(</span><span class="nx">xptr</span><span class="o">+</span><span class="p">(</span><span class="nx">i</span><span class="o">*</span><span class="mi">8</span><span class="p">),</span> <span class="nx">xdata</span><span class="p">[</span><span class="nx">offsetX</span><span class="o">+</span><span class="p">(</span><span class="nx">i</span><span class="o">*</span><span class="nx">strideX</span><span class="p">)],</span> <span class="kc">true</span><span class="p">);</span> <span class="nx">view</span><span class="p">.</span><span class="nf">setFloat64</span><span class="p">(</span><span class="nx">yptr</span><span class="o">+</span><span class="p">(</span><span class="nx">i</span><span class="o">*</span><span class="mi">8</span><span class="p">),</span> <span class="nx">ydata</span><span class="p">[</span><span class="nx">offsetY</span><span class="o">+</span><span class="p">(</span><span class="nx">i</span><span class="o">*</span><span class="nx">strideY</span><span class="p">)],</span> <span class="kc">true</span><span class="p">);</span> <span class="p">}</span> </code></pre> </div> <p>Now that data is written to module memory, we can call the <code>c_daxpy</code> routine.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="nx">instance</span><span class="p">.</span><span class="nx">exports</span><span class="p">.</span><span class="nf">c_daxpy</span><span class="p">(</span><span class="nx">N</span><span class="p">,</span> <span class="mf">5.0</span><span class="p">,</span> <span class="nx">xptr</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">yptr</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span> </code></pre> </div> <p>And, finally, if we need to pass the results to a downstream library without support for WebAssembly memory "pointers" (i.e., byte offsets), such as D3, for visualization or further analysis, we need to copy data from module memory back to the original output array.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">N</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="nx">ydata</span><span class="p">[</span><span class="nx">offsetY</span><span class="o">+</span><span class="p">(</span><span class="nx">i</span><span class="o">*</span><span class="nx">strideY</span><span class="p">)]</span> <span class="o">=</span> <span class="nx">view</span><span class="p">.</span><span class="nf">getFloat64</span><span class="p">(</span><span class="nx">yptr</span><span class="o">+</span><span class="p">(</span><span class="nx">i</span><span class="o">*</span><span class="mi">8</span><span class="p">),</span> <span class="kc">true</span><span class="p">);</span> <span class="p">}</span> </code></pre> </div> <p>That's a lot of work just to compute <code>y = a*x + y</code>. In contrast, compare to a plain JavaScript implementation, which might look like the following code snippet.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">function</span> <span class="nf">daxpy</span><span class="p">(</span><span class="nx">N</span><span class="p">,</span> <span class="nx">alpha</span><span class="p">,</span> <span class="nx">X</span><span class="p">,</span> <span class="nx">strideX</span><span class="p">,</span> <span class="nx">Y</span><span class="p">,</span> <span class="nx">strideY</span><span class="p">)</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">ix</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">iy</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">i</span><span class="p">;</span> <span class="k">if </span><span class="p">(</span><span class="nx">N</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="k">if </span><span class="p">(</span><span class="nx">alpha</span> <span class="o">===</span> <span class="mf">0.0</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="k">if </span><span class="p">(</span><span class="nx">strideX</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">ix</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="nx">N</span><span class="p">)</span> <span class="o">*</span> <span class="nx">strideX</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nx">ix</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="k">if </span><span class="p">(</span><span class="nx">strideY</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">iy</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="nx">N</span><span class="p">)</span> <span class="o">*</span> <span class="nx">strideY</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nx">iy</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="k">for </span><span class="p">(</span><span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">N</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="nx">Y</span><span class="p">[</span><span class="nx">iy</span><span class="p">]</span> <span class="o">+=</span> <span class="nx">alpha</span> <span class="o">*</span> <span class="nx">X</span><span class="p">[</span><span class="nx">ix</span><span class="p">];</span> <span class="nx">ix</span> <span class="o">+=</span> <span class="nx">strideX</span><span class="p">;</span> <span class="nx">iy</span> <span class="o">+=</span> <span class="nx">strideY</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>With the JavaScript implementation, we can then directly call <code>daxpy</code> with our externally defined data without the data movement required in the WebAssembly example above.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="nf">daxpy</span><span class="p">(</span><span class="nx">N</span><span class="p">,</span> <span class="mf">5.0</span><span class="p">,</span> <span class="nx">xdata</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">ydata</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span> </code></pre> </div> <p>At least in this case, not only is the WebAssembly approach less ergonomic, but, as might be expected given the required data movement, there's a negative performance impact, as well, as demonstrated in the following figure.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4bguh3kklzpsnro026ib.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4bguh3kklzpsnro026ib.png" alt="Grouped column chart displaying a performance comparison of stdlib's C, JavaScript, and WebAssembly (Wasm) implementations for the BLAS routine daxpy for increasing array lengths." width="800" height="431"></a></p> <p><em>Figure 1: Performance comparison of stdlib's C, JavaScript, and WebAssembly (Wasm) implementations for the BLAS routine <b>daxpy</b> for increasing array lengths (x-axis). In the <b>Wasm (copy)</b> benchmark, input and output data is copied to and from Wasm memory, leading to poorer performance.</em></p> <p>In the figure above, I'm displaying a performance comparison of stdlib's C, JavaScript, and WebAssembly (Wasm) implementations for the BLAS routine <code>daxpy</code> for increasing array lengths, as enumerated along the x-axis. The y-axis shows a normalized rate relative to a baseline C implementation. In the <code>Wasm</code> benchmark, input and output data is allocated and manipulated directly in WebAssembly module memory, and, in the <code>Wasm (copy)</code> benchmark, input and output data is copied to and from WebAssembly module memory, as discussed above. From the chart, we may observe the following:</p> <ol> <li>In general, thanks to highly optimized just-in-time (JIT) compilers, JavaScript code, when carefully written, can execute only 2-to-3 times slower than native code. This result is impressive for a loosely typed, dynamically compiled programming language and, at least for <code>daxpy</code>, remains consistent across varying array lengths.</li> <li>As data sizes and thus the amount of time spent in a WebAssembly module increase, WebAssembly can approach near-native (~1.5x) speed. This result aligns more generally with expected WebAssembly performance.</li> <li>While WebAssembly can achieve near-native speed, data movement requirements may adversely affect performance, as observed for <code>daxpy</code>. In such cases, a well-crafted JavaScript implementation which avoids such requirements can achieve equal, if not better, performance, as is the case for <code>daxpy</code>. </li> </ol> <p>Overall, WebAssembly can offer performance improvements; however, the technology is not a silver bullet and needs to be used carefully in order to realize desired gains. And even when offering superior performance, such gains must be balanced against the costs of increased complexity, potentially larger bundle sizes, and more complex toolchains. For many applications, a plain JavaScript implementation will do just fine.</p> <h2> Radical modularity </h2> <p>Now that I've prosecuted the case against just compiling the entirety of LAPACK to WebAssembly and calling it a day, where does that leave us? Well, if we're going to embrace the stdlib ethos, it leaves us in need of radical modularity.</p> <p>To embrace radical modularity is to recognize that what is best is highly contextual, and, depending on the needs and constraints of user applications, developers need the flexibility to pick the right abstraction. If a developer is writing a Node.js application, that may mean binding to hardware-optimized libraries, such as OpenBLAS, Intel MKL, or Apple Accelerate in order to achieve superior performance. If a developer is deploying a web application needing a small set of numerical routines, JavaScript is likely the right tool for the job. And if a developer is working on a large, resource intensive WebAssembly application (e.g., for image editing or a gaming engine), then being able to easily compile individual routines as part of the larger application will be paramount. In short, we want a radically modular LAPACK.</p> <p>My mission was to lay the groundwork for such an endeavor, to work out the kinks and find the gaps, and to hopefully get us a few steps closer to high-performance linear algebra on the web. But what does radical modularity look like? It all begins with the fundamental unit of functionality, the <strong>package</strong>.</p> <p>Every package in stdlib is its own standalone thing, containing co-localized tests, benchmarks, examples, documentation, build files, and associated meta data (including the enumeration of any dependencies) and defining a clear API surface with the outside world. In order to add LAPACK support to stdlib, that means creating a separate standalone package for each LAPACK routine with the following structure:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>├── benchmark │ ├── c │ │ ├── Makefile │ │ └── benchmark.c │ ├── fortran │ │ ├── Makefile │ │ └── benchmark.f │ └── benchmark*.js ├── docs │ ├── types │ │ ├── index.d.ts │ │ └── test.ts │ └── repl.txt ├── examples │ ├── c │ │ ├── Makefile │ │ └── example.c │ └── index.js ├── include/* ├── lib │ ├── index.js │ └── *.js ├── src │ ├── Makefile │ ├── addon.c │ ├── *.c │ └── *.f ├── test │ └── test*.js ├── binding.gyp ├── include.gypi ├── manifest.json ├── package.json └── README.md </code></pre> </div> <p>Briefly,</p> <ul> <li> <strong>benchmark</strong>: a folder containing micro-benchmarks to assess performance relative to a reference implementation (i.e., reference LAPACK).</li> <li> <strong>docs</strong>: a folder containing auxiliary documentation including REPL help text and TypeScript declarations defining typed API signatures.</li> <li> <strong>examples</strong>: a folder containing executable demonstration code, which, in addition to serving as documentation, helps developers sanity check implementation behavior.</li> <li> <strong>include</strong>: a folder containing C header files.</li> <li> <strong>lib</strong>: a folder containing JavaScript source implementations, with <code>index.js</code> serving as the package entry point and other <code>*.js</code> files defining internal implementation modules.</li> <li> <strong>src</strong>: a folder containing C and Fortran source implementations. Each modular LAPACK package should contain a slightly modified Fortran reference implementation (F77 to free-form Fortran). C files include a plain C implementation which follows the Fortran reference implementation, a wrapper for calling the Fortran reference implementation, a wrapper for calling hardware-optimized libraries (e.g., OpenBLAS) in server-side applications, and a native binding for calling into compiled C from JavaScript in Node.js or a compatible server-side JavaScript runtime.</li> <li> <strong>test</strong>: a folder containing unit tests for testing expected behavior in both JavaScript and native implementations. Tests for native implementations are written in JavaScript and leverage the native binding for interoperation between JavaScript and C/Fortran.</li> <li> <strong>binding.gyp/include.gypi</strong>: build files for compiling Node.js native add-ons, which provide a bridge between JavaScript and native code.</li> <li> <strong>manifest.json</strong>: a configuration file for stdlib's internal C and Fortran compiled source file package management.</li> <li> <strong>package.json</strong>: a file containing package meta data, including the enumeration of external package dependencies and a path to a plain JavaScript implementation for use in browser-based web applications.</li> <li> <strong>README.md</strong>: a file containing a package's primary documentation, which includes API signatures and examples for both JavaScript and C interfaces.</li> </ul> <p>Given stdlib's demanding documentation and testing requirements, adding support for each routine is a decent amount of work, but the end result is robust, high-quality, and, most importantly, modular code suitable for serving as the foundation for scientific computation on the modern web. But enough with the preliminaries! Let's get down to business!</p> <h2> A multi-phase approach </h2> <p>Building on <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/pulls?q=sort%3Aupdated-desc+label%3ABLAS" rel="noopener noreferrer">previous efforts</a> which added BLAS support to stdlib, we decided to follow a similar multi-phase approach when adding LAPACK support in which we first prioritize JavaScript implementations and their associated testing and documentation and then, once tests and documentation are present, back fill C and Fortran implementations and any associated native bindings to hardware-optimized libraries. This approach allows us to put some early points on the board, so to speak, quickly getting APIs in front of users, establishing robust test procedures and benchmarks, and investigating potential avenues for tooling and automation before diving into the weeds of build toolchains and performance optimizations. But where to even begin?</p> <p>To determine which LAPACK routines to target first, I parsed LAPACK's Fortran source code to generate a call graph. This allowed me to infer the dependency tree for each LAPACK routine. With the graph in hand, I then performed a topological sort, thus helping me identify routines without dependencies and which will inevitably be building blocks for other routines. While a depth-first approach in which I picked a particular high-level routine and worked backward would enable me to land a specific feature, such an approach might cause me to get bogged down trying to implement routines of increasing complexity. By focusing on the "leaves" of the graph, I could prioritize commonly used routines (i.e., routines with high <em>indegrees</em>) and thus maximize my impact by unlocking the ability to deliver multiple higher-level routines either later in my efforts or by other contributors.</p> <p>With my plan in hand, I was excited to get to work. For my first routine, I chose <a href="proxy.php?url=https://www.netlib.org/lapack/explore-html/d1/d7e/group__laswp_ga5d3ea3e3cb61e32750bf062a2446aa33.html#ga5d3ea3e3cb61e32750bf062a2446aa33" rel="noopener noreferrer"><code>dlaswp</code></a>, which performs a series of row interchanges on a general rectangular matrix according to a provided list of pivot indices and which is a key building block for LAPACK's LU decomposition routines. And that is when my challenges began...</p> <h2> Challenges </h2> <h3> Legacy Fortran </h3> <p>Prior to my Quansight Labs internship, I was (and still am!) a regular contributor to <a href="proxy.php?url=https://lfortran.org" rel="noopener noreferrer">LFortran</a>, a modern interactive Fortran compiler built on top of LLVM, and I was feeling fairly confident in my Fortran skills. However, one of my first challenges was simply understanding what is now considered <a href="proxy.php?url=https://fortranwiki.org/fortran/show/Modernizing+Old+Fortran" rel="noopener noreferrer">"legacy" Fortran code</a>. I highlight three initial hurdles below.</p> <h4> Formatting </h4> <p>LAPACK was originally written in FORTRAN 77 (F77). While the library was moved to Fortran 90 in version 3.2 (2008), legacy conventions still persist in the reference implementation. One of the most visible of those conventions is formatting.</p> <p>Developers writing F77 programs did so using a fixed form layout inherited from punched cards. This layout had strict requirements concerning the use of character columns:</p> <ul> <li> Comments occupying an entire line must begin with a special character (e.g., <code>*</code>, <code>!</code>, or <code>C</code>) in the first column.</li> <li> For non-comment lines, 1) the first five columns must be blank or contain a numeric label, 2) column six is reserved for continuation characters, 3) executable statements must begin at column seven, and 4) any code beyond column 72 was ignored.</li> </ul> <p>Fortran 90 introduced the free form layout which removed column and line length restrictions and settled on <code>!</code> as the comment character. The following code snippet shows the reference implementation for the LAPACK routine <a href="proxy.php?url=https://www.netlib.org/lapack/explore-html/da/dcf/dlacpy_8f_source.html" rel="noopener noreferrer"><code>dlacpy</code></a>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight fortran"><code><span class="w"> </span><span class="k">SUBROUTINE</span><span class="w"> </span><span class="n">dlacpy</span><span class="p">(</span><span class="w"> </span><span class="n">UPLO</span><span class="p">,</span><span class="w"> </span><span class="n">M</span><span class="p">,</span><span class="w"> </span><span class="n">N</span><span class="p">,</span><span class="w"> </span><span class="n">A</span><span class="p">,</span><span class="w"> </span><span class="n">LDA</span><span class="p">,</span><span class="w"> </span><span class="n">B</span><span class="p">,</span><span class="w"> </span><span class="n">LDB</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">LAPACK</span><span class="w"> </span><span class="n">auxiliary</span><span class="w"> </span><span class="n">routine</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">LAPACK</span><span class="w"> </span><span class="k">is</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">software</span><span class="w"> </span><span class="n">package</span><span class="w"> </span><span class="n">provided</span><span class="w"> </span><span class="n">by</span><span class="w"> </span><span class="n">Univ</span><span class="err">.</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">Tennessee</span><span class="p">,</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">Univ</span><span class="err">.</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">California</span><span class="w"> </span><span class="n">Berkeley</span><span class="p">,</span><span class="w"> </span><span class="n">Univ</span><span class="err">.</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">Colorado</span><span class="w"> </span><span class="n">Denver</span><span class="w"> </span><span class="n">and</span><span class="w"> </span><span class="n">NAG</span><span class="w"> </span><span class="n">Ltd</span><span class="err">..</span><span class="o">--</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="n">Scalar</span><span class="w"> </span><span class="n">Arguments</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="kt">CHARACTER</span><span class="w"> </span><span class="n">UPLO</span><span class="w"> </span><span class="kt">INTEGER</span><span class="w"> </span><span class="n">LDA</span><span class="p">,</span><span class="w"> </span><span class="n">LDB</span><span class="p">,</span><span class="w"> </span><span class="n">M</span><span class="p">,</span><span class="w"> </span><span class="n">N</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="n">Array</span><span class="w"> </span><span class="n">Arguments</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="kt">DOUBLE PRECISION</span><span class="w"> </span><span class="n">A</span><span class="p">(</span><span class="w"> </span><span class="n">LDA</span><span class="p">,</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="p">),</span><span class="w"> </span><span class="n">B</span><span class="p">(</span><span class="w"> </span><span class="n">LDB</span><span class="p">,</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">=====================================================================</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="n">Local</span><span class="w"> </span><span class="n">Scalars</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="kt">INTEGER</span><span class="w"> </span><span class="n">I</span><span class="p">,</span><span class="w"> </span><span class="n">J</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="k">External</span><span class="w"> </span><span class="n">Functions</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="kt">LOGICAL</span><span class="w"> </span><span class="n">LSAME</span><span class="w"> </span><span class="k">EXTERNAL</span><span class="w"> </span><span class="n">lsame</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="k">Intrinsic</span><span class="w"> </span><span class="n">Functions</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="k">INTRINSIC</span><span class="w"> </span><span class="nb">min</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="n">Executable</span><span class="w"> </span><span class="n">Statements</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">IF</span><span class="p">(</span><span class="w"> </span><span class="n">lsame</span><span class="p">(</span><span class="w"> </span><span class="n">uplo</span><span class="p">,</span><span class="w"> </span><span class="s1">'U'</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">THEN</span><span class="w"> </span><span class="k">DO</span><span class="w"> </span><span class="mi">20</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="k">DO</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nb">min</span><span class="p">(</span><span class="w"> </span><span class="n">j</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="n">b</span><span class="p">(</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">a</span><span class="p">(</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="k">CONTINUE</span><span class="w"> </span><span class="mi">20</span><span class="w"> </span><span class="k">CONTINUE</span><span class="w"> </span><span class="k">ELSE</span><span class="w"> </span><span class="k">IF</span><span class="p">(</span><span class="w"> </span><span class="n">lsame</span><span class="p">(</span><span class="w"> </span><span class="n">uplo</span><span class="p">,</span><span class="w"> </span><span class="s1">'L'</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">THEN</span><span class="w"> </span><span class="k">DO</span><span class="w"> </span><span class="mi">40</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="k">DO</span><span class="w"> </span><span class="mi">30</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">j</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="w"> </span><span class="n">b</span><span class="p">(</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">a</span><span class="p">(</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="mi">30</span><span class="w"> </span><span class="k">CONTINUE</span><span class="w"> </span><span class="mi">40</span><span class="w"> </span><span class="k">CONTINUE</span><span class="w"> </span><span class="k">ELSE</span><span class="w"> </span><span class="k">DO</span><span class="w"> </span><span class="mi">60</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="k">DO</span><span class="w"> </span><span class="mi">50</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="w"> </span><span class="n">b</span><span class="p">(</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">a</span><span class="p">(</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="mi">50</span><span class="w"> </span><span class="k">CONTINUE</span><span class="w"> </span><span class="mi">60</span><span class="w"> </span><span class="k">CONTINUE</span><span class="w"> </span><span class="k">END</span><span class="w"> </span><span class="k">IF</span><span class="w"> </span><span class="k">RETURN</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">End</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">DLACPY</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">END</span><span class="w"> </span></code></pre> </div> <p>The next code snippet shows the same routine, but implemented using the free form layout introduced in Fortran 90.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight fortran"><code><span class="k">subroutine</span><span class="w"> </span><span class="n">dlacpy</span><span class="p">(</span><span class="w"> </span><span class="n">uplo</span><span class="p">,</span><span class="w"> </span><span class="n">M</span><span class="p">,</span><span class="w"> </span><span class="n">N</span><span class="p">,</span><span class="w"> </span><span class="n">A</span><span class="p">,</span><span class="w"> </span><span class="n">LDA</span><span class="p">,</span><span class="w"> </span><span class="n">B</span><span class="p">,</span><span class="w"> </span><span class="n">LDB</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">implicit</span><span class="w"> </span><span class="k">none</span><span class="w"> </span><span class="c1">! ..</span><span class="w"> </span><span class="c1">! Scalar arguments:</span><span class="w"> </span><span class="kt">character</span><span class="w"> </span><span class="p">::</span><span class="w"> </span><span class="n">uplo</span><span class="w"> </span><span class="kt">integer</span><span class="w"> </span><span class="p">::</span><span class="w"> </span><span class="n">LDA</span><span class="p">,</span><span class="w"> </span><span class="n">LDB</span><span class="p">,</span><span class="w"> </span><span class="n">M</span><span class="p">,</span><span class="w"> </span><span class="n">N</span><span class="w"> </span><span class="c1">! ..</span><span class="w"> </span><span class="c1">! Array arguments:</span><span class="w"> </span><span class="kt">double precision</span><span class="w"> </span><span class="p">::</span><span class="w"> </span><span class="n">A</span><span class="p">(</span><span class="w"> </span><span class="n">LDA</span><span class="p">,</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="p">),</span><span class="w"> </span><span class="n">B</span><span class="p">(</span><span class="w"> </span><span class="n">LDB</span><span class="p">,</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="c1">! ..</span><span class="w"> </span><span class="c1">! Local scalars:</span><span class="w"> </span><span class="kt">integer</span><span class="w"> </span><span class="p">::</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="c1">! ..</span><span class="w"> </span><span class="c1">! External functions:</span><span class="w"> </span><span class="kt">logical</span><span class="w"> </span><span class="n">LSAME</span><span class="w"> </span><span class="k">external</span><span class="w"> </span><span class="n">lsame</span><span class="w"> </span><span class="c1">! ..</span><span class="w"> </span><span class="c1">! Intrinsic functions:</span><span class="w"> </span><span class="k">intrinsic</span><span class="w"> </span><span class="nb">min</span><span class="w"> </span><span class="c1">! ..</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="w"> </span><span class="n">lsame</span><span class="p">(</span><span class="w"> </span><span class="n">uplo</span><span class="p">,</span><span class="w"> </span><span class="s1">'U'</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">then</span><span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nb">min</span><span class="p">(</span><span class="w"> </span><span class="n">j</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="n">b</span><span class="p">(</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">a</span><span class="p">(</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="w"> </span><span class="n">lsame</span><span class="p">(</span><span class="w"> </span><span class="n">uplo</span><span class="p">,</span><span class="w"> </span><span class="s1">'L'</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">then</span><span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">j</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="w"> </span><span class="n">b</span><span class="p">(</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">a</span><span class="p">(</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="w"> </span><span class="n">b</span><span class="p">(</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">a</span><span class="p">(</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">subroutine</span><span class="w"> </span><span class="n">dlacpy</span><span class="w"> </span></code></pre> </div> <p>As may be observed, by removing column restrictions and moving away from the F77 convention of writing specifiers in ALL CAPS, modern Fortran code is more visibly consistent and thus more readable.</p> <h4> Labeled control structures </h4> <p>Another common practice in LAPACK routines is the use of labeled control structures. For example, consider the following code snippet in which the label <code>10</code> must match a corresponding <code>CONTINUE</code>.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight fortran"><code><span class="w"> </span><span class="k">DO</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="n">I</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="k">PRINT</span><span class="w"> </span><span class="o">*</span><span class="p">,</span><span class="w"> </span><span class="n">I</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="k">CONTINUE</span><span class="w"> </span></code></pre> </div> <p>Fortran 90 obviated the need for this practice and improved code readability by allowing one to use <code>end do</code> to end a <code>do</code> loop. This change is shown in the free form version of <code>dlacpy</code> provided above.</p> <h4> Assumed-size arrays </h4> <p>To allow flexibility in handling arrays of varying sizes, LAPACK routines commonly operate on arrays having an assumed-size. In the <code>dlacpy</code> routine above, the input matrix <code>A</code> is declared to be a two-dimensional array having an assumed-size according to the expression <code>A(LDA, *)</code>. This expression declares that <code>A</code> has <code>LDA</code> number of rows and uses <code>*</code> as a placeholder to indicate that the size of the second dimension is determined by the calling program.</p> <p>One consequence of using assumed-size arrays is that compilers are unable to perform bounds checking on the unspecified dimension. Thus, <a href="proxy.php?url=https://fortran-lang.discourse.group/t/matrix-index-pointer-confusion/8453/5" rel="noopener noreferrer">current best practice</a> is to use explicit interfaces and assumed-shape arrays (e.g., <code>A(LDA,:)</code>) in order to prevent out-of-bounds memory access. This stated, the use of assumed-shape arrays can be problematic when needing to pass sub-matrices to other functions, as doing so requires slicing which often results in compilers creating internal copies of array data.</p> <h4> Migrating to Fortran 95 </h4> <p>Needless to say, it took me a while to adjust to LAPACK conventions and adopt a LAPACK mindset. However, being something of a purist, if I was going to be porting over routines anyway, I at least wanted to bring those routines I did manage to port into a more modern age in hopes of improving code readability and future maintenance. So, after discussing things with stdlib maintainers, I settled on migrating routines to Fortran 95, which, while not the latest and greatest Fortran version, seemed to strike the right balance between maintaining the look-and-feel of the original implementations, ensuring (good enough) backward compatibility, and taking advantage of newer syntactical features.</p> <h3> Test Coverage </h3> <p>One of the problems with pursuing a bottom-up approach to adding LAPACK support is that explicit unit tests for lower-level utility routines are often non-existent in LAPACK. LAPACK's test suite largely employs a hierarchical testing philosophy in which testing higher-level routines is assumed to ensure that their dependent lower-level routines are functioning correctly as part of an overall workflow. While one can argue that focusing on integration testing over unit testing for lower-level routines is reasonable, as adding tests for every routine could potentially increase the maintenance burden and complexity of LAPACK's testing framework, it means that we couldn't readily rely on prior art for unit testing and would have to come up with comprehensive standalone unit tests for each lower-level routine on our own.</p> <h3> Documentation </h3> <p>Along a similar vein to test coverage, outside of LAPACK itself, finding real-world documented examples showcasing the use of lower-level routines was challenging. While LAPACK routines are consistently preceded by a documentation comment providing descriptions of input arguments and possible return values, without code examples, visualizing and grokking expected input and output values can be challenging, especially when dealing with specialized matrices. And while neither the absence of unit tests nor documented examples is the end of the world, it meant that adding LAPACK support to stdlib would be more of a slog than I expected. Writing benchmarks, tests, examples, and documentation was simply going to require more time and effort, potentially limiting the number of routines I could implement during the internship.</p> <h3> Memory layouts </h3> <p>When storing matrix elements in linear memory, one has two choices: either store columns contiguously or rows contiguously (see Figure 2). The former memory layout is referred to as <strong>column-major</strong> order and the latter as <strong>row-major</strong> order.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fapfhr5wmjsskehx8py7d.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fapfhr5wmjsskehx8py7d.png" alt="Schematic demonstrating storing matrix elements in linear memory in either column-major or row-major order" width="800" height="421"></a></p> <p><em>Figure 2: Schematic demonstrating storing matrix elements in linear memory in either (a) column-major (Fortran-style) or (b) row-major (C-style) order. The choice of which layout to use is largely a matter of convention.</em></p> <p>The choice of which layout to use is largely a matter of convention. For example, Fortran stores elements in column-major order, and C stores elements in row-major order. Higher-level libraries, such as NumPy and stdlib, support both column- and row-major orders, allowing you to configure the layout of a multi-dimensional array during array creation.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">import</span> <span class="nx">asarray</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@stdlib/ndarray-array</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// Create a row-major array:</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="nf">asarray</span><span class="p">([</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">2.0</span><span class="p">,</span> <span class="mf">3.0</span><span class="p">,</span> <span class="mf">4.0</span><span class="p">],</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">shape</span><span class="dl">'</span><span class="p">:</span> <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span> <span class="dl">'</span><span class="s1">order</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">row-major</span><span class="dl">'</span> <span class="p">});</span> <span class="c1">// Create a column-major array:</span> <span class="kd">const</span> <span class="nx">y</span> <span class="o">=</span> <span class="nf">asarray</span><span class="p">([</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">3.0</span><span class="p">,</span> <span class="mf">2.0</span><span class="p">,</span> <span class="mf">4.0</span><span class="p">],</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">shape</span><span class="dl">'</span><span class="p">:</span> <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span> <span class="dl">'</span><span class="s1">order</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">column-major</span><span class="dl">'</span> <span class="p">});</span> </code></pre> </div> <p>While neither memory layout is inherently better than the other, arranging data to ensure sequential access in accordance with the conventions of the underlying storage model is critical in ensuring optimal performance. Modern CPUs are able to process sequential data more efficiently than non-sequential data, which is primarily due to CPU caching which, in turn, exploits spatial locality of reference.</p> <p>To demonstrate the performance impact of sequential vs non-sequential element access, consider the following function which copies all the elements from an <code>MxN</code> matrix <code>A</code> to another <code>MxN</code> matrix <code>B</code> and which does so assuming that matrix elements are stored in column-major order.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="cm">/** * Copies elements from `A` to `B`. * * @param {integer} M - number of rows * @param {integer} N - number of columns * @param {Array} A - source matrix * @param {integer} strideA1 - index increment to move to the next element in a column * @param {integer} strideA2 - index increment to move to the next element in a row * @param {integer} offsetA - index of the first indexed element in `A` * @param {Array} B - source matrix * @param {integer} strideB1 - index increment to move to the next element in a column * @param {integer} strideB2 - index increment to move to the next element in a row * @param {integer} offsetB - index of the first indexed element in `B` */</span> <span class="kd">function</span> <span class="nf">copy</span><span class="p">(</span><span class="nx">M</span><span class="p">,</span> <span class="nx">N</span><span class="p">,</span> <span class="nx">A</span><span class="p">,</span> <span class="nx">strideA1</span><span class="p">,</span> <span class="nx">strideA2</span><span class="p">,</span> <span class="nx">offsetA</span><span class="p">,</span> <span class="nx">B</span><span class="p">,</span> <span class="nx">strideB1</span><span class="p">,</span> <span class="nx">strideB2</span><span class="p">,</span> <span class="nx">offsetB</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Initialize loop bounds:</span> <span class="kd">const</span> <span class="nx">S0</span> <span class="o">=</span> <span class="nx">M</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">S1</span> <span class="o">=</span> <span class="nx">N</span><span class="p">;</span> <span class="c1">// For column-major matrices, the first dimension has the fastest changing index.</span> <span class="c1">// Compute "pointer" increments accordingly:</span> <span class="kd">const</span> <span class="nx">da0</span> <span class="o">=</span> <span class="nx">strideA1</span><span class="p">;</span> <span class="c1">// pointer increment for innermost loop</span> <span class="kd">const</span> <span class="nx">da1</span> <span class="o">=</span> <span class="nx">strideA2</span> <span class="o">-</span> <span class="p">(</span><span class="nx">S0</span><span class="o">*</span><span class="nx">strideA1</span><span class="p">);</span> <span class="c1">// pointer increment for outermost loop</span> <span class="kd">const</span> <span class="nx">db0</span> <span class="o">=</span> <span class="nx">strideB1</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">db1</span> <span class="o">=</span> <span class="nx">strideB2</span> <span class="o">-</span> <span class="p">(</span><span class="nx">S0</span><span class="o">*</span><span class="nx">strideB1</span><span class="p">);</span> <span class="c1">// Initialize "pointers" to the first indexed elements in the respective arrays:</span> <span class="kd">let</span> <span class="nx">ia</span> <span class="o">=</span> <span class="nx">offsetA</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">ib</span> <span class="o">=</span> <span class="nx">offsetB</span><span class="p">;</span> <span class="c1">// Iterate over matrix dimensions:</span> <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">i1</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i1</span> <span class="o">&lt;</span> <span class="nx">S1</span><span class="p">;</span> <span class="nx">i1</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">i0</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i0</span> <span class="o">&lt;</span> <span class="nx">S0</span><span class="p">;</span> <span class="nx">i0</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="nx">B</span><span class="p">[</span><span class="nx">ib</span><span class="p">]</span> <span class="o">=</span> <span class="nx">A</span><span class="p">[</span><span class="nx">ia</span><span class="p">];</span> <span class="nx">ia</span> <span class="o">+=</span> <span class="nx">da0</span><span class="p">;</span> <span class="nx">ib</span> <span class="o">+=</span> <span class="nx">db0</span><span class="p">;</span> <span class="p">}</span> <span class="nx">ia</span> <span class="o">+=</span> <span class="nx">da1</span><span class="p">;</span> <span class="nx">ib</span> <span class="o">+=</span> <span class="nx">db1</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>Let <code>A</code> and <code>B</code> be the following <code>3x2</code> matrices:</p> <p> </p> <div class="katex-element"> <span class="katex-display"><span class="katex"><span class="katex-mathml">A=[123456], B=[000000]A = \begin{bmatrix}1 &amp; 2 \\3 &amp; 4 \\5 &amp; 6\end{bmatrix},\ B = \begin{bmatrix}0 &amp; 0 \\0 &amp; 0 \\0 &amp; 0\end{bmatrix}</span><span class="katex-html"><span class="base"><span class="strut"></span><span class="mord mathnormal">A</span><span class="mspace"></span><span class="mrel">=</span><span class="mspace"></span></span><span class="base"><span class="strut"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist"><span><span class="pstrut"></span><span class="delimsizinginner delim-size4"><span>⎣</span></span></span><span><span class="pstrut"></span><span></span></span><span><span class="pstrut"></span><span class="delimsizinginner delim-size4"><span>⎡</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist"><span><span class="pstrut"></span><span class="mord"><span class="mord">1</span></span></span><span><span class="pstrut"></span><span class="mord"><span class="mord">3</span></span></span><span><span class="pstrut"></span><span class="mord"><span class="mord">5</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist"><span></span></span></span></span></span><span class="arraycolsep"></span><span class="arraycolsep"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist"><span><span class="pstrut"></span><span class="mord"><span class="mord">2</span></span></span><span><span class="pstrut"></span><span class="mord"><span class="mord">4</span></span></span><span><span class="pstrut"></span><span class="mord"><span class="mord">6</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist"><span><span class="pstrut"></span><span class="delimsizinginner delim-size4"><span>⎦</span></span></span><span><span class="pstrut"></span><span></span></span><span><span class="pstrut"></span><span class="delimsizinginner delim-size4"><span>⎤</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist"><span></span></span></span></span></span></span></span><span class="mspace"></span><span class="mpunct">,</span><span class="mspace"> </span><span class="mspace"></span><span class="mord mathnormal">B</span><span class="mspace"></span><span class="mrel">=</span><span class="mspace"></span></span><span class="base"><span class="strut"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist"><span><span class="pstrut"></span><span class="delimsizinginner delim-size4"><span>⎣</span></span></span><span><span class="pstrut"></span><span></span></span><span><span class="pstrut"></span><span class="delimsizinginner delim-size4"><span>⎡</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist"><span><span class="pstrut"></span><span class="mord"><span class="mord">0</span></span></span><span><span class="pstrut"></span><span class="mord"><span class="mord">0</span></span></span><span><span class="pstrut"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist"><span></span></span></span></span></span><span class="arraycolsep"></span><span class="arraycolsep"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist"><span><span class="pstrut"></span><span class="mord"><span class="mord">0</span></span></span><span><span class="pstrut"></span><span class="mord"><span class="mord">0</span></span></span><span><span class="pstrut"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist"><span><span class="pstrut"></span><span class="delimsizinginner delim-size4"><span>⎦</span></span></span><span><span class="pstrut"></span><span></span></span><span><span class="pstrut"></span><span class="delimsizinginner delim-size4"><span>⎤</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist"><span></span></span></span></span></span></span></span></span></span></span></span> </div> <p>When both <code>A</code> and <code>B</code> are stored in column-major order, we can call the <code>copy</code> routine as follows:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">A</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">6</span><span class="p">];</span> <span class="kd">const</span> <span class="nx">B</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">];</span> <span class="nf">copy</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="nx">A</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">B</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> </code></pre> </div> <p>If, however, <code>A</code> and <code>B</code> are both stored in row-major order, the call signature changes to<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">A</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">];</span> <span class="kd">const</span> <span class="nx">B</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">];</span> <span class="nf">copy</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="nx">A</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">B</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> </code></pre> </div> <p>Notice that, in the latter scenario, we fail to access elements in sequential order within the innermost loop, as <code>da0</code> is <code>2</code> and <code>da1</code> is <code>-5</code> and similarly for <code>db0</code> and <code>db1</code>. Instead, the array index "pointers" repeatedly skip ahead before returning to earlier elements in linear memory, with <code>ia = {0, 2, 4, 1, 3, 5}</code> and <code>ib</code> the same. In Figure 3, we show the performance impact of non-sequential access.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gerv8tqdee70g0zvzhy.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gerv8tqdee70g0zvzhy.png" alt="Performance comparison of copying matrices stored in either row- or column-major order when the underlying algorithm assumes column-major order" width="800" height="431"></a></p> <p><em>Figure 3: Performance comparison when providing square column-major versus row-major matrices to <b>copy</b> when <b>copy</b> assumes sequential element access according to column-major order. The x-axis enumerates increasing matrix sizes (i.e., number of elements). All rates are normalized relative to column-major results for a corresponding matrix size.</em></p> <p>From the figure, we may observe that column- and row-major performance is roughly equivalent until we operate on square matrices having more than 1e5 elements (<code>M = N = ~316</code>). For 1e6 elements (<code>M = N = ~1000</code>), providing a row-major matrix to <code>copy</code> results in a greater than 25% performance decrease. For 1e7 elements (<code>M = N = ~3160</code>), we observe a greater than 85% performance decrease. The significant performance impact may be attributed to decreased locality of reference when operating on row-major matrices having large row sizes.</p> <p>Given that it is written in Fortran, LAPACK assumes column-major access order and implements its algorithms accordingly. This presents issues for libraries, such as stdlib, which not only support row-major order, but make it their default memory layout. Were we to simply port LAPACK's Fortran implementations to JavaScript, users providing row-major matrices would experience adverse performance impacts stemming from non-sequential access.</p> <p>To mitigate adverse performance impacts, we borrowed an idea from <a href="proxy.php?url=https://github.com/flame/blis" rel="noopener noreferrer">BLIS</a>, a BLAS-like library supporting both row- and column-major memory layouts in BLAS routines, and decided to create modified LAPACK implementations when porting routines from Fortran to JavaScript and C that explicitly accommodate both column- and row-major memory layouts through separate stride parameters for each dimension. For some implementations, such as <code>dlacpy</code>, which is similar to the <code>copy</code> function defined above, incorporating separate and independent strides is straightforward, often involving stride tricks and loop interchange, but, for others, the modifications turned out to be much less straightforward due to specialized matrix handling, varying access patterns, and combinatorial parameterization.</p> <h3> ndarrays </h3> <p>LAPACK routines primarily operate on matrices stored in linear memory and whose elements are accessed according to specified dimensions and the stride of the leading (i.e., first) dimension. Dimensions specify the number of elements in each row and column, respectively. The stride specifies how many elements in linear memory must be skipped in order to access the next element of a row. LAPACK assumes that elements belonging to the same column are always contiguous (i.e., adjacent in linear memory). Figure 4 provides a visual representation of LAPACK conventions (specifically, schematics (a) and (b)).</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmsxyam36858qp3cisrdc.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmsxyam36858qp3cisrdc.png" alt="Diagram illustrating the generalization of LAPACK strided array conventions to non-contiguous strided arrays" width="800" height="258"></a></p> <p><em>Figure 4: Schematics illustrating the generalization of LAPACK strided array conventions to non-contiguous strided arrays. a) A 5-by-5 contiguous matrix stored in column-major order. b) A 3-by-3 non-contiguous sub-matrix stored in column-major order. Sub-matrices can be operated on in LAPACK by providing a pointer to the first indexed element and specifying the stride of the leading (i.e., first) dimension. In this case, the stride of leading dimension is five, even though there are only three elements per column, due to the non-contiguity of sub-matrix elements in linear memory when stored as part of a larger matrix. In LAPACK, the stride of the trailing (i.e., second) dimension is always assumed to be unity. c) A 3-by-3 non-contiguous sub-matrix stored in column-major order having non-unit strides and generalizing LAPACK stride conventions to both leading and trailing dimensions. This generalization underpins stdlib's multi-dimensional arrays (also referred to as "ndarrays").</em></p> <p>Libraries, such as NumPy and stdlib, generalize LAPACK's strided array conventions to support</p> <ol> <li>non-unit strides in the last dimension (see Figure 4 (c)). LAPACK assumes that the last dimension of a matrix always has unit stride (i.e., elements within a column are stored contiguously in linear memory).</li> <li>negative strides for any dimension. LAPACK requires that the stride of a leading matrix dimension be positive.</li> <li>multi-dimensional arrays having more than two dimensions. LAPACK only explicitly supports strided vectors and (sub)matrices.</li> </ol> <p>Support for non-unit strides in the last dimension ensures support for O(1) creation of non-contiguous views of linear memory without requiring explicit data movement. These views are often called "slices". As an example, consider the following code snippet which creates such views using APIs provided by stdlib.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">import</span> <span class="nx">linspace</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@stdlib/array-linspace</span><span class="dl">'</span> <span class="k">import</span> <span class="nx">FancyArray</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@stdlib/ndarray-fancy</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// Define a two-dimensional array similar to that shown in Figure 4 (a):</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FancyArray</span><span class="p">(</span><span class="dl">'</span><span class="s1">float64</span><span class="dl">'</span><span class="p">,</span> <span class="nf">linspace</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">24</span><span class="p">,</span> <span class="mi">25</span><span class="p">),</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">],</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span> <span class="mi">0</span><span class="p">,</span> <span class="dl">'</span><span class="s1">row-major</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// returns &lt;FancyArray&gt;</span> <span class="c1">// Create a sub-matrix view similar to that shown in Figure 4 (b):</span> <span class="kd">const</span> <span class="nx">v1</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span><span class="dl">'</span><span class="s1">1:4,:3</span><span class="dl">'</span><span class="p">];</span> <span class="c1">// returns &lt;FancyArray&gt;</span> <span class="c1">// Create a sub-matrix view similar to that shown in Figure 4 (c):</span> <span class="kd">const</span> <span class="nx">v2</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span><span class="dl">'</span><span class="s1">::2,::2</span><span class="dl">'</span><span class="p">];</span> <span class="c1">// returns &lt;FancyArray&gt;</span> <span class="c1">// Assert that all arrays share the same underlying memory buffer:</span> <span class="kd">const</span> <span class="nx">b1</span> <span class="o">=</span> <span class="p">(</span><span class="nx">v1</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">buffer</span> <span class="o">===</span> <span class="nx">x</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">buffer</span><span class="p">);</span> <span class="c1">// returns true</span> <span class="kd">const</span> <span class="nx">b2</span> <span class="o">=</span> <span class="p">(</span><span class="nx">v2</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">buffer</span> <span class="o">===</span> <span class="nx">x</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">buffer</span><span class="p">);</span> <span class="c1">// returns true</span> </code></pre> </div> <p>Without support for non-unit strides in the last dimension, returning a view from the expression <code>x['::2,::2']</code> would not be possible, as one would need to copy selected elements to a new linear memory buffer in order to ensure contiguity.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flyjzwupbpovsfduginh2.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flyjzwupbpovsfduginh2.png" alt="Schematics illustrating the use of stride manipulation to create flipped and rotated views of matrix elements stored in linear memory" width="717" height="1300"></a></p> <p><em>Figure 5: Schematics illustrating the use of stride manipulation to create flipped and rotated views of matrix elements stored in linear memory. For all sub-schematics, strides are listed as <code>[trailing_dimension, leading_dimension]</code>. Implicit for each schematic is an "offset", which indicates the index of the first indexed element such that, for a matrix <b>A</b>, the element <b>Aij</b> is resolved according to <code>i⋅strides[1] + j⋅strides[0] + offset</code>. a) Given a 3-by-3 matrix stored in column-major order, one can manipulate the strides of the leading and trailing dimensions to create views in which matrix elements along one or more axes are accessed in reverse order. b) Using similar stride manipulation, one can create rotated views of matrix elements relative to their arrangement within linear memory.</em></p> <p>Support for negative strides enables O(1) reversal and rotation of elements along one or more dimensions (see Figure 5). For example, to flip a matrix top-to-bottom and left-to-right, one need only negate the strides. Building on the previous code snippet, the following code snippet demonstrates reversing elements about one or more axes.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">import</span> <span class="nx">linspace</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@stdlib/array-linspace</span><span class="dl">'</span> <span class="k">import</span> <span class="nx">FancyArray</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@stdlib/ndarray-fancy</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// Define a two-dimensional array similar to that shown in Figure 5 (a):</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FancyArray</span><span class="p">(</span><span class="dl">'</span><span class="s1">float64</span><span class="dl">'</span><span class="p">,</span> <span class="nf">linspace</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">9</span><span class="p">),</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span> <span class="mi">0</span><span class="p">,</span> <span class="dl">'</span><span class="s1">row-major</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// Reverse elements along each row:</span> <span class="kd">const</span> <span class="nx">v1</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span><span class="dl">'</span><span class="s1">::-1,:</span><span class="dl">'</span><span class="p">];</span> <span class="c1">// Reverse elements along each column:</span> <span class="kd">const</span> <span class="nx">v2</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span><span class="dl">'</span><span class="s1">:,::-1</span><span class="dl">'</span><span class="p">];</span> <span class="c1">// Reverse elements along both columns and rows:</span> <span class="kd">const</span> <span class="nx">v3</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span><span class="dl">'</span><span class="s1">::-1,::-1</span><span class="dl">'</span><span class="p">];</span> <span class="c1">// Assert that all arrays share the same underlying memory buffer:</span> <span class="kd">const</span> <span class="nx">b1</span> <span class="o">=</span> <span class="p">(</span><span class="nx">v1</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">buffer</span> <span class="o">===</span> <span class="nx">x</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">buffer</span><span class="p">);</span> <span class="c1">// returns true</span> <span class="kd">const</span> <span class="nx">b2</span> <span class="o">=</span> <span class="p">(</span><span class="nx">v2</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">buffer</span> <span class="o">===</span> <span class="nx">x</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">buffer</span><span class="p">);</span> <span class="c1">// returns true</span> <span class="kd">const</span> <span class="nx">b3</span> <span class="o">=</span> <span class="p">(</span><span class="nx">v3</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">buffer</span> <span class="o">===</span> <span class="nx">x</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">buffer</span><span class="p">);</span> <span class="c1">// returns true</span> </code></pre> </div> <p>Implicit in the discussion of negative strides is the need for an "offset" parameter which indicates the index of the first indexed element in linear memory. For a strided multi-dimensional array <em>A</em> and a list of strides <em>s</em>, the index corresponding to element <em>Aij⋅⋅⋅n</em> can be resolved according to the equation</p> <div class="katex-element"> <span class="katex-display"><span class="katex"><span class="katex-mathml">idx=offset+i⋅s0+j⋅s1+…+n⋅sN−1\textrm{idx} = \textrm{offset} + i \cdot s_0 + j \cdot s_1 + \ldots + n \cdot s_{N-1}</span><span class="katex-html"><span class="base"><span class="strut"></span><span class="mord text"><span class="mord textrm">idx</span></span><span class="mspace"></span><span class="mrel">=</span><span class="mspace"></span></span><span class="base"><span class="strut"></span><span class="mord text"><span class="mord textrm">offset</span></span><span class="mspace"></span><span class="mbin">+</span><span class="mspace"></span></span><span class="base"><span class="strut"></span><span class="mord mathnormal">i</span><span class="mspace"></span><span class="mbin">⋅</span><span class="mspace"></span></span><span class="base"><span class="strut"></span><span class="mord"><span class="mord mathnormal">s</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist"><span><span class="pstrut"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist"><span></span></span></span></span></span></span><span class="mspace"></span><span class="mbin">+</span><span class="mspace"></span></span><span class="base"><span class="strut"></span><span class="mord mathnormal">j</span><span class="mspace"></span><span class="mbin">⋅</span><span class="mspace"></span></span><span class="base"><span class="strut"></span><span class="mord"><span class="mord mathnormal">s</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist"><span><span class="pstrut"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist"><span></span></span></span></span></span></span><span class="mspace"></span><span class="mbin">+</span><span class="mspace"></span></span><span class="base"><span class="strut"></span><span class="minner">…</span><span class="mspace"></span><span class="mbin">+</span><span class="mspace"></span></span><span class="base"><span class="strut"></span><span class="mord mathnormal">n</span><span class="mspace"></span><span class="mbin">⋅</span><span class="mspace"></span></span><span class="base"><span class="strut"></span><span class="mord"><span class="mord mathnormal">s</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist"><span><span class="pstrut"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">N</span><span class="mbin mtight">−</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist"><span></span></span></span></span></span></span></span></span></span></span> </div> <p>where <em>N</em> is the number of array dimensions and <em>sk</em> corresponds to kth stride.</p> <p>In BLAS and LAPACK routines supporting negative strides—something which is only supported when operating on strided vectors (e.g., see <code>daxpy</code> above)—the index offset is computed using logic similar to the following code snippet:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight c"><code><span class="k">if</span> <span class="p">(</span><span class="n">stride</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">offset</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">M</span><span class="p">)</span> <span class="o">*</span> <span class="n">stride</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">offset</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>where <code>M</code> is the number of vector elements. This implicitly assumes that a provided data pointer points to the beginning of linear memory for a vector. In languages supporting pointers, such as C, in order to operate on a different region of linear memory, one typically adjusts a pointer using pointer arithmetic prior to function invocation, which is relatively cheap and straightforward, at least for the one-dimensional case.</p> <p>For example, returning to <code>c_daxpy</code> as defined above, we can use pointer arithmetic to limit element access to five elements within linear memory beginning at the eleventh and sixteenth elements (note: zero-based indexing) of an input and output array, respectively, as shown in the following code snippet.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight c"><code><span class="c1">// Define data arrays:</span> <span class="k">const</span> <span class="kt">double</span> <span class="n">X</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{...};</span> <span class="kt">double</span> <span class="n">Y</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{...};</span> <span class="c1">// Specify the indices of the elements which begin a desired memory region:</span> <span class="k">const</span> <span class="n">xoffset</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span> <span class="k">const</span> <span class="n">yoffset</span> <span class="o">=</span> <span class="mi">15</span><span class="p">;</span> <span class="c1">// Limit the operation to only elements within the desired memory region:</span> <span class="n">c_daxpy</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="n">X</span><span class="o">+</span><span class="n">xoffset</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">Y</span><span class="o">+</span><span class="n">yoffset</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span> </code></pre> </div> <p>However, in JavaScript, which does not support explicit pointer arithmetic for binary buffers, one must <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/1c56b737ec018cc818cebf19e5c7947fa684e126/lib/node_modules/%40stdlib/strided/base/offset-view" rel="noopener noreferrer">explicitly instantiate</a> new typed array objects having a desired <a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray#parameters" rel="noopener noreferrer">byte offset</a>. In the following code snippet, in order to achieve the same results as the C example above, we must resolve a typed array constructor, compute a new byte offset, compute a new typed array length, and create a new typed array instance.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="cm">/** * Returns a typed array view having the same data type as a provided input typed * array and starting at a specified index offset. * * @param {TypedArray} x - input array * @param {integer} offset - starting index * @returns {TypedArray} typed array view */</span> <span class="kd">function</span> <span class="nf">offsetView</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">offset</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nx">x</span><span class="p">.</span><span class="nf">constructor</span><span class="p">(</span><span class="nx">x</span><span class="p">.</span><span class="nx">buffer</span><span class="p">,</span> <span class="nx">x</span><span class="p">.</span><span class="nx">byteOffset</span><span class="o">+</span><span class="p">(</span><span class="nx">x</span><span class="p">.</span><span class="nx">BYTES_PER_ELEMENT</span><span class="o">*</span><span class="nx">offset</span><span class="p">),</span> <span class="nx">x</span><span class="p">.</span><span class="nx">length</span><span class="o">-</span><span class="nx">offset</span><span class="p">);</span> <span class="p">}</span> <span class="c1">// ...</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Float64Array</span><span class="p">([...]);</span> <span class="kd">const</span> <span class="nx">y</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Float64Array</span><span class="p">([...]);</span> <span class="c1">// ...</span> <span class="nf">daxpy</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mf">5.0</span><span class="p">,</span> <span class="nf">offsetView</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="mi">10</span><span class="p">),</span> <span class="mi">1</span><span class="p">,</span> <span class="nf">offsetView</span><span class="p">(</span><span class="nx">y</span><span class="p">,</span> <span class="mi">15</span><span class="p">),</span> <span class="mi">1</span><span class="p">);</span> </code></pre> </div> <p>For large array sizes, the cost of typed array instantiation is negligible compared to the time spent accessing and operating on individual array elements; however, for smaller array sizes, object instantiation can significantly impact performance.</p> <p>Accordingly, in order to avoid adverse object instantiation performance impacts, stdlib decouples an ndarray's data buffer from the location of the buffer element corresponding to the beginning of an <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/1c56b737ec018cc818cebf19e5c7947fa684e126/lib/node_modules/%40stdlib/ndarray/base/min-view-buffer-index" rel="noopener noreferrer">ndarray view</a>. This allows the slice expressions <code>x[2:,3:]</code> and <code>x[3:,1:]</code> to return new ndarray views <strong>without</strong> needing to instantiate new buffer instances, as demonstrated in the following code snippet.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">import</span> <span class="nx">linspace</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@stdlib/array-linspace</span><span class="dl">'</span> <span class="k">import</span> <span class="nx">FancyArray</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@stdlib/ndarray-fancy</span><span class="dl">'</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FancyArray</span><span class="p">(</span><span class="dl">'</span><span class="s1">float64</span><span class="dl">'</span><span class="p">,</span> <span class="nf">linspace</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">24</span><span class="p">,</span> <span class="mi">25</span><span class="p">),</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">],</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span> <span class="mi">0</span><span class="p">,</span> <span class="dl">'</span><span class="s1">row-major</span><span class="dl">'</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">v1</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span><span class="dl">'</span><span class="s1">2:,3:</span><span class="dl">'</span><span class="p">];</span> <span class="kd">const</span> <span class="nx">v2</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span><span class="dl">'</span><span class="s1">3:,1:</span><span class="dl">'</span><span class="p">];</span> <span class="c1">// Assert that all arrays share the same typed array data instance:</span> <span class="kd">const</span> <span class="nx">b1</span> <span class="o">=</span> <span class="p">(</span><span class="nx">v1</span><span class="p">.</span><span class="nx">data</span> <span class="o">===</span> <span class="nx">x</span><span class="p">.</span><span class="nx">data</span><span class="p">);</span> <span class="c1">// returns true</span> <span class="kd">const</span> <span class="nx">b2</span> <span class="o">=</span> <span class="p">(</span><span class="nx">v2</span><span class="p">.</span><span class="nx">data</span> <span class="o">===</span> <span class="nx">x</span><span class="p">.</span><span class="nx">data</span><span class="p">);</span> <span class="c1">// returns true</span> </code></pre> </div> <p>As a consequence of decoupling a data buffer from the beginning of an ndarray view, we similarly sought to avoid having to instantiate new typed array instances when calling into LAPACK routines with ndarray data. This meant creating modified LAPACK API signatures supporting explicit offset parameters for all strided vectors and matrices.</p> <p>For simplicity, let's return to the JavaScript implementation of <code>daxpy</code>, which was previously defined above.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">function</span> <span class="nf">daxpy</span><span class="p">(</span><span class="nx">N</span><span class="p">,</span> <span class="nx">alpha</span><span class="p">,</span> <span class="nx">X</span><span class="p">,</span> <span class="nx">strideX</span><span class="p">,</span> <span class="nx">Y</span><span class="p">,</span> <span class="nx">strideY</span><span class="p">)</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">ix</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">iy</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">i</span><span class="p">;</span> <span class="k">if </span><span class="p">(</span><span class="nx">N</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="k">if </span><span class="p">(</span><span class="nx">alpha</span> <span class="o">===</span> <span class="mf">0.0</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="k">if </span><span class="p">(</span><span class="nx">strideX</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">ix</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="nx">N</span><span class="p">)</span> <span class="o">*</span> <span class="nx">strideX</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nx">ix</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="k">if </span><span class="p">(</span><span class="nx">strideY</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">iy</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="nx">N</span><span class="p">)</span> <span class="o">*</span> <span class="nx">strideY</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nx">iy</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="k">for </span><span class="p">(</span><span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">N</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="nx">Y</span><span class="p">[</span><span class="nx">iy</span><span class="p">]</span> <span class="o">+=</span> <span class="nx">alpha</span> <span class="o">*</span> <span class="nx">X</span><span class="p">[</span><span class="nx">ix</span><span class="p">];</span> <span class="nx">ix</span> <span class="o">+=</span> <span class="nx">strideX</span><span class="p">;</span> <span class="nx">iy</span> <span class="o">+=</span> <span class="nx">strideY</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>As demonstrated in the following code snippet, we can modify the above signature and implementation such that the responsibility for resolving the first indexed element is shifted to the API consumer.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">function</span> <span class="nf">daxpy_ndarray</span><span class="p">(</span><span class="nx">N</span><span class="p">,</span> <span class="nx">alpha</span><span class="p">,</span> <span class="nx">X</span><span class="p">,</span> <span class="nx">strideX</span><span class="p">,</span> <span class="nx">offsetX</span><span class="p">,</span> <span class="nx">Y</span><span class="p">,</span> <span class="nx">strideY</span><span class="p">,</span> <span class="nx">offsetY</span><span class="p">)</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">ix</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">iy</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">i</span><span class="p">;</span> <span class="k">if </span><span class="p">(</span><span class="nx">N</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="k">if </span><span class="p">(</span><span class="nx">alpha</span> <span class="o">===</span> <span class="mf">0.0</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="nx">ix</span> <span class="o">=</span> <span class="nx">offsetX</span><span class="p">;</span> <span class="nx">iy</span> <span class="o">=</span> <span class="nx">offsetY</span><span class="p">;</span> <span class="k">for </span><span class="p">(</span><span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">N</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="nx">Y</span><span class="p">[</span><span class="nx">iy</span><span class="p">]</span> <span class="o">+=</span> <span class="nx">alpha</span> <span class="o">*</span> <span class="nx">X</span><span class="p">[</span><span class="nx">ix</span><span class="p">];</span> <span class="nx">ix</span> <span class="o">+=</span> <span class="nx">strideX</span><span class="p">;</span> <span class="nx">iy</span> <span class="o">+=</span> <span class="nx">strideY</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>For ndarrays, resolution happens during ndarray instantiation, making the invocation of <code>daxpy_ndarray</code> with ndarray data a straightforward passing of associated ndarray meta data. This is demonstrated in the following code snippet.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">import</span> <span class="nx">linspace</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@stdlib/array-linspace</span><span class="dl">'</span> <span class="k">import</span> <span class="nx">FancyArray</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@stdlib/ndarray-fancy</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// Create two ndarrays:</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FancyArray</span><span class="p">(</span><span class="dl">'</span><span class="s1">float64</span><span class="dl">'</span><span class="p">,</span> <span class="nf">linspace</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">24</span><span class="p">,</span> <span class="mi">25</span><span class="p">),</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">],</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span> <span class="mi">0</span><span class="p">,</span> <span class="dl">'</span><span class="s1">row-major</span><span class="dl">'</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">y</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FancyArray</span><span class="p">(</span><span class="dl">'</span><span class="s1">float64</span><span class="dl">'</span><span class="p">,</span> <span class="nf">linspace</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">24</span><span class="p">,</span> <span class="mi">25</span><span class="p">),</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">],</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span> <span class="mi">0</span><span class="p">,</span> <span class="dl">'</span><span class="s1">row-major</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// Create a view of `x` corresponding to every other element in the 3rd row:</span> <span class="kd">const</span> <span class="nx">v1</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span><span class="dl">'</span><span class="s1">2,1::2</span><span class="dl">'</span><span class="p">];</span> <span class="c1">// Create a view of `y` corresponding to every other element in the 3rd column:</span> <span class="kd">const</span> <span class="nx">v2</span> <span class="o">=</span> <span class="nx">y</span><span class="p">[</span><span class="dl">'</span><span class="s1">1::2,2</span><span class="dl">'</span><span class="p">];</span> <span class="c1">// Operate on the vectors:</span> <span class="nf">daxpy_ndarray</span><span class="p">(</span><span class="nx">v1</span><span class="p">.</span><span class="nx">length</span><span class="p">,</span> <span class="mf">5.0</span><span class="p">,</span> <span class="nx">v1</span><span class="p">.</span><span class="nx">data</span><span class="p">,</span> <span class="nx">v1</span><span class="p">.</span><span class="nx">strides</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nx">v1</span><span class="p">.</span><span class="nx">offset</span><span class="p">,</span> <span class="nx">v2</span><span class="p">.</span><span class="nx">data</span><span class="p">,</span> <span class="nx">v2</span><span class="p">.</span><span class="nx">strides</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nx">v2</span><span class="p">.</span><span class="nx">offset</span><span class="p">);</span> </code></pre> </div> <p>Similar to BLIS, we saw value in both conventional LAPACK API signatures (e.g., for backward compatibility) and modified API signatures (e.g., for minimizing adverse performance impacts), and thus, we settled on a plan to provide both conventional and modified APIs for each LAPACK routine. To minimize code duplication, we aimed to implement a common lower-level "base" implementation which could then be wrapped by higher-level APIs. While the changes for the BLAS routine <code>daxpy</code> shown above may appear relatively straightforward, the transformation of a conventional LAPACK routine and its expected behavior to a generalized implementation was often much less so.</p> <h2> dlaswp </h2> <p>Enough with the challenges! What does a final product look like?!</p> <p>Let's come full circle and bring this back to <code>dlaswp</code>, a LAPACK routine for performing a series of row interchanges on an input matrix according to a list of pivot indices. The following code snippet shows the reference LAPACK <a href="proxy.php?url=https://www.netlib.org/lapack/explore-html/d7/d6b/dlaswp_8f_source.html" rel="noopener noreferrer">Fortran implementation</a>.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight fortran"><code><span class="k">SUBROUTINE</span><span class="w"> </span><span class="n">dlaswp</span><span class="p">(</span><span class="w"> </span><span class="n">N</span><span class="p">,</span><span class="w"> </span><span class="n">A</span><span class="p">,</span><span class="w"> </span><span class="n">LDA</span><span class="p">,</span><span class="w"> </span><span class="n">K1</span><span class="p">,</span><span class="w"> </span><span class="n">K2</span><span class="p">,</span><span class="w"> </span><span class="n">IPIV</span><span class="p">,</span><span class="w"> </span><span class="n">INCX</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">LAPACK</span><span class="w"> </span><span class="n">auxiliary</span><span class="w"> </span><span class="n">routine</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">LAPACK</span><span class="w"> </span><span class="k">is</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">software</span><span class="w"> </span><span class="n">package</span><span class="w"> </span><span class="n">provided</span><span class="w"> </span><span class="n">by</span><span class="w"> </span><span class="n">Univ</span><span class="err">.</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">Tennessee</span><span class="p">,</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">Univ</span><span class="err">.</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">California</span><span class="w"> </span><span class="n">Berkeley</span><span class="p">,</span><span class="w"> </span><span class="n">Univ</span><span class="err">.</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">Colorado</span><span class="w"> </span><span class="n">Denver</span><span class="w"> </span><span class="n">and</span><span class="w"> </span><span class="n">NAG</span><span class="w"> </span><span class="n">Ltd</span><span class="err">..</span><span class="o">--</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="n">Scalar</span><span class="w"> </span><span class="n">Arguments</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="kt">INTEGER</span><span class="w"> </span><span class="n">INCX</span><span class="p">,</span><span class="w"> </span><span class="n">K1</span><span class="p">,</span><span class="w"> </span><span class="n">K2</span><span class="p">,</span><span class="w"> </span><span class="n">LDA</span><span class="p">,</span><span class="w"> </span><span class="n">N</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="n">Array</span><span class="w"> </span><span class="n">Arguments</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="kt">INTEGER</span><span class="w"> </span><span class="n">IPIV</span><span class="p">(</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="kt">DOUBLE PRECISION</span><span class="w"> </span><span class="n">A</span><span class="p">(</span><span class="w"> </span><span class="n">LDA</span><span class="p">,</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">=====================================================================</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="n">Local</span><span class="w"> </span><span class="n">Scalars</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="kt">INTEGER</span><span class="w"> </span><span class="n">I</span><span class="p">,</span><span class="w"> </span><span class="n">I1</span><span class="p">,</span><span class="w"> </span><span class="n">I2</span><span class="p">,</span><span class="w"> </span><span class="n">INC</span><span class="p">,</span><span class="w"> </span><span class="n">IP</span><span class="p">,</span><span class="w"> </span><span class="n">IX</span><span class="p">,</span><span class="w"> </span><span class="n">IX0</span><span class="p">,</span><span class="w"> </span><span class="n">J</span><span class="p">,</span><span class="w"> </span><span class="n">K</span><span class="p">,</span><span class="w"> </span><span class="n">N32</span><span class="w"> </span><span class="kt">DOUBLE PRECISION</span><span class="w"> </span><span class="n">TEMP</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="n">Executable</span><span class="w"> </span><span class="n">Statements</span><span class="w"> </span><span class="err">..</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">Interchange</span><span class="w"> </span><span class="n">row</span><span class="w"> </span><span class="n">I</span><span class="w"> </span><span class="n">with</span><span class="w"> </span><span class="n">row</span><span class="w"> </span><span class="n">IPIV</span><span class="p">(</span><span class="n">K1</span><span class="o">+</span><span class="p">(</span><span class="n">I</span><span class="o">-</span><span class="n">K1</span><span class="p">)</span><span class="o">*</span><span class="nb">abs</span><span class="p">(</span><span class="n">INCX</span><span class="p">))</span><span class="w"> </span><span class="n">for</span><span class="w"> </span><span class="n">each</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">rows</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">K1</span><span class="w"> </span><span class="n">through</span><span class="w"> </span><span class="n">K2</span><span class="err">.</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">IF</span><span class="p">(</span><span class="w"> </span><span class="n">incx</span><span class="ow">.GT.</span><span class="mi">0</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">THEN</span><span class="w"> </span><span class="n">ix0</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">k1</span><span class="w"> </span><span class="n">i1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">k1</span><span class="w"> </span><span class="n">i2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">k2</span><span class="w"> </span><span class="n">inc</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="k">ELSE</span><span class="w"> </span><span class="k">IF</span><span class="p">(</span><span class="w"> </span><span class="n">incx</span><span class="ow">.LT.</span><span class="mi">0</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">THEN</span><span class="w"> </span><span class="n">ix0</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">k1</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="w"> </span><span class="n">k1</span><span class="o">-</span><span class="n">k2</span><span class="w"> </span><span class="p">)</span><span class="o">*</span><span class="n">incx</span><span class="w"> </span><span class="n">i1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">k2</span><span class="w"> </span><span class="n">i2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">k1</span><span class="w"> </span><span class="n">inc</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">-1</span><span class="w"> </span><span class="k">ELSE</span><span class="w"> </span><span class="k">RETURN</span><span class="w"> </span><span class="k">END</span><span class="w"> </span><span class="k">IF</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">n32</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="p">/</span><span class="w"> </span><span class="mi">32</span><span class="w"> </span><span class="p">)</span><span class="o">*</span><span class="mi">32</span><span class="w"> </span><span class="k">IF</span><span class="p">(</span><span class="w"> </span><span class="n">n32</span><span class="ow">.NE.</span><span class="mi">0</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">THEN</span><span class="w"> </span><span class="k">DO</span><span class="w"> </span><span class="mi">30</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="n">n32</span><span class="p">,</span><span class="w"> </span><span class="mi">32</span><span class="w"> </span><span class="n">ix</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ix0</span><span class="w"> </span><span class="k">DO</span><span class="w"> </span><span class="mi">20</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">i1</span><span class="p">,</span><span class="w"> </span><span class="n">i2</span><span class="p">,</span><span class="w"> </span><span class="n">inc</span><span class="w"> </span><span class="n">ip</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ipiv</span><span class="p">(</span><span class="w"> </span><span class="n">ix</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">IF</span><span class="p">(</span><span class="w"> </span><span class="n">ip</span><span class="ow">.NE.</span><span class="n">i</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">THEN</span><span class="w"> </span><span class="k">DO</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="n">k</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">j</span><span class="p">,</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">31</span><span class="w"> </span><span class="n">temp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">a</span><span class="p">(</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">k</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="n">a</span><span class="p">(</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">k</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">a</span><span class="p">(</span><span class="w"> </span><span class="n">ip</span><span class="p">,</span><span class="w"> </span><span class="n">k</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="n">a</span><span class="p">(</span><span class="w"> </span><span class="n">ip</span><span class="p">,</span><span class="w"> </span><span class="n">k</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">temp</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="k">CONTINUE</span><span class="w"> </span><span class="k">END</span><span class="w"> </span><span class="k">IF</span><span class="w"> </span><span class="n">ix</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ix</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">incx</span><span class="w"> </span><span class="mi">20</span><span class="w"> </span><span class="k">CONTINUE</span><span class="w"> </span><span class="mi">30</span><span class="w"> </span><span class="k">CONTINUE</span><span class="w"> </span><span class="k">END</span><span class="w"> </span><span class="k">IF</span><span class="w"> </span><span class="k">IF</span><span class="p">(</span><span class="w"> </span><span class="n">n32</span><span class="ow">.NE.</span><span class="n">n</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">THEN</span><span class="w"> </span><span class="n">n32</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">n32</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="n">ix</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ix0</span><span class="w"> </span><span class="k">DO</span><span class="w"> </span><span class="mi">50</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">i1</span><span class="p">,</span><span class="w"> </span><span class="n">i2</span><span class="p">,</span><span class="w"> </span><span class="n">inc</span><span class="w"> </span><span class="n">ip</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ipiv</span><span class="p">(</span><span class="w"> </span><span class="n">ix</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">IF</span><span class="p">(</span><span class="w"> </span><span class="n">ip</span><span class="ow">.NE.</span><span class="n">i</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">THEN</span><span class="w"> </span><span class="k">DO</span><span class="w"> </span><span class="mi">40</span><span class="w"> </span><span class="n">k</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">n32</span><span class="p">,</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="n">temp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">a</span><span class="p">(</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">k</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="n">a</span><span class="p">(</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">k</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">a</span><span class="p">(</span><span class="w"> </span><span class="n">ip</span><span class="p">,</span><span class="w"> </span><span class="n">k</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="n">a</span><span class="p">(</span><span class="w"> </span><span class="n">ip</span><span class="p">,</span><span class="w"> </span><span class="n">k</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">temp</span><span class="w"> </span><span class="mi">40</span><span class="w"> </span><span class="k">CONTINUE</span><span class="w"> </span><span class="k">END</span><span class="w"> </span><span class="k">IF</span><span class="w"> </span><span class="n">ix</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ix</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">incx</span><span class="w"> </span><span class="mi">50</span><span class="w"> </span><span class="k">CONTINUE</span><span class="w"> </span><span class="k">END</span><span class="w"> </span><span class="k">IF</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">RETURN</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">End</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">DLASWP</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">END</span><span class="w"> </span></code></pre> </div> <p>To facilitate interfacing with the Fortran implementation from C, LAPACK provides a two-level C interface called <a href="proxy.php?url=https://netlib.org/lapack/lapacke.html" rel="noopener noreferrer">LAPACKE</a>, which wraps Fortran implementations and makes accommodations for both row- and column-major input and output matrices. The middle-level interface for <code>dlaswp</code> is shown in the following code snippet.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight c"><code><span class="n">lapack_int</span> <span class="nf">LAPACKE_dlaswp_work</span><span class="p">(</span> <span class="kt">int</span> <span class="n">matrix_layout</span><span class="p">,</span> <span class="n">lapack_int</span> <span class="n">n</span><span class="p">,</span> <span class="kt">double</span><span class="o">*</span> <span class="n">a</span><span class="p">,</span> <span class="n">lapack_int</span> <span class="n">lda</span><span class="p">,</span> <span class="n">lapack_int</span> <span class="n">k1</span><span class="p">,</span> <span class="n">lapack_int</span> <span class="n">k2</span><span class="p">,</span> <span class="k">const</span> <span class="n">lapack_int</span><span class="o">*</span> <span class="n">ipiv</span><span class="p">,</span> <span class="n">lapack_int</span> <span class="n">incx</span> <span class="p">)</span> <span class="p">{</span> <span class="n">lapack_int</span> <span class="n">info</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">if</span><span class="p">(</span> <span class="n">matrix_layout</span> <span class="o">==</span> <span class="n">LAPACK_COL_MAJOR</span> <span class="p">)</span> <span class="p">{</span> <span class="cm">/* Call LAPACK function and adjust info */</span> <span class="n">LAPACK_dlaswp</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">n</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">lda</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">k1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">k2</span><span class="p">,</span> <span class="n">ipiv</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">incx</span> <span class="p">);</span> <span class="k">if</span><span class="p">(</span> <span class="n">info</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="p">)</span> <span class="p">{</span> <span class="n">info</span> <span class="o">=</span> <span class="n">info</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if</span><span class="p">(</span> <span class="n">matrix_layout</span> <span class="o">==</span> <span class="n">LAPACK_ROW_MAJOR</span> <span class="p">)</span> <span class="p">{</span> <span class="n">lapack_int</span> <span class="n">lda_t</span> <span class="o">=</span> <span class="n">MAX</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="n">k2</span><span class="p">);</span> <span class="n">lapack_int</span> <span class="n">i</span><span class="p">;</span> <span class="k">for</span><span class="p">(</span> <span class="n">i</span> <span class="o">=</span> <span class="n">k1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">k2</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span> <span class="p">)</span> <span class="p">{</span> <span class="n">lda_t</span> <span class="o">=</span> <span class="n">MAX</span><span class="p">(</span> <span class="n">lda_t</span><span class="p">,</span> <span class="n">ipiv</span><span class="p">[</span><span class="n">k1</span> <span class="o">+</span> <span class="p">(</span> <span class="n">i</span> <span class="o">-</span> <span class="n">k1</span> <span class="p">)</span> <span class="o">*</span> <span class="n">ABS</span><span class="p">(</span> <span class="n">incx</span> <span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="p">);</span> <span class="p">}</span> <span class="kt">double</span><span class="o">*</span> <span class="n">a_t</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> <span class="cm">/* Check leading dimension(s) */</span> <span class="k">if</span><span class="p">(</span> <span class="n">lda</span> <span class="o">&lt;</span> <span class="n">n</span> <span class="p">)</span> <span class="p">{</span> <span class="n">info</span> <span class="o">=</span> <span class="o">-</span><span class="mi">4</span><span class="p">;</span> <span class="n">LAPACKE_xerbla</span><span class="p">(</span> <span class="s">"LAPACKE_dlaswp_work"</span><span class="p">,</span> <span class="n">info</span> <span class="p">);</span> <span class="k">return</span> <span class="n">info</span><span class="p">;</span> <span class="p">}</span> <span class="cm">/* Allocate memory for temporary array(s) */</span> <span class="n">a_t</span> <span class="o">=</span> <span class="p">(</span><span class="kt">double</span><span class="o">*</span><span class="p">)</span><span class="n">LAPACKE_malloc</span><span class="p">(</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">double</span><span class="p">)</span> <span class="o">*</span> <span class="n">lda_t</span> <span class="o">*</span> <span class="n">MAX</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="n">n</span><span class="p">)</span> <span class="p">);</span> <span class="k">if</span><span class="p">(</span> <span class="n">a_t</span> <span class="o">==</span> <span class="nb">NULL</span> <span class="p">)</span> <span class="p">{</span> <span class="n">info</span> <span class="o">=</span> <span class="n">LAPACK_TRANSPOSE_MEMORY_ERROR</span><span class="p">;</span> <span class="k">goto</span> <span class="n">exit_level_0</span><span class="p">;</span> <span class="p">}</span> <span class="cm">/* Transpose input matrices */</span> <span class="n">LAPACKE_dge_trans</span><span class="p">(</span> <span class="n">matrix_layout</span><span class="p">,</span> <span class="n">lda_t</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">lda</span><span class="p">,</span> <span class="n">a_t</span><span class="p">,</span> <span class="n">lda_t</span> <span class="p">);</span> <span class="cm">/* Call LAPACK function and adjust info */</span> <span class="n">LAPACK_dlaswp</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">n</span><span class="p">,</span> <span class="n">a_t</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">lda_t</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">k1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">k2</span><span class="p">,</span> <span class="n">ipiv</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">incx</span> <span class="p">);</span> <span class="n">info</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="cm">/* LAPACK call is ok! */</span> <span class="cm">/* Transpose output matrices */</span> <span class="n">LAPACKE_dge_trans</span><span class="p">(</span> <span class="n">LAPACK_COL_MAJOR</span><span class="p">,</span> <span class="n">lda_t</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="n">a_t</span><span class="p">,</span> <span class="n">lda_t</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">lda</span> <span class="p">);</span> <span class="cm">/* Release memory and exit */</span> <span class="n">LAPACKE_free</span><span class="p">(</span> <span class="n">a_t</span> <span class="p">);</span> <span class="nl">exit_level_0:</span> <span class="k">if</span><span class="p">(</span> <span class="n">info</span> <span class="o">==</span> <span class="n">LAPACK_TRANSPOSE_MEMORY_ERROR</span> <span class="p">)</span> <span class="p">{</span> <span class="n">LAPACKE_xerbla</span><span class="p">(</span> <span class="s">"LAPACKE_dlaswp_work"</span><span class="p">,</span> <span class="n">info</span> <span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">info</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span> <span class="n">LAPACKE_xerbla</span><span class="p">(</span> <span class="s">"LAPACKE_dlaswp_work"</span><span class="p">,</span> <span class="n">info</span> <span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="n">info</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>When called with a column-major matrix <code>a</code>, the wrapper <code>LAPACKE_dlaswp_work</code> simply passes along provided arguments to the Fortran implementation. However, when called with a row-major matrix <code>a</code>, the wrapper must allocate memory, explicitly transpose and copy <code>a</code> to a temporary matrix <code>a_t</code>, recompute the stride of the leading dimension, invoke <code>dlaswp</code> with <code>a_t</code>, transpose and copy the results stored in <code>a_t</code> to <code>a</code>, and finally free allocated memory. That is a fair amount of work and is common across most LAPACK routines.</p> <p>The following code snippet shows the reference LAPACK implementation <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/blob/1c56b737ec018cc818cebf19e5c7947fa684e126/lib/node_modules/%40stdlib/lapack/base/dlaswp/lib/base.js" rel="noopener noreferrer">ported</a> to JavaScript, with support for leading and trailing dimension strides, index offsets, and a strided vector containing pivot indices.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="c1">// File: base.js</span> <span class="c1">// ...</span> <span class="kd">const</span> <span class="nx">BLOCK_SIZE</span> <span class="o">=</span> <span class="mi">32</span><span class="p">;</span> <span class="c1">// ...</span> <span class="kd">function</span> <span class="nf">base</span><span class="p">(</span><span class="nx">N</span><span class="p">,</span> <span class="nx">A</span><span class="p">,</span> <span class="nx">strideA1</span><span class="p">,</span> <span class="nx">strideA2</span><span class="p">,</span> <span class="nx">offsetA</span><span class="p">,</span> <span class="nx">k1</span><span class="p">,</span> <span class="nx">k2</span><span class="p">,</span> <span class="nx">inck</span><span class="p">,</span> <span class="nx">IPIV</span><span class="p">,</span> <span class="nx">strideIPIV</span><span class="p">,</span> <span class="nx">offsetIPIV</span><span class="p">)</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">nrows</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">n32</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">tmp</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">row</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">ia1</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">ia2</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">ip</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">o</span><span class="p">;</span> <span class="c1">// Compute the number of rows to be interchanged:</span> <span class="k">if </span><span class="p">(</span><span class="nx">inck</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">nrows</span> <span class="o">=</span> <span class="nx">k2</span> <span class="o">-</span> <span class="nx">k1</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nx">nrows</span> <span class="o">=</span> <span class="nx">k1</span> <span class="o">-</span> <span class="nx">k2</span><span class="p">;</span> <span class="p">}</span> <span class="nx">nrows</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// If the order is row-major, we can delegate to the Level 1 routine `dswap` for interchanging rows...</span> <span class="k">if </span><span class="p">(</span><span class="nf">isRowMajor</span><span class="p">([</span><span class="nx">strideA1</span><span class="p">,</span> <span class="nx">strideA2</span><span class="p">]))</span> <span class="p">{</span> <span class="nx">ip</span> <span class="o">=</span> <span class="nx">offsetIPIV</span><span class="p">;</span> <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">k</span> <span class="o">=</span> <span class="nx">k1</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">nrows</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">,</span> <span class="nx">k</span> <span class="o">+=</span> <span class="nx">inck</span><span class="p">)</span> <span class="p">{</span> <span class="nx">row</span> <span class="o">=</span> <span class="nx">IPIV</span><span class="p">[</span><span class="nx">ip</span><span class="p">];</span> <span class="k">if </span><span class="p">(</span><span class="nx">row</span> <span class="o">!==</span> <span class="nx">k</span><span class="p">)</span> <span class="p">{</span> <span class="nf">dswap</span><span class="p">(</span><span class="nx">N</span><span class="p">,</span> <span class="nx">A</span><span class="p">,</span> <span class="nx">strideA2</span><span class="p">,</span> <span class="nx">offsetA</span><span class="o">+</span><span class="p">(</span><span class="nx">k</span><span class="o">*</span><span class="nx">strideA1</span><span class="p">),</span> <span class="nx">A</span><span class="p">,</span> <span class="nx">strideA2</span><span class="p">,</span> <span class="nx">offsetA</span><span class="o">+</span><span class="p">(</span><span class="nx">row</span><span class="o">*</span><span class="nx">strideA1</span><span class="p">));</span> <span class="p">}</span> <span class="nx">ip</span> <span class="o">+=</span> <span class="nx">strideIPIV</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">A</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// If the order is column-major, we need to use loop tiling to ensure efficient cache access when accessing matrix elements...</span> <span class="nx">n32</span> <span class="o">=</span> <span class="nf">floor</span><span class="p">(</span><span class="nx">N</span><span class="o">/</span><span class="nx">BLOCK_SIZE</span><span class="p">)</span> <span class="o">*</span> <span class="nx">BLOCK_SIZE</span><span class="p">;</span> <span class="k">if </span><span class="p">(</span><span class="nx">n32</span> <span class="o">!==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">j</span> <span class="o">&lt;</span> <span class="nx">n32</span><span class="p">;</span> <span class="nx">j</span> <span class="o">+=</span> <span class="nx">BLOCK_SIZE</span><span class="p">)</span> <span class="p">{</span> <span class="nx">ip</span> <span class="o">=</span> <span class="nx">offsetIPIV</span><span class="p">;</span> <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">k</span> <span class="o">=</span> <span class="nx">k1</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">nrows</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">,</span> <span class="nx">k</span> <span class="o">+=</span> <span class="nx">inck</span><span class="p">)</span> <span class="p">{</span> <span class="nx">row</span> <span class="o">=</span> <span class="nx">IPIV</span><span class="p">[</span><span class="nx">ip</span><span class="p">];</span> <span class="k">if </span><span class="p">(</span><span class="nx">row</span> <span class="o">!==</span> <span class="nx">k</span><span class="p">)</span> <span class="p">{</span> <span class="nx">ia1</span> <span class="o">=</span> <span class="nx">offsetA</span> <span class="o">+</span> <span class="p">(</span><span class="nx">k</span><span class="o">*</span><span class="nx">strideA1</span><span class="p">);</span> <span class="nx">ia2</span> <span class="o">=</span> <span class="nx">offsetA</span> <span class="o">+</span> <span class="p">(</span><span class="nx">row</span><span class="o">*</span><span class="nx">strideA1</span><span class="p">);</span> <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">n</span> <span class="o">=</span> <span class="nx">j</span><span class="p">;</span> <span class="nx">n</span> <span class="o">&lt;</span> <span class="nx">j</span><span class="o">+</span><span class="nx">BLOCK_SIZE</span><span class="p">;</span> <span class="nx">n</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="nx">o</span> <span class="o">=</span> <span class="nx">n</span> <span class="o">*</span> <span class="nx">strideA2</span><span class="p">;</span> <span class="nx">tmp</span> <span class="o">=</span> <span class="nx">A</span><span class="p">[</span><span class="nx">ia1</span><span class="o">+</span><span class="nx">o</span><span class="p">];</span> <span class="nx">A</span><span class="p">[</span><span class="nx">ia1</span><span class="o">+</span><span class="nx">o</span><span class="p">]</span> <span class="o">=</span> <span class="nx">A</span><span class="p">[</span><span class="nx">ia2</span><span class="o">+</span><span class="nx">o</span><span class="p">];</span> <span class="nx">A</span><span class="p">[</span><span class="nx">ia2</span><span class="o">+</span><span class="nx">o</span><span class="p">]</span> <span class="o">=</span> <span class="nx">tmp</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="nx">ip</span> <span class="o">+=</span> <span class="nx">strideIPIV</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="k">if </span><span class="p">(</span><span class="nx">n32</span> <span class="o">!==</span> <span class="nx">N</span><span class="p">)</span> <span class="p">{</span> <span class="nx">ip</span> <span class="o">=</span> <span class="nx">offsetIPIV</span><span class="p">;</span> <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">k</span> <span class="o">=</span> <span class="nx">k1</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">nrows</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">,</span> <span class="nx">k</span> <span class="o">+=</span> <span class="nx">inck</span><span class="p">)</span> <span class="p">{</span> <span class="nx">row</span> <span class="o">=</span> <span class="nx">IPIV</span><span class="p">[</span> <span class="nx">ip</span> <span class="p">];</span> <span class="k">if </span><span class="p">(</span><span class="nx">row</span> <span class="o">!==</span> <span class="nx">k</span><span class="p">)</span> <span class="p">{</span> <span class="nx">ia1</span> <span class="o">=</span> <span class="nx">offsetA</span> <span class="o">+</span> <span class="p">(</span><span class="nx">k</span><span class="o">*</span><span class="nx">strideA1</span><span class="p">);</span> <span class="nx">ia2</span> <span class="o">=</span> <span class="nx">offsetA</span> <span class="o">+</span> <span class="p">(</span><span class="nx">row</span><span class="o">*</span><span class="nx">strideA1</span><span class="p">);</span> <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">n</span> <span class="o">=</span> <span class="nx">n32</span><span class="p">;</span> <span class="nx">n</span> <span class="o">&lt;</span> <span class="nx">N</span><span class="p">;</span> <span class="nx">n</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="nx">o</span> <span class="o">=</span> <span class="nx">n</span> <span class="o">*</span> <span class="nx">strideA2</span><span class="p">;</span> <span class="nx">tmp</span> <span class="o">=</span> <span class="nx">A</span><span class="p">[</span><span class="nx">ia1</span><span class="o">+</span><span class="nx">o</span><span class="p">];</span> <span class="nx">A</span><span class="p">[</span><span class="nx">ia1</span><span class="o">+</span><span class="nx">o</span><span class="p">]</span> <span class="o">=</span> <span class="nx">A</span><span class="p">[</span><span class="nx">ia2</span><span class="o">+</span><span class="nx">o</span><span class="p">];</span> <span class="nx">A</span><span class="p">[</span><span class="nx">ia2</span><span class="o">+</span><span class="nx">o</span><span class="p">]</span> <span class="o">=</span> <span class="nx">tmp</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="nx">ip</span> <span class="o">+=</span> <span class="nx">strideIPIV</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">A</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>To provide an API having consistent behavior with conventional LAPACK, I then wrapped the above implementation and adapted input arguments to the "base" implementation, as shown in the following code snippet.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="c1">// File: dlaswp.js</span> <span class="c1">// ...</span> <span class="kd">const</span> <span class="nx">base</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">./base.js</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// ...</span> <span class="kd">function</span> <span class="nf">dlaswp</span><span class="p">(</span><span class="nx">order</span><span class="p">,</span> <span class="nx">N</span><span class="p">,</span> <span class="nx">A</span><span class="p">,</span> <span class="nx">LDA</span><span class="p">,</span> <span class="nx">k1</span><span class="p">,</span> <span class="nx">k2</span><span class="p">,</span> <span class="nx">IPIV</span><span class="p">,</span> <span class="nx">incx</span><span class="p">)</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">tmp</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">inc</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">sa1</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">sa2</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">io</span><span class="p">;</span> <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nf">isLayout</span><span class="p">(</span><span class="nx">order</span><span class="p">))</span> <span class="p">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">TypeError</span><span class="p">(</span><span class="nf">format</span><span class="p">(</span><span class="dl">'</span><span class="s1">invalid argument. First argument must be a valid order. Value: `%s`.</span><span class="dl">'</span><span class="p">,</span> <span class="nx">order</span><span class="p">));</span> <span class="p">}</span> <span class="k">if </span><span class="p">(</span><span class="nx">order</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">row-major</span><span class="dl">'</span> <span class="o">&amp;&amp;</span> <span class="nx">LDA</span> <span class="o">&lt;</span> <span class="nf">max</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nx">N</span><span class="p">))</span> <span class="p">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">RangeError</span><span class="p">(</span><span class="nf">format</span><span class="p">(</span><span class="dl">'</span><span class="s1">invalid argument. Fourth argument must be greater than or equal to max(1,%d). Value: `%d`.</span><span class="dl">'</span><span class="p">,</span> <span class="nx">N</span><span class="p">,</span> <span class="nx">LDA</span><span class="p">));</span> <span class="p">}</span> <span class="k">if </span><span class="p">(</span><span class="nx">incx</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">inc</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">io</span> <span class="o">=</span> <span class="nx">k1</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">incx</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">inc</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span> <span class="nx">io</span> <span class="o">=</span> <span class="nx">k1</span> <span class="o">+</span> <span class="p">((</span><span class="nx">k1</span><span class="o">-</span><span class="nx">k2</span><span class="p">)</span> <span class="o">*</span> <span class="nx">incx</span><span class="p">);</span> <span class="nx">tmp</span> <span class="o">=</span> <span class="nx">k1</span><span class="p">;</span> <span class="nx">k1</span> <span class="o">=</span> <span class="nx">k2</span><span class="p">;</span> <span class="nx">k2</span> <span class="o">=</span> <span class="nx">tmp</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">A</span><span class="p">;</span> <span class="p">}</span> <span class="k">if </span><span class="p">(</span><span class="nx">order</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">column-major</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span> <span class="nx">sa1</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">sa2</span> <span class="o">=</span> <span class="nx">LDA</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="c1">// order === 'row-major'</span> <span class="nx">sa1</span> <span class="o">=</span> <span class="nx">LDA</span><span class="p">;</span> <span class="nx">sa2</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span> <span class="nf">base</span><span class="p">(</span><span class="nx">N</span><span class="p">,</span> <span class="nx">A</span><span class="p">,</span> <span class="nx">sa1</span><span class="p">,</span> <span class="nx">sa2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">k1</span><span class="p">,</span> <span class="nx">k2</span><span class="p">,</span> <span class="nx">inc</span><span class="p">,</span> <span class="nx">IPIV</span><span class="p">,</span> <span class="nx">incx</span><span class="p">,</span> <span class="nx">io</span><span class="p">);</span> <span class="p">}</span> </code></pre> </div> <p>I subsequently wrote a separate but similar <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/blob/1c56b737ec018cc818cebf19e5c7947fa684e126/lib/node_modules/%40stdlib/lapack/base/dlaswp/lib/ndarray.js" rel="noopener noreferrer">wrapper</a> which provides an API mapping more directly to stdlib's multi-dimensional arrays and which performs some special handling when the direction in which to apply pivots is negative, as shown in the following code snippet.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="c1">// File: ndarray.js</span> <span class="kd">const</span> <span class="nx">base</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">./base.js</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// ...</span> <span class="kd">function</span> <span class="nf">dlaswp_ndarray</span><span class="p">(</span><span class="nx">N</span><span class="p">,</span> <span class="nx">A</span><span class="p">,</span> <span class="nx">strideA1</span><span class="p">,</span> <span class="nx">strideA2</span><span class="p">,</span> <span class="nx">offsetA</span><span class="p">,</span> <span class="nx">k1</span><span class="p">,</span> <span class="nx">k2</span><span class="p">,</span> <span class="nx">inck</span><span class="p">,</span> <span class="nx">IPIV</span><span class="p">,</span> <span class="nx">strideIPIV</span><span class="p">,</span> <span class="nx">offsetIPIV</span><span class="p">)</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">tmp</span><span class="p">;</span> <span class="k">if </span><span class="p">(</span><span class="nx">inck</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">offsetIPIV</span> <span class="o">+=</span> <span class="nx">k2</span> <span class="o">*</span> <span class="nx">strideIPIV</span><span class="p">;</span> <span class="nx">strideIPIV</span> <span class="o">*=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span> <span class="nx">tmp</span> <span class="o">=</span> <span class="nx">k1</span><span class="p">;</span> <span class="nx">k1</span> <span class="o">=</span> <span class="nx">k2</span><span class="p">;</span> <span class="nx">k2</span> <span class="o">=</span> <span class="nx">tmp</span><span class="p">;</span> <span class="nx">inck</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nx">offsetIPIV</span> <span class="o">+=</span> <span class="nx">k1</span> <span class="o">*</span> <span class="nx">strideIPIV</span><span class="p">;</span> <span class="nx">inck</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span> <span class="nf">base</span><span class="p">(</span><span class="nx">N</span><span class="p">,</span> <span class="nx">A</span><span class="p">,</span> <span class="nx">strideA1</span><span class="p">,</span> <span class="nx">strideA2</span><span class="p">,</span> <span class="nx">offsetA</span><span class="p">,</span> <span class="nx">k1</span><span class="p">,</span> <span class="nx">k2</span><span class="p">,</span> <span class="nx">inck</span><span class="p">,</span> <span class="nx">IPIV</span><span class="p">,</span> <span class="nx">strideIPIV</span><span class="p">,</span> <span class="nx">offsetIPIV</span><span class="p">);</span> <span class="p">}</span> </code></pre> </div> <p>A few points to note:</p> <ol> <li>In contrast to the conventional LAPACKE API, the <code>matrix_layout</code> (order) parameter is not necessary in the <code>dlaswp_ndarray</code> and <code>base</code> APIs, as the order can be inferred from the provided strides.</li> <li>In contrast to the conventional LAPACKE API, when an input matrix is row-major, we don't need to copy data to temporary workspace arrays, thus reducing unnecessary memory allocation.</li> <li>In contrast to libraries, such as NumPy and SciPy, which interface with BLAS and LAPACK directly, when calling LAPACK routines in stdlib, we don't need to copy non-contiguous multi-dimensional data to and from temporary workspace arrays before and after invocation, respectively. Except when interfacing with hardware-optimized BLAS and LAPACK, the pursued approach helps minimize data movement and ensures performance in resource constrained browser applications.</li> </ol> <p>For server-side applications hoping to leverage hardware-optimized libraries, such as OpenBLAS, we provide separate wrappers which adapt generalized signature arguments to their optimized API equivalents. In this context, at least for sufficiently large arrays, creating temporary copies can be worth the overhead.</p> <h2> Current status and next steps </h2> <p>Despite the challenges, unforeseen setbacks, and multiple design iterations, I am happy to report that, in addition to <code>dlaswp</code> above, I was able to open <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/pulls?q=sort%3Aupdated-desc+is%3Apr+author%3APranavchiku+label%3ALAPACK+" rel="noopener noreferrer">35 PRs</a> adding support for various LAPACK routines and associated utilities. Obviously not quite 1,700 routines, but a good start! :)</p> <p>Nevertheless, the future is bright, and we are quite excited about this work. There's still plenty of room for improvement and additional research and development. In particular, we're keen to</p> <ol> <li>explore tooling and automation.</li> <li>address build issues when resolving the source files of Fortran dependencies spread across multiple stdlib packages.</li> <li>roll out C and Fortran implementations and native bindings for stdlib's existing LAPACK packages.</li> <li>continue growing stdlib's library of modular LAPACK routines.</li> <li>identify additional areas for performance optimization.</li> </ol> <p>While my Quansight Labs internship has ended, my plan is to continue adding packages and pushing this effort along. Given the immense potential and LAPACK's fundamental importance, we'd love to see this initiative of bringing LAPACK to the web continue to grow, so, if you are interested in helping drive this forward, please don't hesitate to reach out! And if you are interested in sponsoring development, the folks at <a href="proxy.php?url=https://quansight.com" rel="noopener noreferrer">Quansight</a> would be more than happy to chat.</p> <p>And with that, I would like to thank Quansight for providing this internship opportunity. I feel incredibly fortunate to have learned so much. Being an intern at Quansight was long a dream of mine, and I am very grateful to have fulfilled it. I want to extend a special thanks to <a href="proxy.php?url=https://github.com/kgryte" rel="noopener noreferrer">Athan Reines</a> and to <a href="proxy.php?url=https://github.com/melissawm" rel="noopener noreferrer">Melissa Mendonça</a>, who is an amazing mentor and all around wonderful person! And thank you to all the stdlib core developers and everyone else at Quansight for helping me out in ways both big and small along the way.</p> <p>Cheers!</p> <p><a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> is an open source software project dedicated to providing a comprehensive suite of robust, high-performance libraries to accelerate your project's development and give you peace of mind knowing that you're depending on expertly crafted, high-quality software.</p> <p>If you've enjoyed this post, give us a star 🌟 on <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">GitHub</a> and consider <a href="proxy.php?url=https://opencollective.com/stdlib" rel="noopener noreferrer">financially supporting</a> the project. Your contributions and continued support help ensure the project's long-term success and are greatly appreciated!</p> javascript webdev programming node Reflecting on GSoC 2024 Philipp Burckhardt Fri, 04 Oct 2024 03:14:52 +0000 https://dev.to/stdlib/reflecting-on-gsoc-2024-5doa https://dev.to/stdlib/reflecting-on-gsoc-2024-5doa <blockquote> <p>Achievements, Lessons, and Tips for Future Success</p> </blockquote> <p>An exciting summer has come to a close for stdlib with our first participation in Google Summer of Code (GSoC). GSoC is an annual program run by Google and a highlight within the open source community. It brings together passionate contributors and mentors to collaborate on open source projects. Selected contributors receive a stipend for their hard work, while organizations benefit from new features, improved project visibility, and the potential to cultivate long-term contributors.</p> <p>stdlib (/ˈstændərd lɪb/ "standard lib") is a fundamental numerical library for JavaScript. Our mission is to create a scientific computing ecosystem for JavaScript and TypeScript, similar to what NumPy and SciPy are for Python. This year, we were granted four slots in GSoC, marking a significant milestone for us as a first-time participating organization.</p> <p>The purpose of this post is to share our GSoC experiences to help future organizations and contributors prepare more effectively. We aim to provide insights into what worked well, what challenges we faced, and advice for making the most out of this incredible program.</p> <h2> Highlights of the Program </h2> <p>While we certainly encountered bumps along the way (more on that in a second), overall, our participation in GSoC was packed with standout moments. Our accepted contributors successfully completed their four GSoC projects.</p> <p>To illustrate the impact of our participation, here are some key statistics and accomplishments from our community since the GSoC organization announcement in February:</p> <ul> <li> Over 1,000 PRs opened</li> <li> More than 100 unique PR contributors</li> <li> Over 2,000 new commits to the codebase</li> </ul> <p>We had a range of successful contributions that significantly advanced stdlib. Specifically, our four GSoC contributors worked on the following projects:</p> <ul> <li> Aman Bhansali worked on BLAS bindings, overcoming the challenge of integrating complex numerical libraries into JavaScript.</li> <li> Gunj Joshi developed C implementations for special mathematical functions, significantly improving the performance of our library.</li> <li> Jaysukh Makvana added support for Boolean arrays, enhancing the library's functionality and usability and paving the way for NumPy-like array indexing in JavaScript.</li> <li> Snehil Shah worked on enhancing the stdlib REPL for scientific computing in Node.js, making it easier for users to interact with our library and perform data analysis in their terminals.</li> </ul> <p>Each project addressed critical areas in our mission to create a comprehensive numerical library for JavaScript and the web platform. </p> <p>Finally, we already see a glimpse of the project attracting long-term contributors from both GSoC participants and the broader community.</p> <h2> An Unexpected Challenge </h2> <p>Despite the many positives, our journey wasn't without its share of challenges. Early on, we faced an unexpected incident that seemed straight out of a movie plot. A prospective contributor tried to sabotage a fellow applicant by impersonating them on Gitter, the open source instant messaging and chat room service where we engage with the community. After signing up via a fake Twitter/X account, he started sending unhinged messages to several of the project's core contributors. While it quickly became clear that we were communicating with an impersonator, it was an unsettling experience nonetheless. The impersonator even ended up copying the real applicant's proposal and later attempted to claim the work as their own on GitHub after the conclusion of GSoC.</p> <p>In light of this experience, we advise any organizations participating in GSoC to keep in mind that competition for slots can be fierce, and that some individuals may be tempted to use subterfuge or actively jeopardize others' applications. One must be vigilant and expect the unexpected. We also recommend having a Code of Conduct (CoC) in place to address such unethical behavior and raising awareness among GSoC contributors of its existence, such as having a CoC acknowledgment checkbox on pull requests and when submitting proposals.</p> <h2> Lessons Learned and Advice for Future Participants </h2> <h3> Engage Early with the Community </h3> <p>First and foremost, it is crucial to encourage potential contributors to start interacting with the community and codebase well before the application period. This helps build familiarity and commitment. Although we were aware of this, we could have done more to encourage early engagement and provide clearer guidance on how to get started. Going through all onboarding steps afresh may help uncover outdated information in documentation or other inconsistencies.</p> <blockquote> <p>💡 <strong>Community Outreach:</strong> Actively promote your participation through social media, blogs, and coding forums. Use platforms like X/Twitter, LinkedIn, and relevant forums to announce your participation and engage with potential contributors.</p> </blockquote> <h3> Handling Community Queries </h3> <p>After our participation was announced, we were quickly bombarded with what seemed like a non-stop barrage of messages per day on Gitter and other communication channels, and with dozens of PRs opened each day. As the core stdlib team is not working on the project full-time, it was very challenging to keep up. We learned that it's essential to set clear expectations and boundaries early on to manage the influx of new contributors.</p> <h3> Managing the Onboarding Process </h3> <p>Answering the same questions repeatedly can be time-consuming, so having frequently asked questions (FAQs) and a well-documented onboarding process will prove to be invaluable. We also started a weekly office hour for people to drop by. This had a decent turnout and proved valuable, as only individuals who were genuinely interested in the project attended and helped weed out those who were just making "drive-by" contributions. In addition to the weekly office hours, we also held two sessions during the application period to serve as informational sessions specifically focusing on GSoC so we could answer all questions that prospective contributors had.</p> <p>After the conclusion of GSoC, we have continued to hold weekly office hours, which have been a great way to keep the community engaged!</p> <blockquote> <p>💡 <strong>Communication Channels:</strong> Clearly outline the primary communication channels (e.g., mailing lists, chat platforms like Gitter, etc) and how to use them.</p> </blockquote> <h3> Good First Issues </h3> <p>What worked less well were the "good first issues" issues we had opened and labeled as such on GitHub. We found that issues we thought were good first ones, such as updating documentation and examples, resulted in a very high number of low-quality submissions, often suffering from hallucinated contents due to AI generation or other issues, which caused more work for reviewers. On the other hand, other tasks, such as refining TypeScript definitions, were often too complex and challenging for newcomers. </p> <p>We learned that the best first issues are those that are well-scoped, have clear instructions, and are easy to test and verify. Having a bunch of trivial issues provides weak signal; you want to see contributors progressively tackle more complicated tasks as they become more acquainted with the project. To aid in this progression, one would be well served to have enough issues of varying difficulty that prospective contributors can tackle. If possible, it may be ideal to have issues build on top of each other and take the contributor on a journey toward mastery. Similarly, it may be good to create open issues that are related to each of the potential GSoC project topics, so that contributors can get familiar with the parts of the codebase they would be working on during the GSoC program. And lastly, consider creating issue templates specifically for GSoC participants, which include detailed instructions, links to relevant documentation, and expected outcomes. This reduces ambiguity and helps set clear expectations for newcomers.</p> <p>Going forward, we plan to focus on creating well-defined, incremental issues that serve as stepping stones for new contributors to build familiarity and gradually take on more complex tasks.</p> <blockquote> <p>💡 <strong>Starter Issues and Mini-Projects:</strong> Offer beginner-friendly issues and smaller tasks early on to help newcomers familiarize themselves with the codebase. Fixing existing bugs or writing tests can be a good starting point.</p> </blockquote> <h3> The Role of AI </h3> <p>I think it's fair to conclude that Generative AI has emerged as both a blessing and a curse in the world of open source contributions. Personally, I am an avid user of LLMs and happy about the innovation they have sparked in the developer tooling space. They can assist non-native English speakers in better communicating their ideas, provide a conversation partner equipped with vast knowledge of even quite remote topics, and can increase developer productivity through code completions and code generation. However, AI has also led to a flood of low-quality PRs generated by AI tools, often filled with hallucinated code or content that doesn't align with the project's actual requirements. While writing code can feel more rewarding than the often tedious task of reviewing it—especially when the code isn't your own—reviewer fatigue becomes a real issue when faced with a barrage of poorly constructed or misaligned PRs.</p> <p>Contributors must recognize that AI is an assistant, not a replacement for personal responsibility and craftsmanship. We have by now spent a significant amount of effort in automation to filter out low-effort submissions before they even reach the review stage. Beside workflows that close PRs which don't adhere to basic contribution conventions, we have added jobs that post helpful comments on how to set up a development environment or which remind contributors that they have to accept the project's contribution guidelines before their PR can be reviewed. This significantly reduces the burden on reviewers and ensures contributors are aware of expectations from the beginning.</p> <h3> Contributor Triage </h3> <p>Another important takeaway is to watch out for contributors claiming multiple issues without completing them. We found that it's best to avoid assigning issues to anyone via the respective GitHub feature and instead focus on encouraging quality contributions over sheer quantity. Additionally, be prepared to manage contributors who may place unrealistic demands on review times, such as insisting on immediate feedback.</p> <p>One has to be ruthless in prioritizing contributions. This approach ensures that contributors who show genuine interest and effort receive the attention they deserve, leading to higher quality interactions and outcomes for both the project and the contributor. Reviewer time is a limited resource, and it's simply not feasible to provide equal attention to every contributor.</p> <p>At the end of the day, contributors must invest the time necessary to familiarize themselves with a project's conventions, guidelines, and best practices. If they don't meet this minimum threshold and do not show genuine effort, it's not worth allocating the finite resources of the core team. This may sound harsh, but it's necessary to ensure there is enough time to focus on the high-quality contributions. Otherwise, one ends up in a position where everybody is unhappy with your responsiveness. This may be less of an issue for organizations in niches requiring specialized skills and which may not have as wide an audience as a JavaScript library.</p> <h3> Provide Clear Documentation </h3> <p>Ensure that your project documentation is comprehensive and up-to-date. This includes installation guides, contribution guidelines, and a clear roadmap. Poor documentation can be a significant barrier to entry. During the community bonding period, we found that our documentation was outdated in some areas and that there were issues arising from our setup instructions not working on all operating systems. Providing a <code>devcontainer</code> setup for Visual Studio Code helped to mitigate these issues and streamline the onboarding process.</p> <blockquote> <p>💡 <strong>Contribution Guides:</strong> Providing detailed guides on setting up the development environment, navigating the codebase, and submitting contributions is crucial.</p> </blockquote> <h2> Mentor Selection and Training </h2> <p>Choose experienced and committed mentors who can provide guidance and support throughout the program. Consider providing mentor training sessions and setting clear expectations around time commitments and responsibilities to better prepare mentors for their roles. Expect mentoring to be more demanding than envisioned.</p> <p>We found that having weekly stand-ups allowed contributors to get to know each other and share their progress. We had also, early on, decided to have weekly 1:1s between contributors and mentors, combined with active conversations on PRs, RFC issues, and our project-internal Slack. All these channels helped to keep the communication flowing and ensure that everyone was on the same page. However, it's crucial to try to be responsive. Personally, I could have been better at responding to PRs and questions given how quickly the time flies by, with GSoC being over before you know it!</p> <blockquote> <p>💡 Encourage mentors to actively communicate with each other about their experiences and challenges, so they can offer consistent advice and collaborate on strategies for effectively supporting contributors.</p> </blockquote> <h2> Post-GSoC Engagement Strategies </h2> <p>After GSoC ends, it's essential to keep contributors engaged in order to build a sustainable community. Continue holding regular office hours, offer additional project ideas, or even invite selected GSoC contributors to mentor the next round of participants. This will go a long way toward creating a sense of belonging and long-term commitment.</p> <h2> Common Pitfalls to Avoid </h2> <ul> <li> <strong>Overwhelming Newcomers:</strong> Don't assign tasks that are too complex or lacking adequate documentation.</li> <li> <strong>Inadequate Support:</strong> Ensure mentors are available and can provide adequate guidance.</li> <li> <strong>Poor Documentation:</strong> Avoid outdated or incomplete documentation which can create barriers to entry. </li> <li> <strong>Insufficient Community Interaction:</strong> Foster a sense of community and two-way communication.</li> </ul> <p>To provide an illustrative example of where we fell prey to the pitfalls above, a number of contributors working on Windows machines initially struggled with setting up their local development environment. Because the core stdlib team primarily develops on MacOS and Linux, we are largely unaware of the needs and constraints of Windows users, and our contributing guidelines largely reflected that ignorance. Needless to say, telling people to just use Ubuntu shell was not sufficient. We could have saved ourselves a lot of back and forth by (a) providing preconfigured dev containers, (b) investing the time necessary to create more comprehensive documentation, and (c) having a quick onboarding session over a higher bandwidth medium than chat.</p> <h2> Advice for Contributors </h2> <ul> <li> <strong>Early Engagement:</strong> Interact with the community and start working on beginner-friendly issues early on. If you start contributing before the application period and show your commitment to the project, you will stand out as a proactive candidate during the selection process. This is probably the biggest hack to get selected for GSoC.</li> <li> <strong>Invest in Project Familiarity Early On</strong>: Before contributing code, take time to read through old issues, PR discussions, and any architectural documentation available. Understanding the project's historical context can help avoid misunderstandings and improve the relevance of your contributions.</li> <li> <strong>Prioritize Code Quality and Documentation:</strong> Don't rush to make as many contributions as possible. Take your time to write high-quality code and back it up with sufficient documentation and test cases. Especially in stdlib, we place a high priority on ensuring consistency throughout the codebase, so the more your contributions look and feel like stdlib, the more likely your contributions will be accepted. This attention to detail will set you apart from others who may focus solely on quantity and ignore project conventions. </li> <li> <strong>Clear Communication:</strong> Don't hesitate to ask questions and seek guidance from mentors and the community. Organizations may be overwhelmed with applications, so stepping up and answering questions on the community forums can help you stand out as well.</li> <li> <strong>Ask for Feedback:</strong> Throughout the GSoC program, ask for and incorporate feedback from project mentors. During the GSoC application phase, contributors who clearly demonstrate an ability to receive and act on feedback will stand out. It can be frustrating for project reviewers to repeat the same feedback across multiple PRs, especially concerning project style and conventions. Make it a goal to reduce the number of reviewer comments on each PR. Clean PRs requiring little-to-no review feedback significantly improve the odds of you setting yourself apart from the pack.</li> <li> <strong>Respect Maintainer Time:</strong> Be respectful of maintainer time. GSoC can be highly competitive, and, for many, GSoC acceptance is a meaningful resumé item. Recognize, however, that maintainers often have obligations and jobs outside of their open source work. Sometimes it just isn't possible to immediately review your PR or answer your question, especially toward the end of the GSoC application period. You can significantly improve the likelihood of a response if you heed the advice above; namely, invest in project familiarity early on, prioritize code quality and documentation, and incorporate feedback. Maintainers are human, and they are more likely to invest in you, the more you show you care about them.</li> <li> <strong>Time Management:</strong> Plan your time effectively to meet project milestones and deadlines. The time will fly by, and you don't want to be scrambling to complete your project at the last minute. Break down your project into smaller tasks, and set realistic goals for each week. Where possible, be strategic in your planning, such that, if one task becomes blocked, you can continue making progress by working on other tasks in parallel. If you encounter obstacles, reach out for help sooner rather than later. Being proactive not only ensures you stay on track but also demonstrates your commitment and initiative.</li> <li> <strong>Participate Beyond Code:</strong> Engage in discussions beyond code contributions. Once you have familiarized yourself with the project, gotten up to speed on how to contribute, and successfully made contributions to the codebase, help other newcomers by participating in community channels, answering questions, and directing them to appropriate resources. Not only does this show that you are invested in the community, but it also helps reduce maintainer burden—something which is unlikely to go unnoticed.</li> <li> <strong>Be Adaptive and Open to Change:</strong> Sometimes your initial project plan may not work out as expected. Be flexible and willing to adjust your project scope or approach based on feedback and evolving project priorities.</li> </ul> <blockquote> <p>💡 Remember that valuable contributions aren't limited to code alone. Participating in community discussions, improving documentation, and offering support to other newcomers are all meaningful ways to contribute and demonstrate commitment to the project.</p> </blockquote> <h2> Acknowledgments </h2> <p>Our heartfelt thanks go out to everyone involved in this year's GSoC, from the mentors and contributors to the broader community, and last but not least, to Google. We're excited to build on the momentum from this summer and look forward to seeing what the future holds for stdlib!</p> <p>If you're interested in becoming a part of our growing community or exploring the opportunities GSoC can provide, visit our <a href="proxy.php?url=https://github.com/stdlib-js/google-summer-of-code" rel="noopener noreferrer">Google Summer of Code</a> repository and join the conversation on our community channels. We're always excited to welcome new contributors!</p> <p>And if you're just generally interested in contributing or staying updated, be sure to check out the project <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">repository</a>. Don't be shy, and come say hi. We'd love for you to be a part of our community!</p> <p><a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> is an open source software project dedicated to providing a comprehensive suite of robust, high-performance libraries to accelerate your project's development and give you peace of mind knowing that you're depending on expertly crafted, high-quality software.</p> <p>If you've enjoyed this post, give us a star 🌟 on <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">GitHub</a> and consider <a href="proxy.php?url=https://opencollective.com/stdlib" rel="noopener noreferrer">financially supporting</a> the project. Your contributions and continued support help ensure the project's long-term success and are greatly appreciated!</p> javascript webdev node beginners Welcoming colors to the stdlib REPL! Snehil Shah Mon, 19 Aug 2024 18:50:31 +0000 https://dev.to/stdlib/welcoming-colors-to-the-stdlib-repl-24ie https://dev.to/stdlib/welcoming-colors-to-the-stdlib-repl-24ie <blockquote> <p>The stdlib REPL now supports syntax highlighting and custom theming.</p> </blockquote> <p>The stdlib REPL (Read-Eval-Print Loop) is an interactive interpreter environment for executing JavaScript and enabling easy prototyping, testing, debugging, and programming. With syntax highlighting now added, editing in the REPL becomes way more intuitive and fun.</p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Ftyping.gif" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Ftyping.gif" alt="An animated GIF showing the typing of raw `var a = /45/;` endraw and demonstrating syntax highlighting applied in real-time"></a></p> <p>How to get your hands on the new hotness? Download the latest package from <a href="proxy.php?url=https://www.npmjs.com/package/@stdlib/repl" rel="noopener noreferrer">npm</a>, fire it up, and just start typing.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>npm <span class="nb">install</span> <span class="nt">-g</span> @stdlib/repl <span class="nv">$ </span>stdlib-repl </code></pre> </div> <p>We have various themes to get started with. But if you want to make the REPL your own, you can also customize it. We explore customization later in this post.</p> <h2> stdlib </h2> <p>A brief segue about <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a>. stdlib is a standard library for numerical and scientific computation for use in web browsers and in server-side runtimes supporting JavaScript. The library provides high-performance and rigorously tested APIs for data manipulation and transformation, mathematics, statistics, linear algebra, pseudorandom number generation, array programming, and a whole lot more.</p> <p>We're on a mission to make JavaScript (and TypeScript!) a preferred language for numerical computation. If this sounds interesting to you, check out the project on <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">GitHub</a>, and be sure to give us a star 🌟!</p> <p>Moving on... 🏃💨</p> <h2> Themes </h2> <p>Where were we? Ah, yes, themes! The REPL comes with the following themes built-in.</p> <blockquote> <p><strong>Pro tip</strong>: You can always use the <code>themes()</code> REPL command to list available themes.</p> </blockquote> <ul> <li> <strong>stdlib-ansi-basic</strong>: The classic. The default.</li> </ul> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fansi-basic-blur-bg.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fansi-basic-blur-bg.png" alt="A screenshot of stdlib's ANSI "></a></p> <ul> <li> <strong>stdlib-ansi-light</strong>: For the light mode users.</li> </ul> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fansi-light-blur-bg-1.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fansi-light-blur-bg-1.png" alt="A screenshot of stdlib's ANSI light theme when enabled in the stdlib REPL"></a></p> <ul> <li> <strong>stdlib-ansi-dark</strong>: For the normal users.</li> </ul> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fansi-dark-blur-bg.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fansi-dark-blur-bg.png" alt="A screenshot of stdlib's ANSI dark theme when enabled in the stdlib REPL"></a></p> <ul> <li> <strong>stdlib-ansi-strong</strong>: Expressive and bold.</li> </ul> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fansi-strong-blur-bg.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fansi-strong-blur-bg.png" alt="A screenshot of stdlib's ANSI strong theme when enabled in the stdlib REPL"></a></p> <ul> <li> <strong>solarized</strong>: My personal favorite.</li> </ul> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fsolarized-blur-bg-2.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fsolarized-blur-bg-2.png" alt="A screenshot of stdlib's solarized theme when enabled in the stdlib REPL"></a></p> <ul> <li> <strong>minimalist</strong>: Enough said.</li> </ul> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fminimalist-blur-bg-1.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fminimalist-blur-bg-1.png" alt="A screenshot of stdlib's "></a></p> <ul> <li> <strong>monokai</strong>: The one and only.</li> </ul> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fmonokai-blur-bg.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fmonokai-blur-bg.png" alt="A screenshot of stdlib's monokai theme when enabled in the stdlib REPL"></a></p> <p>In order to change to the theme of your choice, use the REPL <code>settings()</code> command.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="nx">In</span> <span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="nf">settings</span><span class="p">(</span> <span class="dl">'</span><span class="s1">theme</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">solarized</span><span class="dl">'</span> <span class="p">)</span> </code></pre> </div> <h2> Customization </h2> <p>You can create your own syntax highlighting themes using a theme definition. A theme definition is an object mapping each token type to its corresponding color. The following code snippet shows the theme definition for the <code>monokai</code> theme.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">monokai</span> <span class="o">=</span> <span class="p">{</span> <span class="c1">// Keywords:</span> <span class="dl">'</span><span class="s1">control</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">brightRed</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">keyword</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">italic brightCyan</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">specialIdentifier</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">brightMagenta</span><span class="dl">'</span><span class="p">,</span> <span class="c1">// Literals:</span> <span class="dl">'</span><span class="s1">string</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">brightYellow</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">number</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">brightBlue</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">literal</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">brightBlue</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">regexp</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">underline yellow</span><span class="dl">'</span><span class="p">,</span> <span class="c1">// Identifiers:</span> <span class="dl">'</span><span class="s1">command</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">bold brightGreen</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">function</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">brightGreen</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">object</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">italic brightMagenta</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">variable</span><span class="dl">'</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="c1">// Others:</span> <span class="dl">'</span><span class="s1">comment</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">brightBlack</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">punctuation</span><span class="dl">'</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="dl">'</span><span class="s1">operator</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">brightRed</span><span class="dl">'</span> <span class="p">}</span> </code></pre> </div> <p>For the full list of supported tokens, see the REPL <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/repl#replprototypeaddtheme-name-theme-" rel="noopener noreferrer">documentation</a>.</p> <blockquote> <p><strong>Pro tip</strong>: Use the <code>getTheme()</code> REPL command to find out how a theme was built.</p> <pre class="highlight javascript"><code><span class="nx">In</span> <span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="nf">getTheme</span><span class="p">(</span> <span class="dl">'</span><span class="s1">solarized</span><span class="dl">'</span> <span class="p">)</span> </code></pre> </blockquote> <p>Currently, the REPL supports ANSI colors, such as <code>black</code>, <code>red</code>, <code>green</code>, <code>yellow</code>, <code>blue</code>, <code>magenta</code>, <code>cyan</code>, and <code>white</code>, and their brighter variants, such as <code>brightBlack</code> and <code>brightRed</code>.</p> <p>For more expressive themes, you can use styles, such as <code>bold</code>, <code>italic</code>, <code>underline</code>, <code>strikethrough</code>, and <code>reversed</code>, and background colors, such as <code>bgRed</code> and <code>bgBrightRed</code>.</p> <p>Lastly, you can go wild by mixing and matching any of the above colors, styles, and background colors. So something like the following works:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>italic red bgBrightGreen </code></pre> </div> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fridiculous.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fridiculous.png" alt="A screenshot of the string 'ridiculous' stylized with italic font, green background, and red foreground text"></a></p> <p>Some might say this looks ridiculous, but good to know the REPL supports the ridiculousness!</p> <h2> Adding your own theme </h2> <p>To add your theme, use the <code>addTheme()</code> REPL command, as shown in the following REPL snippet.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="nx">In</span> <span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="kd">const</span> <span class="nx">theme</span> <span class="o">=</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">string</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">italic red bgBrightGreen</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">keyword</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">bold magenta</span><span class="dl">'</span><span class="p">,</span> <span class="c1">// Be the artist...</span> <span class="p">};</span> <span class="nx">In</span> <span class="p">[</span><span class="mi">2</span><span class="p">]:</span> <span class="nf">addTheme</span><span class="p">(</span> <span class="dl">'</span><span class="s1">bestThemeEver</span><span class="dl">'</span><span class="p">,</span> <span class="nx">theme</span> <span class="p">)</span> </code></pre> </div> <p>Change your mind and added something you don't like? No worries. Just use the <code>deleteTheme()</code> REPL command to send the theme into oblivion, as in the following REPL snippet.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="nx">In</span> <span class="p">[</span><span class="mi">5</span><span class="p">]:</span> <span class="nf">deleteTheme</span><span class="p">(</span> <span class="dl">'</span><span class="s1">worstThemeEver</span><span class="dl">'</span> <span class="p">)</span> </code></pre> </div> <p>Want to call your theme something different? We've got you covered. Just use the <code>renameTheme()</code> REPL command, as in the following REPL snippet.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="nx">In</span> <span class="p">[</span><span class="mi">6</span><span class="p">]:</span> <span class="nf">renameTheme</span><span class="p">(</span> <span class="dl">'</span><span class="s1">bestThemeEver</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">secondBestThemeEver</span><span class="dl">'</span> <span class="p">)</span> </code></pre> </div> <p>If you prefer spooky <a href="proxy.php?url=https://en.wikipedia.org/wiki/Action_at_a_distance" rel="noopener noreferrer">action at a distance</a>, simply use the corresponding REPL prototype methods for the above operations. Refer to the REPL <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/repl" rel="noopener noreferrer">documentation</a> for the full list of REPL commands and prototype methods related to syntax highlighting and everything else.</p> <h2> Let's wrap this up </h2> <p>Time to end this post with a quote:</p> <blockquote> <p>"Coding without syntax highlighting is like trying to read a book with all the words in the wrong order—frustrating, confusing, and not nearly as fun!"</p> <p>— ChatGPT 4o mini</p> </blockquote> <p>Boy ain't that the truth!</p> <p>The stdlib REPL is in constant development, so feel free to reach out with new ideas and identified issues. Your feedback is appreciated and hugely important!</p> <p>We've got some more REPL news and notes in the pipeline, so stay tuned for the drip. Until next time, cheers and happy REPLing!</p> <p> <em>Snehil Shah is a computer science undergrad, an audio nerd, and a contributor to <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a>.</em> </p> <p><a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> is an open source software project dedicated to providing a comprehensive suite of robust, high-performance libraries to accelerate your project's development and give you peace of mind knowing that you're depending on expertly crafted, high-quality software.</p> <p>If you've enjoyed this post, give us a star 🌟 on <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">GitHub</a> and consider <a href="proxy.php?url=https://opencollective.com/stdlib" rel="noopener noreferrer">financially supporting</a> the project. Your contributions and continued support help ensure the project's long-term success and are greatly appreciated!</p> javascript webdev programming node The Accessor Protocol Athan Tue, 06 Aug 2024 20:39:01 +0000 https://dev.to/stdlib/the-accessor-protocol-38eg https://dev.to/stdlib/the-accessor-protocol-38eg <p>In this post, I'll introduce you to the <strong>accessor protocol</strong> for generalized array-like object iteration. First, I'll provide an overview of built-in array-like objects, along with example usage. I'll then show you how you can create your own custom array-like objects using the same element access syntax. Next, we will explore why you might want to go beyond vanilla array-like objects to create more "exotic" variants to accommodate sparsity, deferred computation, and performance considerations. Following this, I'll introduce the accessor protocol and how it compares to possible alternatives. Finally, I'll showcase various example applications.</p> <p>Sound good?! Great! Let's go!</p> <h2> TL;DR </h2> <p>The <strong>accessor protocol</strong> (also known as the <strong>get/set protocol</strong>) defines a standardized way for non-indexed collections to access element values. In order to be accessor protocol-compliant, an array-like object must implement two methods having the following signatures:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="kd">function</span> <span class="nf">get</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">(</span> <span class="nx">index</span><span class="p">:</span> <span class="kr">number</span> <span class="p">):</span> <span class="nx">T</span> <span class="p">{...}</span> <span class="kd">function</span> <span class="nf">set</span><span class="o">&lt;</span><span class="nx">T</span><span class="p">,</span> <span class="nx">U</span><span class="o">&gt;</span><span class="p">(</span> <span class="nx">value</span><span class="p">:</span> <span class="nx">T</span><span class="p">,</span> <span class="nx">index</span><span class="p">:</span> <span class="kr">number</span> <span class="p">):</span> <span class="nx">U</span> <span class="p">{...}</span> </code></pre> </div> <p>The protocol allows implementation-dependent behavior when an <code>index</code> is out-of-bounds and, similar to built-in array bracket notation, only requires that implementations support nonnegative <code>index</code> values. In short, the protocol prescribes a minimal set of behavior in order to support the widest possible set of use cases, including, but not limited to, sparse arrays, arrays supporting "lazy" (or deferred) materialization, shared memory views, and arrays which clamp, wrap, or constrain <code>index</code> values.</p> <p>The following code sample provides an example class returning an array-like object implementing the accessor protocol and supporting <a href="proxy.php?url=https://en.wikipedia.org/wiki/Stride_of_an_array" rel="noopener noreferrer">strided access</a> over a linear data buffer.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="cm">/** * Class defining a strided array. */</span> <span class="kd">class</span> <span class="nc">StridedArray</span> <span class="p">{</span> <span class="c1">// Define private instance fields:</span> <span class="err">#</span><span class="nx">length</span><span class="p">;</span> <span class="c1">// array length</span> <span class="err">#</span><span class="nx">data</span><span class="p">;</span> <span class="c1">// underlying data buffer</span> <span class="err">#</span><span class="nx">stride</span><span class="p">;</span> <span class="c1">// step size (i.e., the index increment between successive values)</span> <span class="err">#</span><span class="nx">offset</span><span class="p">;</span> <span class="c1">// index of the first indexed value in the data buffer</span> <span class="cm">/** * Returns a new StridedArray instance. * * @param {integer} N - number of indexed elements * @param {ArrayLikeObject} data - underlying data buffer * @param {number} stride - step size * @param {number} offset - index of the first indexed value in the data buffer * @returns {StridedArray} strided array instance */</span> <span class="nf">constructor</span><span class="p">(</span> <span class="nx">N</span><span class="p">,</span> <span class="nx">data</span><span class="p">,</span> <span class="nx">stride</span><span class="p">,</span> <span class="nx">offset</span> <span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">length</span> <span class="o">=</span> <span class="nx">N</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span> <span class="o">=</span> <span class="nx">data</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">stride</span> <span class="o">=</span> <span class="nx">stride</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">offset</span> <span class="o">=</span> <span class="nx">offset</span><span class="p">;</span> <span class="p">}</span> <span class="cm">/** * Returns the array length. * * @returns {number} array length */</span> <span class="kd">get</span> <span class="nf">length</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">length</span><span class="p">;</span> <span class="p">}</span> <span class="cm">/** * Returns the element located at a specified index. * * @param {number} index - element index * @returns {(void|*)} element value */</span> <span class="nf">get</span><span class="p">(</span> <span class="nx">index</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">[</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">offset</span> <span class="o">+</span> <span class="nx">index</span><span class="o">*</span><span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">stride</span> <span class="p">];</span> <span class="p">}</span> <span class="cm">/** * Sets the value for an element located at a specified index. * * @param {*} value - value to set * @param {number} index - element index */</span> <span class="nf">set</span><span class="p">(</span> <span class="nx">value</span><span class="p">,</span> <span class="nx">index</span> <span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">[</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">offset</span> <span class="o">+</span> <span class="nx">index</span><span class="o">*</span><span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">stride</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">// Define a data buffer:</span> <span class="kd">const</span> <span class="nx">buf</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Float64Array</span><span class="p">(</span> <span class="p">[</span> <span class="mf">1.0</span><span class="p">,</span> <span class="mf">2.0</span><span class="p">,</span> <span class="mf">3.0</span><span class="p">,</span> <span class="mf">4.0</span><span class="p">,</span> <span class="mf">5.0</span><span class="p">,</span> <span class="mf">6.0</span><span class="p">,</span> <span class="mf">7.0</span><span class="p">,</span> <span class="mf">8.0</span> <span class="p">]</span> <span class="p">);</span> <span class="c1">// Create a strided view over the data buffer:</span> <span class="kd">const</span> <span class="nx">x1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StridedArray</span><span class="p">(</span> <span class="mi">4</span><span class="p">,</span> <span class="nx">buf</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span> <span class="p">);</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">v1</span> <span class="o">=</span> <span class="nx">x1</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="mi">1</span> <span class="p">);</span> <span class="c1">// returns 4.0</span> <span class="c1">// Mutate the second element:</span> <span class="nx">x1</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span> <span class="nx">v1</span><span class="o">*</span><span class="mf">10.0</span><span class="p">,</span> <span class="mi">1</span> <span class="p">);</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">v2</span> <span class="o">=</span> <span class="nx">x1</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="mi">1</span> <span class="p">);</span> <span class="c1">// returns 40.0</span> <span class="c1">// Create a new strided view over the same data buffer, but reverse the elements:</span> <span class="kd">const</span> <span class="nx">x2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StridedArray</span><span class="p">(</span> <span class="mi">4</span><span class="p">,</span> <span class="nx">buf</span><span class="p">,</span> <span class="o">-</span><span class="mi">2</span><span class="p">,</span> <span class="nx">buf</span><span class="p">.</span><span class="nx">length</span><span class="o">-</span><span class="mi">1</span> <span class="p">);</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">v3</span> <span class="o">=</span> <span class="nx">x2</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="mi">1</span> <span class="p">);</span> <span class="c1">// returns 6.0</span> <span class="c1">// Mutate the second element:</span> <span class="nx">x2</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span> <span class="nx">v3</span><span class="o">*</span><span class="mf">10.0</span><span class="p">,</span> <span class="mi">1</span> <span class="p">);</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">v4</span> <span class="o">=</span> <span class="nx">x2</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="mi">1</span> <span class="p">);</span> <span class="c1">// returns 60.0</span> <span class="c1">// Retrieve the third element from the first array view:</span> <span class="kd">const</span> <span class="nx">v5</span> <span class="o">=</span> <span class="nx">x1</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="mi">2</span> <span class="p">);</span> <span class="c1">// returns 60.0</span> </code></pre> </div> <p>As shown in the code sample above, a strided array is a powerful abstraction over built-in arrays and typed arrays, as it allows for arbitrary views having custom access patterns over a single buffer. In fact, strided arrays are the conceptual basis for multi-dimensional arrays, such as NumPy's <a href="proxy.php?url=https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html" rel="noopener noreferrer"><code>ndarray</code></a> and stdlib's <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/ndarray/ctor" rel="noopener noreferrer"><code>ndarray</code></a>, which are the fundamental building blocks of modern numerical computing. Needless to say, the example above speaks to the utility of going beyond built-in bracket syntax and providing APIs for generalized array-like object iteration.</p> <p>To learn more about the accessor protocol and its use cases, continue reading the rest of the post below! 🚀</p> <h2> stdlib </h2> <p>A brief overview about <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a>. stdlib is a standard library for numerical and scientific computation for use in web browsers and in server-side runtimes supporting JavaScript. The library provides high-performance and rigorously tested APIs for data manipulation and transformation, mathematics, statistics, linear algebra, pseudorandom number generation, array programming, and a whole lot more.</p> <p>We're on a mission to make JavaScript (and TypeScript!) a preferred language for numerical computation. If this sounds interesting to you, check out the project on <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">GitHub</a>, and be sure to give us a star 🌟!</p> <h2> Introduction </h2> <p>In JavaScript, we use bracket notation to access individual array elements. For example, in the following code sample, we use bracket notation to retrieve the second element in an array.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span> <span class="p">];</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">v</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">1</span> <span class="p">];</span> <span class="c1">// returns 2</span> </code></pre> </div> <p>This works for both generic array and typed array instances. In the next code sample, we repeat the previous operation on a typed array.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Float64Array</span><span class="p">(</span> <span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span> <span class="p">]</span> <span class="p">);</span> <span class="c1">// returns &lt;Float64Array&gt;</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">v</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">1</span> <span class="p">];</span> <span class="c1">// returns 2</span> </code></pre> </div> <p>Similarly, one can use bracket notation for built-in array like objects, such as strings. In the next code sample, we retrieve the second UTF-16 code unit in a string.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">s</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">beep boop</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// Retrieve the second UTF-16 code unit:</span> <span class="kd">const</span> <span class="nx">v</span> <span class="o">=</span> <span class="nx">s</span><span class="p">[</span> <span class="mi">1</span> <span class="p">];</span> <span class="c1">// returns 'e'</span> </code></pre> </div> <p>In order to determine how many elements are in an array-like object, we can use the <code>length</code> property, as shown in the following code sample.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span> <span class="p">];</span> <span class="kd">const</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">x</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="c1">// returns 3</span> </code></pre> </div> <p>Arrays and typed arrays are referred to as <strong>indexed collections</strong>, where elements are ordered according to their index value. An array-like object is thus an ordered list of values that one refers to using a variable name and index.</p> <p>While JavaScript arrays and typed arrays have many methods (e.g., <code>forEach</code>, <code>map</code>, <code>filter</code>, <code>sort</code>, and more), the only required property that any array-like object (built-in or custom) must have is a <code>length</code> property. The <code>length</code> property tells us the maximum number of elements for which we can apply an operation. Without it, we'd never know when to stop iterating in a <code>for</code> loop!</p> <h2> Custom array-Like objects </h2> <p>We can create our own custom array-like objects using vanilla object literals. For example, in the following code sample, we create an object having numbered keys and a <code>length</code> property and retrieve the value associated with the key <code>1</code> (i.e., the second element).<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">length</span><span class="dl">'</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="dl">'</span><span class="s1">0</span><span class="dl">'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="dl">'</span><span class="s1">1</span><span class="dl">'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="dl">'</span><span class="s1">2</span><span class="dl">'</span><span class="p">:</span> <span class="mi">3</span> <span class="p">};</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">v</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">1</span> <span class="p">];</span> <span class="c1">// returns 2</span> </code></pre> </div> <p>Notice that we're able to use numeric "indices". This is because, per the ECMAScript Standard, any non-symbol value used as a key is <a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_objects#accessing_properties" rel="noopener noreferrer">first converted</a> to a string before performing property-value look-up. In which case, so long as downstream consumers don't assume the existence of specialized methods, but stick to only indexed iteration, downstream consumers can adopt array-like object neutrality.</p> <p>For example, suppose we want to compute the sum of all elements in an array-like object. We could define the following function which accepts, as its sole argument, any object having a <code>length</code> property and supporting value access via numeric indices.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">function</span> <span class="nf">sum</span><span class="p">(</span> <span class="nx">x</span> <span class="p">)</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">total</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">for </span><span class="p">(</span> <span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">x</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span> <span class="p">)</span> <span class="p">{</span> <span class="nx">total</span> <span class="o">+=</span> <span class="nx">x</span><span class="p">[</span> <span class="nx">i</span> <span class="p">];</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">total</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>We can then provide all manner of array-like objects and <code>sum</code> is none-the-wiser, being capable of handling them all. In the following code sample, we separately provide a generic array, a typed array, and an array-like object, and, for each input value, the <code>sum</code> function readily computes the sum of all elements.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">x1</span> <span class="o">=</span> <span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span> <span class="p">];</span> <span class="kd">const</span> <span class="nx">s1</span> <span class="o">=</span> <span class="nf">sum</span><span class="p">(</span> <span class="nx">x1</span> <span class="p">);</span> <span class="c1">// returns 6</span> <span class="kd">const</span> <span class="nx">x2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Int32Array</span><span class="p">(</span> <span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span> <span class="p">]</span> <span class="p">);</span> <span class="kd">const</span> <span class="nx">s2</span> <span class="o">=</span> <span class="nf">sum</span><span class="p">(</span> <span class="nx">x2</span> <span class="p">);</span> <span class="c1">// returns 6</span> <span class="kd">const</span> <span class="nx">x3</span> <span class="o">=</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">length</span><span class="dl">'</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="dl">'</span><span class="s1">0</span><span class="dl">'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="dl">'</span><span class="s1">1</span><span class="dl">'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="dl">'</span><span class="s1">2</span><span class="dl">'</span><span class="p">:</span> <span class="mi">3</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">s3</span> <span class="o">=</span> <span class="nf">sum</span><span class="p">(</span> <span class="nx">x3</span> <span class="p">);</span> <span class="c1">// returns 6</span> </code></pre> </div> <p>This is great! So long as downstream consumers make minimal assumptions regarding the existence of prototype methods, preferably avoiding the use of methods entirely, we can create functional APIs capable of operating on any indexed collection.</p> <p>But wait, what about those scenarios in which we want to use alternative data structures, such that property-value pairs are not so neatly aligned, or we want to leverage deferred computation, or create views on existing array-like objects? How can we handle those use cases?</p> <h2> Motivating use cases </h2> <h3> Sparse arrays </h3> <p>Up until this point, we've concerned ourselves with "dense" arrays (i.e., arrays in which all elements can be stored sequentially in a contiguous block of memory). In JavaScript, in addition to dense arrays, we have the concept of "sparse" arrays. The following code sample demonstrates sparse array creation by setting an element located at an index which vastly exceeds the length of the target array.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="p">[];</span> <span class="c1">// Convert `x` into a sparse array:</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">10000</span> <span class="p">]</span> <span class="o">=</span> <span class="mf">3.14</span><span class="p">;</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">v1</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">1</span> <span class="p">];</span> <span class="c1">// returns undefined</span> <span class="c1">// Retrieve the last element:</span> <span class="kd">const</span> <span class="nx">v10000</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">10000</span> <span class="p">];</span> <span class="c1">// returns 3.14</span> <span class="c1">// Retrieve the number of elements:</span> <span class="kd">const</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">x</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="c1">// returns 10001</span> </code></pre> </div> <p>Suffice it to say that, by not using the <code>Array.prototype.push</code> method and filling in values until element <code>10000</code>, JavaScript engines responsible for compiling and optimizing your code treat the array as if it were a normal object, which is a reasonable optimization in order to avoid unnecessary memory allocation. Creating a sparse array in this fashion is often referred to as converting an array into "dictionary-mode", where an array is stored in a manner similar to a regular object instance. The above code sample is effectively equivalent to the following code sample where we explicitly define <code>x</code> to be an array-like object containing a single defined value at index <code>10000</code>.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">length</span><span class="dl">'</span><span class="p">:</span> <span class="mi">10001</span><span class="p">,</span> <span class="dl">'</span><span class="s1">10000</span><span class="dl">'</span><span class="p">:</span> <span class="mf">3.14</span> <span class="p">};</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">v1</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">1</span> <span class="p">];</span> <span class="c1">// returns undefined</span> <span class="c1">// Retrieve the last element:</span> <span class="kd">const</span> <span class="nx">v10000</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">10000</span> <span class="p">];</span> <span class="c1">// returns 3.14</span> </code></pre> </div> <p>Creating sparse arrays in this manner is fine for many use cases, but less than optimal in others. For example, in numerical computing, we'd prefer that the "holes" (i.e., undefined values) in our sparse array would be <code>0</code>, rather than <code>undefined</code>. This way, the <code>sum</code> function we defined above could work on both sparse and dense arrays alike (setting aside, for the moment, any performance considerations).</p> <h3> Deferred computation </h3> <p>Next up, consider the case in which we want to avoid materializing array values until they are actually needed. For example, in the following snippet, we'd like the ability to define an array-like object without any pre-defined values and which supports "lazy" materialization such that values are materialized upon element access.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">length</span><span class="dl">'</span><span class="p">:</span> <span class="mi">3</span> <span class="p">};</span> <span class="c1">// Materialize the first element:</span> <span class="kd">const</span> <span class="nx">v0</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">0</span> <span class="p">];</span> <span class="c1">// returns 1</span> <span class="c1">// Materialize the second element:</span> <span class="kd">const</span> <span class="nx">v1</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">1</span> <span class="p">];</span> <span class="c1">// returns 2</span> <span class="c1">// Materialize the third element:</span> <span class="kd">const</span> <span class="nx">v2</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">2</span> <span class="p">];</span> <span class="c1">// returns 3</span> </code></pre> </div> <p>To implement lazy materialization in JavaScript, we could utilize the <a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols" rel="noopener noreferrer">Iterator protocol</a>; however, iterators are not directly "indexable" in a manner similar to array-like objects, and they don't generally have a <code>length</code> property indicating how many elements they contain. To know when they finish, we need to explicitly check the <code>done</code> property of the iterated value. While we can use the built-in <code>for...of</code> statement to iterate over Iterables, this requires either updating our <code>sum</code> implementation to use <code>for...of</code>, and thus require that all provided array-like objects also be Iterables, or introducing branching logic based on the type of value provided. Neither option is ideal, with both entailing increased complexity, constraints, performance-costs, or, more likely, some combination of the above.</p> <h3> Shared memory views </h3> <p>For our next motivating example, consider the case of creating arbitrary views over the same underlying block of memory. While typed arrays support creating contiguous views (e.g., by providing a shared <code>ArrayBuffer</code> to typed array constructors), situations may arise where we want to define non-contiguous views. In order to avoid unnecessary memory allocation, we'd like the ability to define arbitrary iteration patterns which allow accessing particular elements within an underlying linear data buffer.</p> <p>In the following snippet, we illustrate the use case of an array-like object containing complex numbers which are stored in memory as interleaved real and imaginary components. To allow accessing and manipulating just the real components within the array, we'd like the ability to create a "view" atop the underlying data buffer which accesses every other element (i.e., just the real components). We could similarly create a "view" for only accessing the imaginary components.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="c1">// Define a data buffer of interleaved real and imaginary components:</span> <span class="kd">const</span> <span class="nx">buf</span> <span class="o">=</span> <span class="p">[</span> <span class="mf">1.0</span><span class="p">,</span> <span class="o">-</span><span class="mf">2.0</span><span class="p">,</span> <span class="mf">3.0</span><span class="p">,</span> <span class="o">-</span><span class="mf">4.0</span><span class="p">,</span> <span class="mf">5.0</span><span class="p">,</span> <span class="o">-</span><span class="mf">6.0</span><span class="p">,</span> <span class="mf">7.0</span><span class="p">,</span> <span class="o">-</span><span class="mf">8.0</span> <span class="p">];</span> <span class="c1">// Create a complex number array:</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ComplexArray</span><span class="p">(</span> <span class="nx">buf</span> <span class="p">);</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">z1</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">1</span> <span class="p">];</span> <span class="c1">// returns Complex&lt;3.0, -4.0&gt;</span> <span class="c1">// Create a view which only accesses real components:</span> <span class="kd">const</span> <span class="nx">re</span> <span class="o">=</span> <span class="nx">x</span><span class="p">.</span><span class="nf">reals</span><span class="p">();</span> <span class="c1">// Retrieve the real component of the second complex number:</span> <span class="kd">const</span> <span class="nx">r</span> <span class="o">=</span> <span class="nx">re</span><span class="p">[</span> <span class="mi">1</span> <span class="p">];</span> <span class="c1">// returns 3.0</span> <span class="c1">// Mutate the real component:</span> <span class="nx">re</span><span class="p">[</span> <span class="mi">1</span> <span class="p">]</span> <span class="o">=</span> <span class="mf">10.0</span><span class="p">;</span> <span class="c1">// Retrieve the second element of the complex number array:</span> <span class="kd">const</span> <span class="nx">z2</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">1</span> <span class="p">];</span> <span class="c1">// returns Complex&lt;10.0, -4.0&gt;</span> </code></pre> </div> <p>To implement such views, we'd need three pieces of information: (1) an underlying data buffer, (2) an <strong>array stride</strong> which defines the number of locations in memory between successive array elements, and (3) an <strong>offset</strong> which defines the location in memory of the first indexed element. For contiguous arrays, the array stride is unity, and the offset is zero. In the example above, for a real component view, the array stride is two, and the offset is zero; for an imaginary component view, the array stride is also two, but the offset is unity. Ideally, we could define a means for providing generalized access such that array-like objects which provide abstracted element indexing can also be provided to array-agnostic APIs, such as <code>sum</code> above.</p> <h3> Backing data structures </h3> <p>As a final example, consider the case where we'd like to compactly store an ordered sequence of boolean values. While we could use generic arrays (e.g., <code>[true,false,...,true]</code>) or <code>Uint8Array</code> typed arrays for this, doing so would not be the most memory efficient approach. Instead, a more memory efficient data structure would be a <a href="proxy.php?url=https://en.wikipedia.org/wiki/Bit_array" rel="noopener noreferrer"><strong>bit array</strong></a> comprised of a sequence of integer words in which, for each word of <em>n</em> bits, a 1-bit indicates a value of <code>true</code> and a 0-bit indicates a value of <code>false</code>.</p> <p>The following code snippet provides a general idea of mapping a sequence of boolean values to bits, along with desired operations for setting and retrieving boolean elements.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">seq</span> <span class="o">=</span> <span class="p">[</span> <span class="kc">true</span><span class="p">,</span> <span class="kc">false</span><span class="p">,</span> <span class="kc">true</span><span class="p">,</span> <span class="p">...,</span> <span class="kc">false</span><span class="p">,</span> <span class="kc">true</span><span class="p">,</span> <span class="kc">false</span> <span class="p">];</span> <span class="c1">// bit array: 1 0 1 ... 0 1 0 =&gt; 101...010</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BooleanBitArray</span><span class="p">(</span> <span class="nx">seq</span> <span class="p">);</span> <span class="c1">// Retrieve the first element:</span> <span class="kd">const</span> <span class="nx">v0</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">0</span> <span class="p">];</span> <span class="c1">// returns true</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">v1</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">1</span> <span class="p">];</span> <span class="c1">// returns false</span> <span class="c1">// Retrieve the third element:</span> <span class="kd">const</span> <span class="nx">v2</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">2</span> <span class="p">];</span> <span class="c1">// returns true</span> <span class="c1">// Set the second element:</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">1</span> <span class="p">]</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">v3</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">1</span> <span class="p">];</span> <span class="c1">// returns true</span> </code></pre> </div> <p>In JavaScript, we could attempt to subclass array or typed array built-ins in order to allow setting and getting elements via bracket notation; however, this approach would prove limiting as subclassing alone does not allow intercepting property access (e.g., <code>x[i]</code>), which would be needed in order to map an index to a specific bit. We could try and combine subclassing with <a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy" rel="noopener noreferrer"><code>Proxy</code></a> objects, but this would come with a steep performance cost due to property accessor indirection--something which we'll revisit later in this post.</p> <h2> Accessor Protocol </h2> <p>To accommodate the above use cases and more, we'd like to introduce a conceptually simple, but very powerful, new protocol: the <strong>accessor protocol</strong> for generalized element access and iteration of array-like objects. The protocol doesn't require new syntax or built-ins. The protocol only defines a standard way to get and set element values.</p> <p>Any array-like object can implement the accessor protocol (also known as the <strong>get/set protocol</strong>) by following two conventions.</p> <ol> <li> <strong>Define a <code>get</code> method.</strong> A <code>get</code> method accepts a single argument: an integer value specifying the index of the element to return. Similar to bracket notation for built-in array and typed array objects, the protocol requires that the <code>get</code> method be defined for integer values that are nonnegative and within array bounds. Protocol-compliant implementations may choose to support negative index values, but that behavior should not be considered portable. Similarly, how implementations choose to handle out-of-bounds indices is implementation-dependent; implementations may return <code>undefined</code>, raise an exception, wrap, clamp, or some other behavior. By not placing restrictions on out-of-bounds behavior, the protocol can more readily accommodate a broader set of use cases.</li> <li> <strong>Define a <code>set</code> method.</strong> A <code>set</code> method accepts two arguments: the value to set and an integer value specifying the index of the element to replace. Similar to the <code>get</code> method, the protocol requires that the <code>set</code> method be defined for integer indices that are nonnegative and within array bounds. And similarly, protocol-compliant implementations may choose to support negative index values, but that behavior should not be considered portable, and how implementations choose to handle out-of-bounds indices is implementation-dependent.</li> </ol> <p>The following code sample demonstrates an accessor protocol-compliant array-like object.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="c1">// Define a data buffer:</span> <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span> <span class="p">];</span> <span class="c1">// Define a minimal array-like object supporting the accessor protocol:</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">length</span><span class="dl">'</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span> <span class="dl">'</span><span class="s1">get</span><span class="dl">'</span><span class="p">:</span> <span class="p">(</span> <span class="nx">index</span> <span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">data</span><span class="p">[</span> <span class="nx">index</span> <span class="p">],</span> <span class="dl">'</span><span class="s1">set</span><span class="dl">'</span><span class="p">:</span> <span class="p">(</span> <span class="nx">value</span><span class="p">,</span> <span class="nx">index</span> <span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">data</span><span class="p">[</span> <span class="nx">index</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">value</span> <span class="p">};</span> <span class="c1">// Retrieve the third element:</span> <span class="kd">const</span> <span class="nx">v1</span> <span class="o">=</span> <span class="nx">x</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="mi">2</span> <span class="p">);</span> <span class="c1">// returns 3</span> <span class="c1">// Set the third element:</span> <span class="nx">x</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">2</span> <span class="p">);</span> <span class="c1">// Retrieve the third element:</span> <span class="kd">const</span> <span class="nx">v2</span> <span class="o">=</span> <span class="nx">x</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="mi">2</span> <span class="p">);</span> <span class="c1">// returns 10</span> </code></pre> </div> <p>Three things to note about the above code sample.</p> <ol> <li> The above example demonstrates another potential use case—namely, an array-like object which doesn't own the underlying data buffer and, instead, acts as a proxy for element access requests.</li> <li> The signature for the <code>set</code> method may seem counter-intuitive, as one might expect the arguments to be reversed. The rationale for <code>value</code> being the first argument and <code>index</code> being the second argument is to be consistent with built-in typed array <code>set</code> method conventions, where the first argument is an array from which to copy values and the second argument is optional and specifies the offset at which to begin writing values from the first argument. While one could argue that <code>set(v,i)</code> is not ideal, given the argument order precedent found in built-ins, the protocol follows that precedent in order to avoid confusion.</li> <li> In contrast to the built-in typed array <code>set</code> method which expects an array (or typed array) for the first argument, the accessor protocol only requires that protocol-compliant implementations support a single element value. Protocol-compliant implementations may choose to support first arguments which are array-like objects and do so in a manner emulating arrays and typed arrays; however, such behavior should not be considered portable.</li> </ol> <p>In short, in order to be accessor protocol-compliant, an array-like object only needs to support single element retrieval and mutation via dedicated <code>get</code> and <code>set</code> methods, respectively.</p> <p>While built-in typed arrays provide a <code>set</code> method, they are <strong>not</strong> accessor protocol-compliant, as they lack a dedicated <code>get</code> method, and built-in arrays are also <strong>not</strong> accessor protocol-compliant, as they lack both a <code>get</code> and a <code>set</code> method. Their lack of compliance is expected and, from the perspective of the protocol, by design in order to distinguish indexed collections from accessor protocol-compliant array-like objects.</p> <p>Array-like objects implementing the accessor protocol should be expected to pay a small, but likely non-negligible, performance penalty relative to indexed collections using bracket notation for element access. As such, we expect that performance-conscious array-agnostic APIs will maintain two separate code paths: one for indexed collections and one for collections implementing the accessor protocol. Hence, the presence or absence of <code>get</code> and <code>set</code> methods provides a useful heuristic for determining which path takes priority. In general, for indexed collections which are also accessor protocol-compliant, the <code>get</code> and <code>set</code> methods should <strong>always</strong> take precedent over bracket notation.</p> <p>The following code sample refactors the <code>sum</code> API defined above to accommodate array-like objects supporting the accessor protocol.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">function</span> <span class="nf">isAccessorArray</span><span class="p">(</span> <span class="nx">x</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return </span><span class="p">(</span> <span class="k">typeof</span> <span class="nx">x</span><span class="p">.</span><span class="kd">get</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">function</span><span class="dl">'</span> <span class="o">&amp;&amp;</span> <span class="k">typeof</span> <span class="nx">x</span><span class="p">.</span><span class="kd">set</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">function</span><span class="dl">'</span> <span class="p">);</span> <span class="p">}</span> <span class="kd">function</span> <span class="nf">sum</span><span class="p">(</span> <span class="nx">x</span> <span class="p">)</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">total</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// Handle accessor-protocol compliant collections...</span> <span class="k">if </span><span class="p">(</span> <span class="nf">isAccessorArray</span><span class="p">(</span> <span class="nx">x</span> <span class="p">)</span> <span class="p">)</span> <span class="p">{</span> <span class="k">for </span><span class="p">(</span> <span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">x</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span> <span class="p">)</span> <span class="p">{</span> <span class="nx">total</span> <span class="o">+=</span> <span class="nx">x</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="nx">i</span> <span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">total</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// Handle indexed collections...</span> <span class="k">for </span><span class="p">(</span> <span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">x</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span> <span class="p">)</span> <span class="p">{</span> <span class="nx">total</span> <span class="o">+=</span> <span class="nx">x</span><span class="p">[</span> <span class="nx">i</span> <span class="p">];</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">total</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>For array-agnostic APIs which prefer brevity over performance optimization, one can refactor the previous code sample to use a small, reusable helper function which abstracts array element access and allows loop consolidation. A demonstration of this refactoring is shown in the following code sample.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">function</span> <span class="nf">isAccessorArray</span><span class="p">(</span> <span class="nx">x</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return </span><span class="p">(</span> <span class="k">typeof</span> <span class="nx">x</span><span class="p">.</span><span class="kd">get</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">function</span><span class="dl">'</span> <span class="o">&amp;&amp;</span> <span class="k">typeof</span> <span class="nx">x</span><span class="p">.</span><span class="kd">set</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">function</span><span class="dl">'</span> <span class="p">);</span> <span class="p">}</span> <span class="kd">function</span> <span class="nf">array2accessor</span><span class="p">(</span> <span class="nx">x</span> <span class="p">)</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span> <span class="nf">isAccessorArray</span><span class="p">(</span> <span class="nx">x</span> <span class="p">)</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">x</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">length</span><span class="dl">'</span><span class="p">:</span> <span class="nx">x</span><span class="p">.</span><span class="nx">length</span><span class="p">,</span> <span class="dl">'</span><span class="s1">get</span><span class="dl">'</span><span class="p">:</span> <span class="p">(</span> <span class="nx">i</span> <span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span><span class="p">[</span> <span class="nx">i</span> <span class="p">],</span> <span class="dl">'</span><span class="s1">set</span><span class="dl">'</span><span class="p">:</span> <span class="p">(</span> <span class="nx">v</span><span class="p">,</span> <span class="nx">i</span> <span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span><span class="p">[</span> <span class="nx">i</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">v</span> <span class="p">};</span> <span class="p">}</span> <span class="kd">function</span> <span class="nf">sum</span><span class="p">(</span> <span class="nx">x</span> <span class="p">)</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">total</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">x</span> <span class="o">=</span> <span class="nf">array2accessor</span><span class="p">(</span> <span class="nx">x</span> <span class="p">);</span> <span class="k">for </span><span class="p">(</span> <span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">x</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span> <span class="p">)</span> <span class="p">{</span> <span class="nx">total</span> <span class="o">+=</span> <span class="nx">x</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="nx">i</span> <span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">total</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>As before, we can then provide all manner of array-like objects, including those supporting the accessor protocol, and <code>sum</code> is none-the-wiser, being capable of handling them all. In the following code sample, we separately provide a generic array, a typed array, an array-like object, and a "lazy" array implementing the accessor protocol, and, for each input value, the <code>sum</code> function readily computes the sum of all elements.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">x1</span> <span class="o">=</span> <span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span> <span class="p">];</span> <span class="kd">const</span> <span class="nx">s1</span> <span class="o">=</span> <span class="nf">sum</span><span class="p">(</span> <span class="nx">x1</span> <span class="p">);</span> <span class="c1">// returns 6</span> <span class="kd">const</span> <span class="nx">x2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Int32Array</span><span class="p">(</span> <span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span> <span class="p">]</span> <span class="p">);</span> <span class="kd">const</span> <span class="nx">s2</span> <span class="o">=</span> <span class="nf">sum</span><span class="p">(</span> <span class="nx">x2</span> <span class="p">);</span> <span class="c1">// returns 6</span> <span class="kd">const</span> <span class="nx">x3</span> <span class="o">=</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">length</span><span class="dl">'</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="dl">'</span><span class="s1">0</span><span class="dl">'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="dl">'</span><span class="s1">1</span><span class="dl">'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="dl">'</span><span class="s1">2</span><span class="dl">'</span><span class="p">:</span> <span class="mi">3</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">s3</span> <span class="o">=</span> <span class="nf">sum</span><span class="p">(</span> <span class="nx">x3</span> <span class="p">);</span> <span class="c1">// returns 6</span> <span class="kd">const</span> <span class="nx">x4</span> <span class="o">=</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">length</span><span class="dl">'</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="dl">'</span><span class="s1">get</span><span class="dl">'</span><span class="p">:</span> <span class="p">(</span> <span class="nx">i</span> <span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="dl">'</span><span class="s1">set</span><span class="dl">'</span><span class="p">:</span> <span class="p">(</span> <span class="nx">v</span><span class="p">,</span> <span class="nx">i</span> <span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x4</span><span class="p">[</span> <span class="nx">i</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">v</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">s4</span> <span class="o">=</span> <span class="nf">sum</span><span class="p">(</span> <span class="nx">x4</span> <span class="p">);</span> <span class="c1">// returns 6</span> </code></pre> </div> <h2> Alternatives </h2> <p>At this point, you may be thinking that the accessor protocol seems useful, but why invent something new? Doesn't JavaScript already have mechanisms for inheriting indexed collection semantics (subclassing built-ins), supporting lazy materialization (iterators), proxying element access requests (<a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy" rel="noopener noreferrer"><code>Proxy</code></a> objects), and accessing elements via a method (<a href="proxy.php?url=//la.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at"><code>Array.prototype.at</code></a>)?</p> <p>Yes, JavaScript does have built-in mechanisms for supporting, at least partially, the use cases outlined above; however, each approach has limitations, which I'll discuss below.</p> <h3> Subclassing built-ins </h3> <p>In the early days of the web and prior to built-in subclassing support, third-party libraries would commonly add methods directly to the prototypes of built-in global objects in order to expose functionality missing from the JavaScript standard library—a practice which was, and still remains, frowned upon. After standardization of ECMAScript 2015, JavaScript gained support for subclassing built-ins, including arrays and typed arrays. By subclassing built-ins, we can create specialized indexed collections which not only extend built-in behavior, but also retain the semantics of bracket notation for indexed collections. Subclassing can be particularly beneficial when wanting to augment inherited classes with new properties and methods.</p> <p>The following code sample demonstrates extending the built-in <code>Array</code> class to support in-place element-wise addition.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="cm">/** * Class which subclasses the built-in Array class. */</span> <span class="kd">class</span> <span class="nc">SpecialArray</span> <span class="kd">extends</span> <span class="nc">Array</span> <span class="p">{</span> <span class="cm">/** * Performs in-place element-wise addition. * * @param {ArrayLikeObject} other - input array * @throws {RangeError} must have the same number of elements * @returns {SpecialArray} the mutated array */</span> <span class="nf">add</span><span class="p">(</span> <span class="nx">other</span> <span class="p">)</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span> <span class="nx">other</span><span class="p">.</span><span class="nx">length</span> <span class="o">!==</span> <span class="k">this</span><span class="p">.</span><span class="nx">length</span> <span class="p">)</span> <span class="p">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">RangeError</span><span class="p">(</span> <span class="dl">'</span><span class="s1">Must provide an array having the same length.</span><span class="dl">'</span> <span class="p">);</span> <span class="p">}</span> <span class="k">for </span><span class="p">(</span> <span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="k">this</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span> <span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">[</span> <span class="nx">i</span> <span class="p">]</span> <span class="o">+=</span> <span class="nx">other</span><span class="p">[</span> <span class="nx">i</span> <span class="p">];</span> <span class="p">}</span> <span class="k">return</span> <span class="k">this</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">// Create a new SpecialArray instance:</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SpecialArray</span><span class="p">(</span> <span class="mi">10</span> <span class="p">);</span> <span class="c1">// Call an inherited method to fill the array:</span> <span class="nx">x</span><span class="p">.</span><span class="nf">fill</span><span class="p">(</span> <span class="mi">5</span> <span class="p">);</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">v1</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">1</span> <span class="p">];</span> <span class="c1">// returns 5</span> <span class="c1">// Create an array to add:</span> <span class="kd">const</span> <span class="nx">y</span> <span class="o">=</span> <span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">10</span> <span class="p">];</span> <span class="c1">// Perform element-wise addition:</span> <span class="nx">x</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span> <span class="nx">y</span> <span class="p">);</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">v2</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span> <span class="mi">1</span> <span class="p">];</span> <span class="c1">// returns 7</span> </code></pre> </div> <p>While powerful, subclassing built-ins has several limitations.</p> <ol> <li> With respect to the use cases discussed above, subclassing built-ins only satisfies the desire for preserving built-in bracket notation semantics. Subclassing does not confer support for lazy materialization, separate backing data structures, or shared memory views.</li> <li> Subclassing built-ins imposes a greater implementation burden on subclasses. Particularly for more "exotic" array types, such as read-only arrays, subclasses may be forced to override and re-implement parent methods in order to ensure consistent behavior (e.g., returning a collection having a desired instance type).</li> <li> Subclassing built-ins imposes an ongoing maintenance burden. As the ECMAScript Standard evolves and built-in objects gain additional properties and methods, those properties and methods may need to be overridden and re-implemented in order to preserve desired semantics.</li> <li> Subclassing built-ins influences downstream user expectations. If a subclass inherits from a <code>Float64Array</code>, users will likely expect that any subclass satisfying an <code>instanceof</code> check supports all inherited methods, some of which may not be possible to support (e.g., for a read-only array, methods supporting mutation). Distinct (i.e., non-coupled) classes which explicitly own the interface contract will likely be better positioned to manager user expectations.</li> <li> While subclassing built-ins can encourage reuse, object-oriented programming design patterns can more generally lead to code bloat (read: increased bundle sizes in web applications), as the more methods are added or overridden, the less likely any one of those methods is actually used in a given application.</li> </ol> <p>For the reasons listed above, inheriting from built-ins is generally <a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends#subclassing_built-ins" rel="noopener noreferrer">discouraged</a> in favor of composition due to non-negligible performance and security impacts. One of the principle aims of the accessor protocol is to provide the smallest API surface area necessary in order to facilitate generalized array-like object iteration. Subclassing built-ins is unable to fulfill that mandate.</p> <h3> Iterators </h3> <p>Objects implementing the <a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols" rel="noopener noreferrer">iterator protocol</a> can readily support deferred computation (i.e., "lazy" materialization), but, for several of the use cases outlined above, the iterator protocol has limited applicability. More broadly, relying on the iterator protocol has three limitations.</p> <p>First, as alluded to earlier in this post, the iterator protocol does not require that objects have a <code>length</code> property, and, in fact, iterators are allowed to be infinite. As a consequence, for operations requiring fixed memory allocation (e.g., as might be the case when needing to materialize values before passing a typed array from JavaScript to C within a Node.js native add-on), the only way to know how much memory to allocate is by first materializing all iterator values. Doing so may require first filling a temporary array before values can be copied to a final destination. This process is likely to be inefficient.</p> <p>Furthermore, operations involving multiple iterators can quickly become complex. For example, suppose I want to perform element-wise addition for two iterators <code>X</code> and <code>Y</code> (i.e., <code>x0+y0</code>, <code>x1+y1</code>, ..., <code>xn+yn</code>). This works fine if <code>X</code> and <code>Y</code> have the same "length", but what if they have different lengths? Should iteration stop once one of the iterator ends? Or should a fill value, such as zero, be used? Or maybe this is unexpected behavior, and we should raise an exception? Accordingly, generalized downstream APIs accepting iterators may require tailored options to support various edge cases which simply aren't as applicable when working with array-like objects.</p> <p>We could, of course, define a protocol requiring that iterators have a <code>length</code> property, but that leads us to the next limitation: iterators do not support random access. In order to access the <code>n</code>-th iterated value, one must materialize the previous <code>n-1</code> values. This is also likely to be inefficient.</p> <p>Lastly, in general, code paths operating on iterators are significantly slower than equivalent code paths operating on indexed collections. While the accessor protocol does introduce overhead relative to using bracket notation due to explicitly needing to call a method, the overhead is less than the overhead introduced by iterators.</p> <p>The following code sample defines three functions: one for computing the sum of an indexed collection, one for computing the sum of an array-like object implementing the accessor protocol, and a third for computing the sum of an iterator using JavaScript's built-in <code>for...of</code> syntax.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">function</span> <span class="nf">indexedSum</span><span class="p">(</span> <span class="nx">x</span> <span class="p">)</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">total</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">for </span><span class="p">(</span> <span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">x</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span> <span class="p">)</span> <span class="p">{</span> <span class="nx">total</span> <span class="o">+=</span> <span class="nx">x</span><span class="p">[</span> <span class="nx">i</span> <span class="p">];</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">total</span><span class="p">;</span> <span class="p">}</span> <span class="kd">function</span> <span class="nf">accessorSum</span><span class="p">(</span> <span class="nx">x</span> <span class="p">)</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">total</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">for </span><span class="p">(</span> <span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">x</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span> <span class="p">)</span> <span class="p">{</span> <span class="nx">total</span> <span class="o">+=</span> <span class="nx">x</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="nx">i</span> <span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">total</span><span class="p">;</span> <span class="p">}</span> <span class="kd">function</span> <span class="nf">iteratorSum</span><span class="p">(</span> <span class="nx">x</span> <span class="p">)</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">total</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">for </span><span class="p">(</span> <span class="kd">const</span> <span class="nx">v</span> <span class="k">of</span> <span class="nx">x</span> <span class="p">)</span> <span class="p">{</span> <span class="nx">total</span> <span class="o">+=</span> <span class="nx">v</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">total</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>To assess the performance of each function, I ran benchmarks on an Apple M1 Pro running MacOS and Node.js v20.9.0. For a set of array lengths ranging from ten elements to one million elements, I repeated benchmarks three times and used the maximum observed rate for subsequent analysis and chart display. The results are provided in the following grouped column chart.</p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fiterator_benchmarks_white_bkgd.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fiterator_benchmarks_white_bkgd.png" alt="Grouped column chart showing benchmark results for the accessor protocol and iterators and normalized relative to computing the sum over indexed collections"></a></p> <p>In the chart above, columns along the x-axis are grouped according to input array/iterator length. Accordingly, the first group of columns corresponds to input arrays/iterators having <code>10</code> elements, the second group to input array/iterators having <code>100</code> elements, and so on and so forth. The y-axis corresponds to normalized rates relative to the performance observed for indexed collections. For example, if the maximum observed rate when summing over an indexed collection was <code>100</code> iterations per second and the maximum observed rate when summing over an iterator was <code>70</code> iterations per second, the normalized rate is <code>70/100</code>, or <code>0.7</code>. Hence, a rate equal to unity indicates an observed rate equal to that of indexed collections. Anything less than unity indicates an observed rate less than that of indexed collections (i.e., summation involving a given input was slower than using built-in bracket notation). Anything greater than unity indicates an observed rate greater than that of indexed collections (i.e., summation involving a given input was faster than using built-in bracket notation).</p> <p>From the chart, we can observe that, for all array lengths, neither accessor protocol-compliant array-like objects nor iterators matched or exceeded the performance of indexed collections. Array-like objects implementing the accessor protocol were 15% slower than indexed collections, and iterators were 30% slower than indexed collections. These results confirm that the accessor protocol introduces an overhead relative to indexed collections, but not nearly as much as the overhead introduced by iterators.</p> <p>In short, the accessor protocol is both more flexible and more performant than using iterators.</p> <h3> Computed properties </h3> <p>Another alternative to the accessor protocol is to use defined properties having property accessors. When implementing lazy materialization and proxied element access prior to ECMAScript standardization of <code>Proxy</code> objects and support for <code>Array</code> subclassing, property descriptors were the primary way to emulate the built-in bracket notation of indexed collections.</p> <p>The following code sample shows an example class returning an array-like object emulating built-in bracket notation by explicitly defining property descriptors for all elements. Each property descriptor defines an accessor property with specialized <code>get</code> and <code>set</code> accessors.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="cm">/** * Class emulating built-in bracket notation for lazy materialization without subclassing. */</span> <span class="kd">class</span> <span class="nc">LazyArray</span> <span class="p">{</span> <span class="c1">// Define private instance fields:</span> <span class="err">#</span><span class="nx">data</span><span class="p">;</span> <span class="c1">// memoized value cache</span> <span class="cm">/** * Returns a new fixed-length "lazy" array. * * @param {number} len - number of elements * @returns {LazyArray} lazy array instance */</span> <span class="nf">constructor</span><span class="p">(</span> <span class="nx">len</span> <span class="p">)</span> <span class="p">{</span> <span class="nb">Object</span><span class="p">.</span><span class="nf">defineProperty</span><span class="p">(</span> <span class="k">this</span><span class="p">,</span> <span class="dl">'</span><span class="s1">length</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">configurable</span><span class="dl">'</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="dl">'</span><span class="s1">enumerable</span><span class="dl">'</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="dl">'</span><span class="s1">writable</span><span class="dl">'</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="dl">'</span><span class="s1">value</span><span class="dl">'</span><span class="p">:</span> <span class="nx">len</span> <span class="p">});</span> <span class="k">for </span><span class="p">(</span> <span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">len</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span> <span class="p">)</span> <span class="p">{</span> <span class="nb">Object</span><span class="p">.</span><span class="nf">defineProperty</span><span class="p">(</span> <span class="k">this</span><span class="p">,</span> <span class="nx">i</span><span class="p">,</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">configurable</span><span class="dl">'</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="dl">'</span><span class="s1">enumerable</span><span class="dl">'</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="dl">'</span><span class="s1">get</span><span class="dl">'</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nf">get</span><span class="p">(</span> <span class="nx">i</span> <span class="p">),</span> <span class="dl">'</span><span class="s1">set</span><span class="dl">'</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nf">set</span><span class="p">(</span> <span class="nx">i</span> <span class="p">)</span> <span class="p">});</span> <span class="p">}</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span> <span class="o">=</span> <span class="p">{};</span> <span class="p">}</span> <span class="cm">/** * Returns a getter. * * @private * @param {number} index - index * @returns {Function} getter */</span> <span class="err">#</span><span class="nf">get</span><span class="p">(</span> <span class="nx">index</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="kd">get</span><span class="p">;</span> <span class="cm">/** * Returns an element. * * @private * @returns {*} element */</span> <span class="kd">function</span> <span class="nf">get</span><span class="p">()</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">v</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">[</span> <span class="nx">index</span> <span class="p">];</span> <span class="k">if </span><span class="p">(</span> <span class="nx">v</span> <span class="o">===</span> <span class="k">void</span> <span class="mi">0</span> <span class="p">)</span> <span class="p">{</span> <span class="c1">// Perform "lazy" materialization:</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">[</span> <span class="nx">index</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">index</span><span class="p">;</span> <span class="c1">// note: toy example</span> <span class="k">return</span> <span class="nx">index</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">v</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="cm">/** * Returns a setter. * * @private * @param {number} index - index * @returns {Function} setter */</span> <span class="err">#</span><span class="nf">set</span><span class="p">(</span> <span class="nx">index</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="kd">set</span><span class="p">;</span> <span class="cm">/** * Sets an element value. * * @private * @param {*} value - value to set * @returns {boolean} boolean indicating whether a value was set */</span> <span class="kd">function</span> <span class="nf">set</span><span class="p">(</span> <span class="nx">value</span> <span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">[</span> <span class="nx">index</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span> <span class="k">return</span> <span class="kc">true</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">// Create a new "lazy" array:</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LazyArray</span><span class="p">(</span> <span class="mi">10</span> <span class="p">);</span> <span class="c1">// Print the list of elements:</span> <span class="k">for </span><span class="p">(</span> <span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">x</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span> <span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span> <span class="nx">x</span><span class="p">[</span> <span class="nx">i</span> <span class="p">]</span> <span class="p">);</span> <span class="p">}</span> </code></pre> </div> <p>There are several issues with this approach:</p> <ol> <li> Explicitly defining property descriptors is <strong>very</strong> expensive. Thus, especially for large arrays, instantiation can become prohibitively slow.</li> <li> Creating separate accessors for each property requires significantly more memory than the accessor protocol. The latter only needs two methods to serve all elements. The former requires two methods for every element.</li> <li> Element access is orders of magnitude slower than both built-in bracket notation and the accessor protocol.</li> </ol> <p>In the following grouped column chart, I show benchmark results for computing the sum over an array-like object which emulates built-in bracket notation by using property accessors. The chart extends the previous grouped column chart by including the same column groups as the previous chart and adding a new column to each group corresponding to property accessor performance results. As can be observed, using property accessors is more than one hundred times slower than either indexed collection built-in bracket notation or the accessor protocol.</p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fproperty_accessor_benchmarks_white_bkgd.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fproperty_accessor_benchmarks_white_bkgd.png" alt="Grouped column chart showing benchmark results for property accessors and normalized relative to computing the sum over indexed collections"></a></p> <h3> Proxies </h3> <p>The <a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy" rel="noopener noreferrer"><code>Proxy</code></a> object allows you to create a proxy for another object. During its creation, the proxy can be configured to intercept and redefine fundamental object operations, including getting and setting properties. While proxy objects are commonly used for logging property accesses, validation, formatting, or sanitizing inputs, they enable novel and extremely powerful extensions to built-in behavior. One such extension—implementing Python-like indexing in JavaScript—will be the subject of a future post.</p> <p>The following code sample defines a function for creating proxied array-like objects which intercept the operations for getting and setting property values. The proxy is created by providing two parameters:</p> <ol> <li> <code>target</code>: the original object we want to proxy.</li> <li> <code>handler</code>: an object defining which operations to intercept and how to redefine those operations. </li> </ol> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="cm">/** * Tests whether a string contains only integer values. * * @param {string} str - input string * @returns {boolean} boolean indicating whether a string contains only integer values */</span> <span class="kd">function</span> <span class="nf">isDigitString</span><span class="p">(</span> <span class="nx">str</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="sr">/^</span><span class="se">\d</span><span class="sr">+$/</span><span class="p">.</span><span class="nf">test</span><span class="p">(</span> <span class="nx">str</span> <span class="p">);</span> <span class="p">}</span> <span class="cm">/** * Returns a proxied array-like object. * * @param {number} len - array length * @returns {Proxy} proxy object */</span> <span class="kd">function</span> <span class="nf">lazyArray</span><span class="p">(</span> <span class="nx">len</span> <span class="p">)</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">target</span> <span class="o">=</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">length</span><span class="dl">'</span><span class="p">:</span> <span class="nx">len</span> <span class="p">};</span> <span class="k">return</span> <span class="k">new</span> <span class="nc">Proxy</span><span class="p">(</span> <span class="nx">target</span><span class="p">,</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">get</span><span class="dl">'</span><span class="p">:</span> <span class="p">(</span> <span class="nx">target</span><span class="p">,</span> <span class="nx">property</span> <span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span> <span class="nf">isDigitString</span><span class="p">(</span> <span class="nx">property</span> <span class="p">)</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">parseInt</span><span class="p">(</span> <span class="nx">property</span><span class="p">,</span> <span class="mi">10</span> <span class="p">);</span> <span class="c1">// note: toy example</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">target</span><span class="p">[</span> <span class="nx">property</span> <span class="p">];</span> <span class="p">},</span> <span class="dl">'</span><span class="s1">set</span><span class="dl">'</span><span class="p">:</span> <span class="p">(</span> <span class="nx">target</span><span class="p">,</span> <span class="nx">property</span><span class="p">,</span> <span class="nx">value</span> <span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">target</span><span class="p">[</span> <span class="nx">property</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span> <span class="k">return</span> <span class="kc">true</span><span class="p">;</span> <span class="p">}</span> <span class="p">});</span> <span class="p">}</span> <span class="c1">// Create a new "lazy" array:</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="nf">lazyArray</span><span class="p">(</span> <span class="mi">10</span> <span class="p">);</span> <span class="c1">// Print the list of elements:</span> <span class="k">for </span><span class="p">(</span> <span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">x</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span> <span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span> <span class="nx">x</span><span class="p">[</span> <span class="nx">i</span> <span class="p">]</span> <span class="p">);</span> <span class="p">}</span> </code></pre> </div> <p>While proxy objects avoid many of the issues described above for subclassing, iterators, and property accessors, including random access, instantiation costs, and general complexity, their primary limitation at the time of this blog post is performance.</p> <p>The following group column chart builds on the previous column charts by adding a new column to each group corresponding to proxy object results. As can be observed, using proxy objects fares no better than the property accessor approach described above. Performance is on par with property accessors and more than one hundred times slower than either indexed collection built-in bracket notation or the accessor protocol.</p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fproxy_benchmarks_white_bkgd.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.stdlib.io%2Fcontent%2Fimages%2F2024%2F08%2Fproxy_benchmarks_white_bkgd.png" alt="Grouped column chart showing benchmark results for proxied array-like objects and normalized relative to computing the sum over indexed collections"></a></p> <h3> Using "at" rather than "get" </h3> <p>The 2022 revision of the ECMAScript Standard added an <a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at" rel="noopener noreferrer"><code>at</code></a> method to array and typed array prototypes which accepts a single integer argument and returns the element at that index, allowing for both positive and negative integers. Why, then, do we need another method for retrieving an array element as proposed in the accessor protocol? This question seems especially salient given that the protocol's <code>get</code> method only requires support for nonnegative integer arguments, making the <code>get</code> method seem less powerful.</p> <p>There are a few reasons why the accessor protocol chooses to use <code>get</code>, rather than <code>at</code>.</p> <ol> <li> The name <code>get</code> has symmetry with the name <code>set</code>.</li> <li> The <code>at</code> method does not have a built-in method equivalent for setting element values. The <code>set</code> method is only present on typed arrays, not generic arrays, and does not support negative target offsets.</li> <li> The <code>at</code> method does not match built-in bracket notation semantics. When using a negative integer within square brackets, the integer value is serialized to a string <strong>before</strong> property lookup (i.e., <code>x[-1]</code> is equivalent to <code>x['-1']</code>). Unless negative integer properties are explicitly defined, <code>x[-1]</code> will return <code>undefined</code>. In contrast, <code>x.at(-1)</code> is equivalent to <code>x[x.length-1]</code>, which, for non-empty arrays, will return the last array element. Accordingly, the <code>get</code> method of the accessor protocol allows protocol-compliant implementations to match built-in bracket notation semantics exactly.</li> <li> The accessor protocol does <strong>not</strong> specify the behavior of out-of-bounds index arguments. In contrast, when an index argument is negative, the <code>at</code> method normalizes a negative index value according to <code>index + x.length</code>. This, however, is not the only reasonable behavior, depending on the use case. For example, an array-like object implementation may want to clamp out-of-bounds index arguments, such that indices less than zero are clamped to zero (i.e., the first index) and indices greater than <code>x.length-1</code> are clamped to <code>x.length-1</code> (i.e., the last index). Alternatively, an array-like object implementation may want to wrap out-of-bounds index arguments using modulo arithmetic. Lastly, an array-like object implementation may want to raise an exception when an index is out-of-bounds. In short, the <code>at</code> method prescribes a particular mode of behavior, which may not be appropriate for all use cases.</li> <li> By only requiring support for nonnegative integer arguments, the accessor protocol allows protocol-compliant implementations to minimize branching and ensure better performance. While convenient, support for negative indices is not necessary for generalized array-like object iteration.</li> <li> As the EMCAScript Standard does not define a <code>get</code> method for arrays and typed arrays (at least not yet!), the presence or absence of a <code>get</code> method in combination with a <code>set</code> method and <code>length</code> property allows for distinguishing indexed collections from array-like objects implementing the accessor protocol. The combination of <code>at</code>, <code>set</code>, and <code>length</code> would not be sufficient for making such a distinction. This ability is important in order to allow downstream array-like object consumers to implement optimized code paths and ensure optimal performance.</li> </ol> <p>For these reasons, an <code>at</code> method is not a suitable candidate for use in generalized array-like object iteration.</p> <h2> Examples </h2> <p>Now that we've considered the alternatives and established the motivation and need for the accessor protocol, what can we do with it?! Glad you asked! To answer this question, I provide several concrete implementations below.</p> <h3> Complex number arrays </h3> <p>Complex numbers have applications in many scientific domains, including signal processing, fluid dynamics, and quantum mechanics. We can extend the concept of typed arrays to the realm of complex numbers by storing real and imaginary components as interleaved values within a real-valued typed array. In the following code sample, I define a minimal immutable complex number constructor and a complex number array class implementing the accessor protocol.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="cm">/** * Class defining a minimal immutable complex number. */</span> <span class="kd">class</span> <span class="nc">Complex</span> <span class="p">{</span> <span class="c1">// Define private instance fields:</span> <span class="err">#</span><span class="nx">re</span><span class="p">;</span> <span class="c1">// real component</span> <span class="err">#</span><span class="nx">im</span><span class="p">;</span> <span class="c1">// imaginary component</span> <span class="cm">/** * Returns a new complex number instance. * * @param {number} re - real component * @param {number} im - imaginary component * @returns {Complex} complex number instance */</span> <span class="nf">constructor</span><span class="p">(</span> <span class="nx">re</span><span class="p">,</span> <span class="nx">im</span> <span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">re</span> <span class="o">=</span> <span class="nx">re</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">im</span> <span class="o">=</span> <span class="nx">im</span><span class="p">;</span> <span class="p">}</span> <span class="cm">/** * Returns the real component of a complex number. * * @returns {number} real component */</span> <span class="kd">get</span> <span class="nf">re</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">re</span><span class="p">;</span> <span class="p">}</span> <span class="cm">/** * Returns the imaginary component of a complex number. * * @returns {number} imaginary component */</span> <span class="kd">get</span> <span class="nf">im</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">im</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="cm">/** * Class defining a complex number array implementing the accessor protocol. */</span> <span class="kd">class</span> <span class="nc">Complex128Array</span> <span class="p">{</span> <span class="c1">// Define private instance fields:</span> <span class="err">#</span><span class="nx">length</span><span class="p">;</span> <span class="c1">// array length</span> <span class="err">#</span><span class="nx">data</span><span class="p">;</span> <span class="c1">// underlying data buffer</span> <span class="cm">/** * Returns a new complex number array instance. * * @param {number} len - array length * @returns {Complex128Array} complex array instance */</span> <span class="nf">constructor</span><span class="p">(</span> <span class="nx">len</span> <span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">length</span> <span class="o">=</span> <span class="nx">len</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Float64Array</span><span class="p">(</span> <span class="nx">len</span><span class="o">*</span><span class="mi">2</span> <span class="p">);</span> <span class="c1">// accommodate interleaved components</span> <span class="p">}</span> <span class="cm">/** * Returns the array length. * * @returns {number} array length */</span> <span class="kd">get</span> <span class="nf">length</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">length</span><span class="p">;</span> <span class="p">}</span> <span class="cm">/** * Returns an array element. * * @param {integer} index - element index * @returns {(Complex|void)} element value */</span> <span class="nf">get</span><span class="p">(</span> <span class="nx">index</span> <span class="p">)</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span> <span class="nx">index</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> <span class="nx">index</span> <span class="o">&gt;=</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">length</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="kd">const</span> <span class="nx">ptr</span> <span class="o">=</span> <span class="nx">index</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span> <span class="c1">// account for interleaved components</span> <span class="k">return</span> <span class="k">new</span> <span class="nc">Complex</span><span class="p">(</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">[</span> <span class="nx">ptr</span> <span class="p">],</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">[</span> <span class="nx">ptr</span><span class="o">+</span><span class="mi">1</span> <span class="p">]</span> <span class="p">);</span> <span class="p">}</span> <span class="cm">/** * Sets an array element. * * @param {Complex} value - value to set * @param {integer} index - element index * @returns {void} */</span> <span class="nf">set</span><span class="p">(</span> <span class="nx">value</span><span class="p">,</span> <span class="nx">index</span> <span class="p">)</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span> <span class="nx">index</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> <span class="nx">index</span> <span class="o">&gt;=</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">length</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="kd">const</span> <span class="nx">ptr</span> <span class="o">=</span> <span class="nx">index</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span> <span class="c1">// account for interleaved components</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">[</span> <span class="nx">ptr</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">value</span><span class="p">.</span><span class="nx">re</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">[</span> <span class="nx">ptr</span><span class="o">+</span><span class="mi">1</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">value</span><span class="p">.</span><span class="nx">im</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">// Create a new complex number array:</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Complex128Array</span><span class="p">(</span> <span class="mi">10</span> <span class="p">);</span> <span class="c1">// returns &lt;Complex128Array&gt;</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">z1</span> <span class="o">=</span> <span class="nx">x</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="mi">1</span> <span class="p">);</span> <span class="c1">// returns &lt;Complex&gt;</span> <span class="kd">const</span> <span class="nx">re1</span> <span class="o">=</span> <span class="nx">z1</span><span class="p">.</span><span class="nx">re</span><span class="p">;</span> <span class="c1">// returns 0.0</span> <span class="kd">const</span> <span class="nx">im1</span> <span class="o">=</span> <span class="nx">z1</span><span class="p">.</span><span class="nx">im</span><span class="p">;</span> <span class="c1">// returns 0.0</span> <span class="c1">// Set the second element:</span> <span class="nx">x</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span> <span class="k">new</span> <span class="nc">Complex</span><span class="p">(</span> <span class="mf">3.0</span><span class="p">,</span> <span class="mf">4.0</span> <span class="p">),</span> <span class="mi">1</span> <span class="p">);</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">z2</span> <span class="o">=</span> <span class="nx">x</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="mi">1</span> <span class="p">);</span> <span class="c1">// returns &lt;Complex&gt;</span> <span class="kd">const</span> <span class="nx">re2</span> <span class="o">=</span> <span class="nx">z2</span><span class="p">.</span><span class="nx">re</span><span class="p">;</span> <span class="c1">// returns 3.0</span> <span class="kd">const</span> <span class="nx">im2</span> <span class="o">=</span> <span class="nx">z2</span><span class="p">.</span><span class="nx">im</span><span class="p">;</span> <span class="c1">// returns 4.0</span> </code></pre> </div> <p>If you are interested in a concrete implementation of complex number arrays, see the <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/array/complex128" rel="noopener noreferrer"><code>Complex128Array</code></a> and <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/array/complex64" rel="noopener noreferrer"><code>Complex64Array</code></a> packages provided by <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a>. We'll have more to say about these packages in future blog posts.</p> <h3> Sparse arrays </h3> <p>Applications of sparse arrays commonly arise in network theory, numerical analysis, natural language processing, and other areas of science and engineering. When data is "sparse" (i.e., most elements are non-zero), sparse array storage can be particularly advantageous in reducing required memory storage and in accelerating the computation of operations involving only non-zero elements.</p> <p>In the following code sample, I define a minimal accessor protocol-compliant sparse array class using the dictionary of keys (DOK) format and supporting arbitrary fill values. Support for arbitrary fill values is useful as it extends the concept of sparsity to any array having a majority of elements equal to the same value. For such arrays, we can compress an array to a format which stores a single fill value and only those elements which are not equal to the repeated value. This approach is implemented below.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="cm">/** * Class defining a sparse array implementing the accessor protocol. */</span> <span class="kd">class</span> <span class="nc">SparseArray</span> <span class="p">{</span> <span class="c1">// Define private instance fields:</span> <span class="err">#</span><span class="nx">length</span><span class="p">;</span> <span class="c1">// array length</span> <span class="err">#</span><span class="nx">data</span><span class="p">;</span> <span class="c1">// dictionary containing array elements</span> <span class="err">#</span><span class="nx">fill</span><span class="p">;</span> <span class="c1">// fill value</span> <span class="cm">/** * Returns a new sparse array instance. * * @param {number} len - array length * @param {*} fill - fill value * @returns {SparseArray} sparse array instance */</span> <span class="nf">constructor</span><span class="p">(</span> <span class="nx">len</span><span class="p">,</span> <span class="nx">fill</span> <span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">length</span> <span class="o">=</span> <span class="nx">len</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span> <span class="o">=</span> <span class="p">{};</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">fill</span> <span class="o">=</span> <span class="nx">fill</span><span class="p">;</span> <span class="p">}</span> <span class="cm">/** * Returns the array length. * * @returns {number} array length */</span> <span class="kd">get</span> <span class="nf">length</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">length</span><span class="p">;</span> <span class="p">}</span> <span class="cm">/** * Returns an array element. * * @param {number} index - element index * @returns {*} element value */</span> <span class="nf">get</span><span class="p">(</span> <span class="nx">index</span> <span class="p">)</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span> <span class="nx">index</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> <span class="nx">index</span> <span class="o">&gt;=</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">length</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="kd">const</span> <span class="nx">v</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">[</span> <span class="nx">index</span> <span class="p">];</span> <span class="k">if </span><span class="p">(</span> <span class="nx">v</span> <span class="o">===</span> <span class="k">void</span> <span class="mi">0</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">fill</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">v</span><span class="p">;</span> <span class="p">}</span> <span class="cm">/** * Sets an array element. * * @param {*} value - value to set * @param {number} index - element index * @returns {void} */</span> <span class="nf">set</span><span class="p">(</span> <span class="nx">value</span><span class="p">,</span> <span class="nx">index</span> <span class="p">)</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span> <span class="nx">index</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> <span class="nx">index</span> <span class="o">&gt;=</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">length</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">[</span> <span class="nx">index</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">// Create a new sparse array:</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SparseArray</span><span class="p">(</span> <span class="mi">10</span><span class="p">,</span> <span class="mf">0.0</span> <span class="p">);</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">v1</span> <span class="o">=</span> <span class="nx">x</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="mi">1</span> <span class="p">);</span> <span class="c1">// returns 0.0</span> <span class="c1">// Set the second element:</span> <span class="nx">x</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span> <span class="mf">4.0</span><span class="p">,</span> <span class="mi">1</span> <span class="p">);</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">v2</span> <span class="o">=</span> <span class="nx">x</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="mi">1</span> <span class="p">);</span> <span class="c1">// returns 4.0</span> </code></pre> </div> <h3> Lazy arrays </h3> <p>While less broadly applicable, situations may arise in which you want an array-like object supporting lazy materialization and random access. For example, suppose each element is the result of an expensive computation, and you want to defer the computation of each element until first accessed.</p> <p>In the following code sample, I define a class supporting lazy materialization of randomly generated element values. When an element is accessed, a class instance eagerly computes all un-computed element values up to and including the accessed element. Once an element value is computed, the value is memoized and can only be overridden by explicitly setting the element.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="cm">/** * Class defining an array-like object supporting lazy materialization of random values. */</span> <span class="kd">class</span> <span class="nc">LazyRandomArray</span> <span class="p">{</span> <span class="c1">// Define private instance fields:</span> <span class="err">#</span><span class="nx">data</span><span class="p">;</span> <span class="c1">// underlying data buffer</span> <span class="cm">/** * Returns a new lazy random array. * * @returns {LazyRandomArray} new instance */</span> <span class="nf">constructor</span><span class="p">()</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span> <span class="o">=</span> <span class="p">[];</span> <span class="p">}</span> <span class="cm">/** * Materializes array elements. * * @private * @param {number} len - array length */</span> <span class="err">#</span><span class="nf">materialize</span><span class="p">(</span> <span class="nx">len</span> <span class="p">)</span> <span class="p">{</span> <span class="k">for </span><span class="p">(</span> <span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">len</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span> <span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span> <span class="nb">Math</span><span class="p">.</span><span class="nf">random</span><span class="p">()</span> <span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="cm">/** * Returns the array length. * * @returns {number} array length */</span> <span class="kd">get</span> <span class="nf">length</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="p">}</span> <span class="cm">/** * Returns an array element. * * @param {number} index - element index * @returns {*} element value */</span> <span class="nf">get</span><span class="p">(</span> <span class="nx">index</span> <span class="p">)</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span> <span class="nx">index</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="k">if </span><span class="p">(</span> <span class="nx">index</span> <span class="o">&gt;=</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">.</span><span class="nx">length</span> <span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nf">materialize</span><span class="p">(</span> <span class="nx">index</span><span class="o">+</span><span class="mi">1</span> <span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">[</span> <span class="nx">index</span> <span class="p">];</span> <span class="p">}</span> <span class="cm">/** * Sets an array element. * * @param {*} value - value to set * @param {number} index - element index * @returns {void} */</span> <span class="nf">set</span><span class="p">(</span> <span class="nx">value</span><span class="p">,</span> <span class="nx">index</span> <span class="p">)</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span> <span class="nx">index</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="k">if </span><span class="p">(</span> <span class="nx">index</span> <span class="o">&gt;=</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">.</span><span class="nx">length</span> <span class="p">)</span> <span class="p">{</span> <span class="c1">// Materialize `index+1` in order to ensure "fast" elements:</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nf">materialize</span><span class="p">(</span> <span class="nx">index</span><span class="o">+</span><span class="mi">1</span> <span class="p">);</span> <span class="p">}</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">[</span> <span class="nx">index</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">// Create a new lazy array:</span> <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LazyRandomArray</span><span class="p">();</span> <span class="c1">// Retrieve the tenth element:</span> <span class="kd">const</span> <span class="nx">v1</span> <span class="o">=</span> <span class="nx">x</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="mi">9</span> <span class="p">);</span> <span class="c1">// returns &lt;number&gt;</span> <span class="c1">// Set the tenth element:</span> <span class="nx">x</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span> <span class="mf">4.0</span><span class="p">,</span> <span class="mi">9</span> <span class="p">);</span> <span class="c1">// Retrieve the tenth element:</span> <span class="kd">const</span> <span class="nx">v2</span> <span class="o">=</span> <span class="nx">x</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="mi">9</span> <span class="p">);</span> <span class="c1">// returns 4.0</span> <span class="c1">// Return the number of elements in the array:</span> <span class="kd">const</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">x</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="c1">// returns 10</span> </code></pre> </div> <h2> stdlib </h2> <p>While array-like objects implementing the accessor protocol are useful in their own right, they become all the more powerful when combined with functional APIs which are accessor protocol-aware. Fortunately, <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> treats accessor protocol-compliant objects as first-class citizens, providing support for them throughout its codebase.</p> <p>For example, the following code sample uses <a href="proxy.php?url=https://github.com/stdlib-js/array-put" rel="noopener noreferrer"><code>@stdlib/array-put</code></a> to replace the elements of an accessor protocol-compliant strided array at specified indices.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">put</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span> <span class="dl">'</span><span class="s1">@stdlib/array-put</span><span class="dl">'</span> <span class="p">);</span> <span class="cm">/** * Class defining a strided array. */</span> <span class="kd">class</span> <span class="nc">StridedArray</span> <span class="p">{</span> <span class="c1">// Define private instance fields:</span> <span class="err">#</span><span class="nx">length</span><span class="p">;</span> <span class="c1">// array length</span> <span class="err">#</span><span class="nx">data</span><span class="p">;</span> <span class="c1">// underlying data buffer</span> <span class="err">#</span><span class="nx">stride</span><span class="p">;</span> <span class="c1">// step size (i.e., the index increment between successive values)</span> <span class="err">#</span><span class="nx">offset</span><span class="p">;</span> <span class="c1">// index of the first indexed value in the data buffer</span> <span class="cm">/** * Returns a new StridedArray instance. * * @param {integer} N - number of indexed elements * @param {ArrayLikeObject} data - underlying data buffer * @param {number} stride - step size * @param {number} offset - index of the first indexed value in the data buffer * @returns {StridedArray} strided array instance */</span> <span class="nf">constructor</span><span class="p">(</span> <span class="nx">N</span><span class="p">,</span> <span class="nx">data</span><span class="p">,</span> <span class="nx">stride</span><span class="p">,</span> <span class="nx">offset</span> <span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">length</span> <span class="o">=</span> <span class="nx">N</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span> <span class="o">=</span> <span class="nx">data</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">stride</span> <span class="o">=</span> <span class="nx">stride</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">offset</span> <span class="o">=</span> <span class="nx">offset</span><span class="p">;</span> <span class="p">}</span> <span class="cm">/** * Returns the array length. * * @returns {number} array length */</span> <span class="kd">get</span> <span class="nf">length</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">length</span><span class="p">;</span> <span class="p">}</span> <span class="cm">/** * Returns the element located at a specified index. * * @param {number} index - element index * @returns {(void|*)} element value */</span> <span class="nf">get</span><span class="p">(</span> <span class="nx">index</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">[</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">offset</span> <span class="o">+</span> <span class="nx">index</span><span class="o">*</span><span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">stride</span> <span class="p">];</span> <span class="p">}</span> <span class="cm">/** * Sets the value for an element located at a specified index. * * @param {*} value - value to set * @param {number} index - element index */</span> <span class="nf">set</span><span class="p">(</span> <span class="nx">value</span><span class="p">,</span> <span class="nx">index</span> <span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">data</span><span class="p">[</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">offset</span> <span class="o">+</span> <span class="nx">index</span><span class="o">*</span><span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">stride</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">// Define a data buffer:</span> <span class="kd">const</span> <span class="nx">buf</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Float64Array</span><span class="p">(</span> <span class="p">[</span> <span class="mf">1.0</span><span class="p">,</span> <span class="mf">2.0</span><span class="p">,</span> <span class="mf">3.0</span><span class="p">,</span> <span class="mf">4.0</span><span class="p">,</span> <span class="mf">5.0</span><span class="p">,</span> <span class="mf">6.0</span><span class="p">,</span> <span class="mf">7.0</span><span class="p">,</span> <span class="mf">8.0</span> <span class="p">]</span> <span class="p">);</span> <span class="c1">// Create a strided view over the data buffer:</span> <span class="kd">const</span> <span class="nx">x1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StridedArray</span><span class="p">(</span> <span class="mi">4</span><span class="p">,</span> <span class="nx">buf</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span> <span class="p">);</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">v1</span> <span class="o">=</span> <span class="nx">x1</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="mi">1</span> <span class="p">);</span> <span class="c1">// returns 4.0</span> <span class="c1">// Retrieve the fourth element:</span> <span class="kd">const</span> <span class="nx">v2</span> <span class="o">=</span> <span class="nx">x1</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="mi">3</span> <span class="p">);</span> <span class="c1">// returns 8.0</span> <span class="c1">// Replace the second and fourth elements with new values:</span> <span class="nf">put</span><span class="p">(</span> <span class="nx">x</span><span class="p">,</span> <span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">3</span> <span class="p">],</span> <span class="p">[</span> <span class="o">-</span><span class="nx">v1</span><span class="p">,</span> <span class="o">-</span><span class="nx">v2</span> <span class="p">]</span> <span class="p">);</span> <span class="c1">// Retrieve the second element:</span> <span class="kd">const</span> <span class="nx">v3</span> <span class="o">=</span> <span class="nx">x1</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="mi">1</span> <span class="p">);</span> <span class="c1">// returns -4.0</span> <span class="c1">// Retrieve the fourth element:</span> <span class="kd">const</span> <span class="nx">v4</span> <span class="o">=</span> <span class="nx">x1</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="mi">3</span> <span class="p">);</span> <span class="c1">// returns -8.0</span> </code></pre> </div> <p>In addition to supporting accessor protocol-compliant array-like objects in <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/array" rel="noopener noreferrer">utilities</a>, <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/blas" rel="noopener noreferrer">linear algebra operations</a>, and other vectorized APIs, stdlib has leveraged the accessor protocol to implement typed arrays supporting data types beyond real-valued numbers. To see this in action, see stdlib's <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/array/complex128" rel="noopener noreferrer"><code>Complex128Array</code></a>, <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/array/complex64" rel="noopener noreferrer"><code>Complex64Array</code></a>, and <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/array/boolean" rel="noopener noreferrer"><code>BooleanArray</code></a> typed array constructors.</p> <p>In short, the accessor protocol is a powerful abstraction which is not only performant, but can accommodate new use cases with minimal effort.</p> <h2> Conclusion </h2> <p>In this post, we dove deep into techniques for array-like object iteration. Along the way, we discussed the limitations of current approaches and identified opportunities for a lightweight means for element retrieval that can flexibly accommodate a variety of use cases, including strided arrays, arrays supporting deferred computation, shared memory views, and sparse arrays. We then learned about the accessor protocol which provides a straightforward solution for accessing elements in a manner consistent with built-in bracket notation and having minimal performance overhead. With the power and promise of the accessor protocol firmly established, we wrapped up by showcasing a few demos of the accessor protocol in action.</p> <p>In short, we covered a lot of ground, but I hope you learned a thing or two along the way. In future posts, we'll explore more applications of the accessor protocol, including in the implementation of complex number and boolean typed arrays. We hope that you'll continue to follow along as we share our insights and that you'll join us in our mission to realize a future where JavaScript and the web are preferred environments for numerical and scientific computation. 🚀</p> <p><a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> is an open source software project dedicated to providing a comprehensive suite of robust, high-performance libraries to accelerate your project's development and give you peace of mind knowing that you're depending on expertly crafted, high-quality software.</p> <p>If you've enjoyed this post, give us a star 🌟 on <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">GitHub</a> and consider <a href="proxy.php?url=https://opencollective.com/stdlib" rel="noopener noreferrer">financially supporting</a> the project. Your contributions and continued support help ensure the project's long-term success and are greatly appreciated!</p> <p>If you'd like to view the code covered in this post on GitHub, please visit the source code <a href="proxy.php?url=https://github.com/stdlib-js/blog-introducing-the-accessor-protocol-1/tree/main" rel="noopener noreferrer">repository</a>.</p> javascript node programming webdev How to call Fortran routines from JavaScript with Node.js Athan Wed, 24 Jul 2024 02:25:09 +0000 https://dev.to/stdlib/how-to-call-fortran-routines-from-javascript-with-nodejs-40ig https://dev.to/stdlib/how-to-call-fortran-routines-from-javascript-with-nodejs-40ig <p><a href="proxy.php?url=https://fortran-lang.org" rel="noopener noreferrer">Fortran</a> is a commonly used language for numerical and scientific computation, underpinning many of the higher-level numerical libraries and programming languages in use today. Since Fortran's original development in 1957, researchers and software developers have used Fortran as a primary language for high-performance computation and authored thousands of high-performance programs and libraries for astronomy, climate modeling, computational chemistry, fluid dynamics, simulation, weather prediction, and more.</p> <p>Rather than attempt to re-implement the entirety of the Fortran ecosystem, programming languages, such as R, MATLAB, and Julia, and numerical libraries, such as NumPy and SciPy, have opted to provide language-specific wrappers around Fortran functionality. Despite significant interest in numerical computing on the web, no one has developed comprehensive JavaScript bindings for Fortran libraries. That is, until now.</p> <p>In this post, we'll begin laying the groundwork for authoring high-performance Fortran bindings and explore how to call Fortran routines from JavaScript using <a href="proxy.php?url=https://nodejs.org" rel="noopener noreferrer">Node.js</a>. We'll start with a brief introduction to Fortran, followed by writing and compiling a simple Fortran program. We'll then discuss how to use <a href="proxy.php?url=https://nodejs.org/api/n-api.html" rel="noopener noreferrer">Node-API</a> to link a compiled Fortran routine to the Node.js runtime. And we'll conclude by demonstrating how to use <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> to simplify the authoring of Node.js bindings.</p> <p>By the end of this post, you'll have a good understanding of how to call Fortran routines from JavaScript using Node.js.</p> <h2> Prerequisites </h2> <p>Throughout this post, we'll be writing sample programs and performing various steps to compile and run Fortran programs. We'll assume that you have some familiarity with using the terminal, executing commands, and running JavaScript programs. For the most part, terminal commands will assume a Linux-based operating system. Some modifications may be required to successfully run commands and perform compilation steps on Windows.</p> <p>If you're hoping to follow along, you'll need the following prerequisites:</p> <ol> <li> <p>You'll want to make sure you've installed the latest stable <a href="proxy.php?url=https://nodejs.org" rel="noopener noreferrer">Node.js</a> version. To check whether Node.js is already installed<br> </p> <pre class="highlight shell"><code><span class="nv">$ </span>node <span class="nt">--version</span> </code></pre> <p>where <code>$</code> is the terminal prompt and <code>node --version</code> is the entered command.</p> </li> <li><p>We'll be using <a href="proxy.php?url=https://www.npmjs.com/package/npm" rel="noopener noreferrer">npm</a> for installing Node.js dependencies, but you should be able to adapt any installation commands to your preferred JavaScript package manager (e.g., Yarn, pnpm, etc).</p></li> <li><p>In order to generate build files appropriate for your operating system (OS), we'll be using <a href="proxy.php?url=https://github.com/nodejs/node-gyp" rel="noopener noreferrer">node-gyp</a>, which, in turn, has varying prerequisites depending on your OS, including the availability of Python. For more details, see the node-gyp <a href="proxy.php?url=https://github.com/nodejs/node-gyp#installation" rel="noopener noreferrer">installation instructions</a>.</p></li> <li> <p>In order to compile Fortran programs, you'll need a Fortran compiler. In this post, we'll be using <a href="proxy.php?url=https://gcc.gnu.org/fortran/" rel="noopener noreferrer">GNU Fortran</a> (GFortran) to compile Fortran code. GFortran is an implementation of the Fortran programming language in the widely used <a href="proxy.php?url=https://gcc.gnu.org" rel="noopener noreferrer">GNU Compiler Collection</a> (GCC), an open-source project maintained under the umbrella of the GNU Project. To check whether GFortran is already installed<br> </p> <pre class="highlight shell"><code><span class="nv">$ </span>gfortran <span class="nt">--version</span> </code></pre> </li> <li> <p>And finally, we'll be using GCC to compile and link C source code. To check whether GCC is already installed<br> </p> <pre class="highlight shell"><code><span class="nv">$ </span>gcc <span class="nt">--version</span> </code></pre> </li> </ol> <p>If you don't have one or more of the above installed, you'll want to go ahead and install those now.</p> <h2> Introduction to Fortran </h2> <p>Fortran is a compiled, imperative programming language well-suited to numerical and scientific computation. Known for its high performance, versatility, and ease of use, Fortran is natively parallel and has built-in support for array handling. This makes Fortran a popular choice for scientific computing.</p> <p>Many fundamental libraries for numerical computation, such as <a href="proxy.php?url=https://netlib.org/blas/" rel="noopener noreferrer">BLAS</a> (<strong>b</strong>asic <strong>l</strong>inear <strong>a</strong>lgebra <strong>s</strong>ubprograms), <a href="proxy.php?url=https://www.netlib.org/lapack/" rel="noopener noreferrer">LAPACK</a> (<strong>l</strong>inear <strong>a</strong>lgebra <strong>pack</strong>age), <a href="proxy.php?url=https://www.netlib.org/slatec/" rel="noopener noreferrer">SLATEC</a>, and <a href="proxy.php?url=https://www.netlib.org/minpack/" rel="noopener noreferrer">MINPACK</a>, among many others, are written in Fortran. These libraries serve as the foundation of popular open-source numerical computation libraries, such as NumPy and SciPy, and numerical programming languages, such as R, MATLAB, and Julia.</p> <p>Given Fortran's widespread usage and decades of development, one could argue that most modern numerical programming languages and libraries are simply fancy wrappers around Fortran routines. Therefore, enabling JavaScript to call Fortran routines not only leverages these high-performance libraries but also positions JavaScript as a viable language for machine learning and other computation-intensive tasks.</p> <p>Now, let's get started by compiling our first Fortran program!</p> <h3> Compiling our first Fortran program </h3> <p>Recognizing that some readers of this post may not be familiar with Fortran, let's kick things off by writing a "Hello world" program in Fortran for adding two numbers and printing the result. To begin, open up a text editor and create the file <code>add.f90</code> containing the following code which contains a function definition for adding two integers and a <code>main</code> program which calls that function and prints the result.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight fortran"><code><span class="c1">! file: add.f90</span><span class="w"> </span><span class="c1">!&gt;</span><span class="w"> </span><span class="c1">! Adds two integer values.</span><span class="w"> </span><span class="c1">!</span><span class="w"> </span><span class="c1">! @param {integer} x - first input value</span><span class="w"> </span><span class="c1">! @param {integer} y - second input value</span><span class="w"> </span><span class="c1">!&lt;</span><span class="w"> </span><span class="kt">integer</span><span class="w"> </span><span class="k">function</span><span class="w"> </span><span class="n">add</span><span class="p">(</span><span class="w"> </span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="c1">! Define the input parameters:</span><span class="w"> </span><span class="kt">integer</span><span class="p">,</span><span class="w"> </span><span class="k">intent</span><span class="p">(</span><span class="k">in</span><span class="p">)</span><span class="w"> </span><span class="p">::</span><span class="w"> </span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="c1">! ..</span><span class="w"> </span><span class="c1">! Compute the sum:</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">function</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="c1">!&gt;</span><span class="w"> </span><span class="c1">! Main execution sequence.</span><span class="w"> </span><span class="c1">!&lt;</span><span class="w"> </span><span class="k">program</span><span class="w"> </span><span class="n">main</span><span class="w"> </span><span class="c1">! Local variables:</span><span class="w"> </span><span class="kt">character</span><span class="p">(</span><span class="nb">len</span><span class="o">=</span><span class="mi">999</span><span class="p">)</span><span class="w"> </span><span class="p">::</span><span class="w"> </span><span class="n">str</span><span class="p">,</span><span class="w"> </span><span class="n">tmp</span><span class="w"> </span><span class="c1">! ..</span><span class="w"> </span><span class="c1">! Intrinsic functions:</span><span class="w"> </span><span class="k">intrinsic</span><span class="w"> </span><span class="nb">adjustl</span><span class="p">,</span><span class="w"> </span><span class="nb">trim</span><span class="w"> </span><span class="c1">! ..</span><span class="w"> </span><span class="c1">! Define a variable for storing the sum:</span><span class="w"> </span><span class="kt">integer</span><span class="w"> </span><span class="p">::</span><span class="w"> </span><span class="n">res</span><span class="w"> </span><span class="c1">! ..</span><span class="w"> </span><span class="c1">! Compute the sum:</span><span class="w"> </span><span class="n">res</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">add</span><span class="p">(</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="w"> </span><span class="mi">15</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="c1">! ..</span><span class="w"> </span><span class="c1">! Print the results:</span><span class="w"> </span><span class="k">write</span><span class="w"> </span><span class="p">(</span><span class="n">str</span><span class="p">,</span><span class="w"> </span><span class="s1">'(I15)'</span><span class="p">)</span><span class="w"> </span><span class="n">res</span><span class="w"> </span><span class="n">tmp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">adjustl</span><span class="p">(</span><span class="w"> </span><span class="n">str</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">print</span><span class="w"> </span><span class="s1">'(A, A)'</span><span class="p">,</span><span class="w"> </span><span class="s1">'The sum of 12 and 15 is '</span><span class="p">,</span><span class="w"> </span><span class="nb">trim</span><span class="p">(</span><span class="w"> </span><span class="n">tmp</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">program</span><span class="w"> </span></code></pre> </div> <p>There are a few things to note in the above program. The first is that, in general, Fortran routines pass arguments by reference. A common practice is to define and pass output variables for storing results—something that we'll revisit later in this post.</p> <p>Second, a best practice is to specify the <code>intent(xx)</code> of a variable. In the code above, <code>intent(in)</code> indicates that an argument must not be redefined or become undefined during the execution of a subroutine. Similarly, <code>intent(out)</code> indicates that an argument must be defined before the argument is referenced within a subroutine.</p> <p>Third, in order to print formatted results, we need to perform various string manipulation steps, including writing to character buffers (<code>write</code>), adjusting alignment (<code>adjustl</code>), and trimming results (<code>trim</code>).</p> <p>For the purposes of getting something working, our program defines a single variable <code>res</code>, which receives the result of passing two number literals to an <code>add</code> function. To test whether the code works, we first need to see if it compiles, and, to do this, we'll use the <a href="proxy.php?url=https://gcc.gnu.org/fortran/" rel="noopener noreferrer">GNU Fortran</a> (GFortran) compiler, which is part of the GNU Compiler Collection (GCC). While other Fortran compilers exist, such as the Intel Fortran Compiler, LLVM Flang, and LFortran, GFortran is one of the most widely used Fortran compilers, and what we cover in this post should readily translate elsewhere.</p> <p>In a terminal, navigate to the directory containing <code>add.f90</code>, and execute the following command<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>gfortran add.f90 <span class="nt">-o</span> add.out <span class="o">&amp;&amp;</span> ./add.out </code></pre> </div> <p>where <code>add.f90</code> is the file path of the file to be compiled and <code>add.out</code> is the file path to use for storing a generated executable. If all went according to plan, you should see the following text as output<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>The sum of 12 and 15 is 27 </code></pre> </div> <h3> Defining another Fortran subroutine </h3> <p>In <code>add.f90</code>, we defined a self-contained Fortran program which adds two numbers and prints the result. But what if we want to call Fortran functions and subroutines from another Fortran file or from outside of Fortran, such as from JavaScript running in Node.js?</p> <p>To see how this is done, let's begin by creating another Fortran file <code>mul.f90</code>, this time containing a subroutine for multiplying two integers and returning an integer result.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight fortran"><code><span class="c1">! file: mul.f90</span><span class="w"> </span><span class="c1">!&gt;</span><span class="w"> </span><span class="c1">! Multiplies two integer values.</span><span class="w"> </span><span class="c1">!</span><span class="w"> </span><span class="c1">! @param {integer} x - first input value</span><span class="w"> </span><span class="c1">! @param {integer} y - second input value</span><span class="w"> </span><span class="c1">! @param {integer} res - output argument for storing the result</span><span class="w"> </span><span class="c1">!&lt;</span><span class="w"> </span><span class="k">subroutine</span><span class="w"> </span><span class="n">mul</span><span class="p">(</span><span class="w"> </span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="p">,</span><span class="w"> </span><span class="n">res</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="kt">integer</span><span class="p">,</span><span class="w"> </span><span class="k">intent</span><span class="p">(</span><span class="k">in</span><span class="p">)</span><span class="w"> </span><span class="p">::</span><span class="w"> </span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="kt">integer</span><span class="p">,</span><span class="w"> </span><span class="k">intent</span><span class="p">(</span><span class="k">out</span><span class="p">)</span><span class="w"> </span><span class="p">::</span><span class="w"> </span><span class="n">res</span><span class="w"> </span><span class="n">res</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">subroutine</span><span class="w"> </span><span class="n">mul</span><span class="w"> </span></code></pre> </div> <p>Similar to <code>add</code>, <code>mul</code> takes two input parameters <code>x</code> and <code>y</code>, but this time <code>mul</code> is a subroutine which takes an output parameter <code>res</code> for the storing the result.</p> <p>If we try compiling <code>mul.f90</code> as we did with <code>add.f90</code>,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>gfortran mul.f90 <span class="nt">-o</span> mul.out </code></pre> </div> <p>we'll encounter an error message similar to the following<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>Undefined symbols for architecture arm64: "_main", referenced from: &lt;initial-undefines&gt; ld: symbol(s) not found for architecture arm64 collect2: error: ld returned 1 exit status </code></pre> </div> <p>In order to successfully generate a standalone executable, Fortran code must have a <code>main</code> program providing an entry point for execution. Without this entry point, a Fortran compiler does not where to begin executing code or where to look to identify the procedures and functions necessary to run a program.</p> <p>For <code>mul.f90</code>, we're not wanting Fortran to drive execution, and, instead, we're interested in defining an entry point outside of Fortran which will enable a JavaScript runtime to drive execution. This means that we need to figure out a way to establish a bridge between a JavaScript runtime exposing native APIs and Fortran code containing APIs which we want to use. In order to establish such a bridge, we need to disentangle two compiler phases: compilation and linking.</p> <h2> Linking </h2> <p>At a high level, compilation is the process of translating one programming language into another programming language. Often this means taking expressions written in a higher-level language, such as Fortran, and translating them to a lower-level language, such as machine code, in order to create an executable program that a machine can natively understand. The output of compilation is one or more object files, which typically have <code>.o</code> or <code>.obj</code> filename extensions.</p> <p>Linking is the process of taking one or more object files and combining them into a single executable file. During linking, a "linker" performs several tasks:</p> <ul> <li> <strong>symbol resolution</strong>: resolving references to functions and variables across different object files.</li> <li> <strong>address binding</strong>: assigning final memory addresses to a program's functions and variables.</li> <li> <strong>library inclusion</strong>: including code from static or dynamic libraries as required.</li> <li> <strong>executable creation</strong>: producing the final executable file that can be run on a target system.</li> </ul> <p>When we ran the GFortran command above<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>gfortran mul.f90 <span class="nt">-o</span> mul.out </code></pre> </div> <p>the compiler attempted to perform both compilation and linking. However, if we're trying to combine compiled Fortran code with a separate library (or a runtime such as Node.js), we need to split compilation and linking into separate steps.</p> <p>Accordingly, in order to just generate the object file, we can amend the previous command as follows<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>gfortran <span class="nt">-c</span> mul.f90 </code></pre> </div> <p>where the <code>-c</code> flag instructs the compiler to compile, but not to link. After running this command from the same directory as <code>mul.f90</code>, you should see a <code>mul.o</code> (or <code>mul.obj</code>) file containing the compiled source code.</p> <h3> Linking Fortran files </h3> <p>To demonstrate linking as a separate phase, create a <code>mul_script.f90</code> file containing the following code containing a <code>main</code> program which calls the <code>mul</code> function and prints the result.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight fortran"><code><span class="c1">! file: mul_script.f90</span><span class="w"> </span><span class="c1">!&gt;</span><span class="w"> </span><span class="c1">! Main execution sequence.</span><span class="w"> </span><span class="c1">!&lt;</span><span class="w"> </span><span class="k">program</span><span class="w"> </span><span class="n">main</span><span class="w"> </span><span class="c1">! Local variables:</span><span class="w"> </span><span class="kt">character</span><span class="p">(</span><span class="nb">len</span><span class="o">=</span><span class="mi">999</span><span class="p">)</span><span class="w"> </span><span class="p">::</span><span class="w"> </span><span class="n">str</span><span class="p">,</span><span class="w"> </span><span class="n">tmp</span><span class="w"> </span><span class="c1">! ..</span><span class="w"> </span><span class="c1">! Intrinsic functions:</span><span class="w"> </span><span class="k">intrinsic</span><span class="w"> </span><span class="nb">adjustl</span><span class="p">,</span><span class="w"> </span><span class="nb">trim</span><span class="w"> </span><span class="c1">! ..</span><span class="w"> </span><span class="c1">! Define a variable for storing the product:</span><span class="w"> </span><span class="kt">integer</span><span class="w"> </span><span class="p">::</span><span class="w"> </span><span class="n">res</span><span class="w"> </span><span class="c1">! ..</span><span class="w"> </span><span class="c1">! Call the `mul` subroutine to compute the product:</span><span class="w"> </span><span class="k">call</span><span class="w"> </span><span class="n">mul</span><span class="p">(</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="n">res</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="c1">! ..</span><span class="w"> </span><span class="c1">! Print the results:</span><span class="w"> </span><span class="k">write</span><span class="w"> </span><span class="p">(</span><span class="n">str</span><span class="p">,</span><span class="w"> </span><span class="s1">'(I15)'</span><span class="p">)</span><span class="w"> </span><span class="n">res</span><span class="w"> </span><span class="n">tmp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">adjustl</span><span class="p">(</span><span class="w"> </span><span class="n">str</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">print</span><span class="w"> </span><span class="s1">'(A, A)'</span><span class="p">,</span><span class="w"> </span><span class="s1">'The product of 4 and 5 is '</span><span class="p">,</span><span class="w"> </span><span class="nb">trim</span><span class="p">(</span><span class="w"> </span><span class="n">tmp</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">program</span><span class="w"> </span></code></pre> </div> <p>We can then perform the same compilation step as we did for <code>mul.f90</code>.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>gfortran <span class="nt">-c</span> mul_script.f90 </code></pre> </div> <p>At this point, we should have two object files: <code>mul.o</code> and <code>mul_script.o</code> (or <code>mul.obj</code> and <code>mul_script.obj</code>, respectively). To link them into a single executable, we can run the following command in which we define the path of the output executable and pass in the paths of the object files we wish to link.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>gfortran <span class="nt">-o</span> mul_script.out mul.o mul_script.o </code></pre> </div> <p>Once linked, we can test that everything works by running the generated executable.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>./mul_script.out </code></pre> </div> <p>If all went according to plan, you should see the following text as output<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>The product of 4 and 5 is 20 </code></pre> </div> <p>At this point, we've successfully compiled and linked together separate Fortran source files, and we can now turn our attention to linking compiled Fortran to non-Fortran code.</p> <h3> Linking Fortran and C </h3> <p>A common scenario in numerical computing is exposing numerical computing libraries written in Fortran as C functions. C also happens to be the programming language used by Node.js to expose APIs for building native add-ons (i.e., extensions to the Node.js runtime). Accordingly, if we can figure out how to link Fortran to C, we'll be well on our way to creating a Node.js native add-on capable of calling Fortran routines.</p> <h4> Writing Fortran wrappers </h4> <p>While the <code>mul</code> function defined above can be used in conjunction with other Fortran files, we cannot simply call <code>mul</code> from C as we do in Fortran because Fortran expects arguments to be passed by reference rather than by value. It's also worth mentioning that, because Fortran functions can only return scalar values and not, e.g., pointers to arrays, general best practice is to expose Fortran functions as subroutines, which are the equivalent of C functions returning <code>void</code> and which allow passing pointers for storing output return values.</p> <p>While <code>mul</code> is already a subroutine, if we wanted to expose <code>add</code> to C, we'd first need to wrap <code>add</code> as a subroutine in a manner similar to the following code snippet containing the subroutine wrapper <code>addsub</code> which forwards input arguments to <code>add</code> and assigns the result to an output argument <code>res</code>.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight fortran"><code><span class="c1">!&gt;</span><span class="w"> </span><span class="c1">! Wraps `add` as a subroutine.</span><span class="w"> </span><span class="c1">!</span><span class="w"> </span><span class="c1">! @param {integer} x - first input value</span><span class="w"> </span><span class="c1">! @param {integer} y - second input value</span><span class="w"> </span><span class="c1">! @param {integer} res - output argument for storing the result</span><span class="w"> </span><span class="c1">!&lt;</span><span class="w"> </span><span class="k">subroutine</span><span class="w"> </span><span class="n">addsub</span><span class="p">(</span><span class="w"> </span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="p">,</span><span class="w"> </span><span class="n">res</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">implicit</span><span class="w"> </span><span class="k">none</span><span class="w"> </span><span class="c1">! ..</span><span class="w"> </span><span class="c1">! External functions:</span><span class="w"> </span><span class="k">interface</span><span class="w"> </span><span class="kt">integer</span><span class="w"> </span><span class="k">function</span><span class="w"> </span><span class="n">add</span><span class="p">(</span><span class="w"> </span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="kt">integer</span><span class="w"> </span><span class="p">::</span><span class="w"> </span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">function</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">interface</span><span class="w"> </span><span class="c1">! ..</span><span class="w"> </span><span class="kt">integer</span><span class="p">,</span><span class="w"> </span><span class="k">intent</span><span class="p">(</span><span class="k">in</span><span class="p">)</span><span class="w"> </span><span class="p">::</span><span class="w"> </span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="kt">integer</span><span class="p">,</span><span class="w"> </span><span class="k">intent</span><span class="p">(</span><span class="k">out</span><span class="p">)</span><span class="w"> </span><span class="p">::</span><span class="w"> </span><span class="n">res</span><span class="w"> </span><span class="c1">! ..</span><span class="w"> </span><span class="n">res</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">add</span><span class="p">(</span><span class="w"> </span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">subroutine</span><span class="w"> </span><span class="n">addsub</span><span class="w"> </span></code></pre> </div> <h4> Defining function prototypes in C </h4> <p>With those preliminaries out of the way, to help the C compiler reason about functions defined elsewhere (e.g., in a Fortran library or in other source files), we need to define function prototypes for any functions we plan to use before we use them. For our use case of calling a single Fortran routine, we can create a <code>mul_fortran.h</code> header file containing a single function declaration for the <code>mul</code> subroutine.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight c"><code><span class="c1">// file: mul_fortran.h</span> <span class="cp">#ifndef MUL_FORTRAN_H #define MUL_FORTRAN_H </span> <span class="cp">#ifdef __cplusplus </span><span class="k">extern</span> <span class="s">"C"</span> <span class="p">{</span> <span class="cp">#endif </span> <span class="kt">void</span> <span class="n">mul</span><span class="p">(</span> <span class="k">const</span> <span class="kt">int</span> <span class="o">*</span><span class="n">x</span><span class="p">,</span> <span class="k">const</span> <span class="kt">int</span> <span class="o">*</span><span class="n">y</span><span class="p">,</span> <span class="kt">int</span> <span class="o">*</span><span class="n">res</span> <span class="p">);</span> <span class="cp">#ifdef __cplusplus </span><span class="p">}</span> <span class="cp">#endif </span> <span class="cp">#endif </span></code></pre> </div> <p>One thing to note is that, in the above header file, we prevent <a href="proxy.php?url=https://en.wikipedia.org/wiki/Name_mangling" rel="noopener noreferrer">name mangling</a> by using <code>extern "C"</code>. This is common practice in order to facilitate interoperation of C and C++, and preventing name mangling helps avoid compiler errors if we decide to use <code>mul</code> in C++ in the future.</p> <h4> Calling Fortran routines from C </h4> <p>Next, similar to how we created a Fortran program for calling a Fortran function defined in a separate file, we can create a <code>main.c</code> file containing a <code>main</code> function which calls <code>mul</code> and prints the result.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight c"><code><span class="c1">// file: main.c</span> <span class="cp">#include</span> <span class="cpf">"mul_fortran.h"</span><span class="cp"> #include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp"> </span> <span class="kt">int</span> <span class="nf">main</span><span class="p">(</span> <span class="kt">void</span> <span class="p">)</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span> <span class="kt">int</span> <span class="n">y</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span> <span class="kt">int</span> <span class="n">res</span><span class="p">;</span> <span class="c1">// Compute the product, passing arguments by reference:</span> <span class="n">mul</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">x</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">y</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">res</span> <span class="p">);</span> <span class="n">printf</span><span class="p">(</span> <span class="s">"The product of %d and %d is %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">res</span> <span class="p">);</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <h4> Compiling C and Fortran </h4> <p>To compile our C program, we can run the following command<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>gcc <span class="nt">-I</span> mul_fortran.h <span class="nt">-c</span> main.c </code></pre> </div> <p>where <code>-I mul_fortran.h</code> instructs the compiler to use the function declarations defined in the header file we created above.</p> <p>Before linking <code>main.o</code> and <code>mul.o</code>, we first need to recompile <code>mul.f90</code>, making sure to instruct GFortran to not modify function names by appending underscores during compilation. This ensures that the name used in our C code matches the exported symbol from compiled Fortran. One should be careful, however, as non-mangled names may conflict with existing symbols defined in C.</p> <p>To prevent GFortran from appending underscores to symbol names, we set the <a href="proxy.php?url=https://gcc.gnu.org/onlinedocs/gfortran/Code-Gen-Options.html#index-fno-underscoring" rel="noopener noreferrer"><code>-fno-underscoring</code></a> compiler option when calling GFortran.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>gfortran <span class="nt">-fno-underscoring</span> <span class="nt">-c</span> mul.f90 </code></pre> </div> <p>Now that we've compiled our source files, it's time to generate an executable!<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>gcc <span class="nt">-o</span> main.out main.o mul.o </code></pre> </div> <p>Depending on your operating system, if the previous command errors, you may need to modify the previous command to<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>gcc <span class="nt">-o</span> main.out main.o mul.o <span class="nt">-lgfortran</span> </code></pre> </div> <p>where <code>-lgfortran</code> instructs GCC to link to the standard Fortran libraries. And finally, to test that everything works, we run the executable by entering the following command<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>./main.out </code></pre> </div> <p>If successful, you should see the following text as output<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>The product of 4 and 5 is 20 </code></pre> </div> <p>Phew! If you're new to Fortran and C, congratulations on making it this far!</p> <p>Now that we've successfully managed to link Fortran and C code, we can turn our attention to using Node.js native add-ons to call Fortran routines from JavaScript. </p> <h2> Node-API </h2> <p><a href="proxy.php?url=https://nodejs.org/api/n-api.html" rel="noopener noreferrer">Node-API</a> is an API for building Node.js native add-ons (i.e., extensions to the Node.js JavaScript runtime). There's a long history of add-on evolution and development in Node.js, of which I'll spare you the <a href="proxy.php?url=https://nodesource.com/blog/NAN-to-Node-API-migration-a-short-story/" rel="noopener noreferrer">details</a>. The real benefit of Node-API is in providing a stable Application Binary Interface (ABI), which insulates add-ons from changes in the underlying JavaScript engine (namely, V8) and which allows modules compiled for one version of Node.js to run on later versions of Node.js without recompilation. In short, Node-API provides the glue code, in the form of C APIs, necessary for us to extend Node.js capabilities with C/C++ code written and compiled independently of Node.js itself.</p> <p>In order to access Node-API APIs, we need to do two things:</p> <ol> <li> Include the <code>&lt;node_api.h&gt;</code> header in our C files.</li> <li> Compile C source files using Node-API APIs with <a href="proxy.php?url=https://github.com/nodejs/node-gyp" rel="noopener noreferrer">node-gyp</a>, a build system based on Google's <a href="proxy.php?url=https://gyp.gsrc.io/" rel="noopener noreferrer">GYP</a>, a meta-build system for generating other build systems.</li> </ol> <p>So without further ado...</p> <h3> Creating an add-on file </h3> <p>Let's start by creating an <code>addon.c</code> file which will serve as an entry point for our native add-on. In this file, we'll define two functions—<code>addon</code> and <code>Init</code>—and register a Node-API module which exports a function in a manner similar to how we'd export a function if writing a module in vanilla JavaScript.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight c"><code><span class="c1">// file: addon.c</span> <span class="cp">#include</span> <span class="cpf">&lt;node_api.h&gt;</span><span class="cp"> #include</span> <span class="cpf">&lt;assert.h&gt;</span><span class="cp"> </span> <span class="cm">/** * Receives JavaScript callback invocation data. * * @param env environment under which the function is invoked * @param info callback data * @return Node-API value */</span> <span class="k">static</span> <span class="n">napi_value</span> <span class="nf">addon</span><span class="p">(</span> <span class="n">napi_env</span> <span class="n">env</span><span class="p">,</span> <span class="n">napi_callback_info</span> <span class="n">info</span> <span class="p">)</span> <span class="p">{</span> <span class="c1">// NOTE: we'll add code here later in this post</span> <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span> <span class="p">}</span> <span class="cm">/** * Defines the Node.js module "exports" object for the native add-on. * * @param env environment under which the function is invoked * @param exports exports object * @return Node-API value */</span> <span class="k">static</span> <span class="n">napi_value</span> <span class="nf">Init</span><span class="p">(</span> <span class="n">napi_env</span> <span class="n">env</span><span class="p">,</span> <span class="n">napi_value</span> <span class="n">exports</span> <span class="p">)</span> <span class="p">{</span> <span class="n">napi_value</span> <span class="n">fcn</span><span class="p">;</span> <span class="c1">// Export the add-on function as a "default" export:</span> <span class="n">napi_status</span> <span class="n">status</span> <span class="o">=</span> <span class="n">napi_create_function</span><span class="p">(</span> <span class="n">env</span><span class="p">,</span> <span class="s">"exports"</span><span class="p">,</span> <span class="n">NAPI_AUTO_LENGTH</span><span class="p">,</span> <span class="n">addon</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">fcn</span> <span class="p">);</span> <span class="c1">// Verify that we successfully wrapped the `addon` function as a JavaScript function object:</span> <span class="n">assert</span><span class="p">(</span> <span class="n">status</span> <span class="o">==</span> <span class="n">napi_ok</span> <span class="p">);</span> <span class="c1">// Return the JavaScript function object to allow registering with the JavaScript runtime:</span> <span class="k">return</span> <span class="n">fcn</span><span class="p">;</span> <span class="p">}</span> <span class="cm">/** * Register a Node-API module which exports a function. */</span> <span class="n">NAPI_MODULE</span><span class="p">(</span> <span class="n">NODE_GYP_MODULE_NAME</span><span class="p">,</span> <span class="n">Init</span> <span class="p">)</span> </code></pre> </div> <p>The <code>addon.c</code> file is comprised of three parts:</p> <ol> <li> <code>addon</code>: this function receives JavaScript invocation data. If we assume <code>foo()</code> is a JavaScript function exposed by a native add-on, <code>env</code> is the environment in which the JavaScript code runs and <code>info</code> is an opaque object which can be used to retrieve function arguments and other contextual data when <code>foo</code> is invoked.</li> <li> <code>Init</code>: similar to how <a href="proxy.php?url=https://nodejs.org/api/modules.html#moduleexports" rel="noopener noreferrer"><code>module.exports</code></a> defines the APIs a Node.js module exposes to other Node.js modules, this function defines the "exports" object and initializes exported values. In this context, initialization typically means wrapping C APIs as JavaScript objects so that a JavaScript engine can pass data back and forth between JavaScript and native code.</li> <li> <code>NAPI_MODULE</code>: this is a <a href="proxy.php?url=https://nodejs.org/api/n-api.html#module-registration" rel="noopener noreferrer">macro</a> exposed by Node-API for registering a Node-API module with the Node.js JavaScript runtime.</li> </ol> <p>At this point, we're starting to accumulate a number of moving parts: Fortran source files, GFortran, C source files, GCC, Node-API, and a heretofore mentioned, but not explained, node-gyp.</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--YDoEZzwI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.stdlib.io/content/images/2024/07/build_diagram.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--YDoEZzwI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.stdlib.io/content/images/2024/07/build_diagram.png" alt="Diagram providing an overview of building a Node.js native add-on capable of calling Fortran routines from JavaScript" width="667" height="585"></a></p> <p>As may be observed in the diagram above, a key component which we have yet to cover, but which is necessary to allow building a Node.js native add-on in a manner that is portable across platforms, is the <code>binding.gyp</code> file. It's this file and node-gyp that we'll dive into next.</p> <h2> node-gyp </h2> <p><a href="proxy.php?url=https://github.com/nodejs/node-gyp" rel="noopener noreferrer">node-gyp</a> is a build system based on Google's <a href="proxy.php?url=https://gyp.gsrc.io/" rel="noopener noreferrer">GYP</a>, which, in turn, is a meta-build system for generating other build systems. The key idea behind GYP is the generation of build files, such as Makefiles, Ninja build files, Visual Studio projects, and XCode projects, which are tailored to the platform on which a project is being compiled. Once GYP scaffolds a project in a manner tailored to the host platform, GYP can then perform build steps which replicate as closely as possible the way that one would have set up a native build of the project were one writing the project build system from scratch. node-gyp subsequently extends GYP by providing the configuration and tooling specific to developing Node.js native add-ons.</p> <h3> Configuring how to build an add-on </h3> <p>In order to describe the configuration necessary to build a Node.js native add-on, one needs to provide a <code>binding.gyp</code> file. This file is written in a JSON-like format and is placed at the root of a JavaScript package alongside a package's <code>package.json</code> file. GYP configuration files can be awkward to write, and, unfortunately, GYP has long been abandoned by the Google team responsible for its creation. Adding insult to injury, good documentation for authoring GYP files can be hard to come by, as the GYP documentation is incomplete and finding real-world examples doing exactly what you are wanting to do can be a time-consuming task, especially when authoring <code>binding.gyp</code> files requiring specialized configuration (e.g., as might be needed when compiling CUDA, OpenCL, or Fortran).</p> <p>Nevertheless, persist we shall! Fortunately, writing a minimal <code>binding.gyp</code> file capable of supporting Fortran compilation is within reach. Start by creating a <code>binding.gyp</code> file specifying various configuration parameters, including build targets, source files, compiler flags, and rules for how to process files having a specific file type.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight python"><code><span class="c1"># file: binding.gyp </span> <span class="c1"># A `.gyp` file for building a Node.js native add-on. # # [1]: https://gyp.gsrc.io/docs/InputFormatReference.md # [2]: https://gyp.gsrc.io/docs/UserDocumentation.md </span><span class="p">{</span> <span class="c1"># Define variables to be used throughout the configuration for all targets: </span> <span class="sh">'</span><span class="s">variables</span><span class="sh">'</span><span class="p">:</span> <span class="p">{</span> <span class="c1"># Set variables based on the host OS: </span> <span class="sh">'</span><span class="s">conditions</span><span class="sh">'</span><span class="p">:</span> <span class="p">[</span> <span class="p">[</span> <span class="sh">'</span><span class="s">OS==</span><span class="sh">"</span><span class="s">win</span><span class="sh">"'</span><span class="p">,</span> <span class="p">{</span> <span class="c1"># Define the object file suffix on Windows: </span> <span class="sh">'</span><span class="s">obj</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">obj</span><span class="sh">'</span><span class="p">,</span> <span class="p">},</span> <span class="p">{</span> <span class="c1"># Define the object file suffix for other operating systems (e.g., Linux and MacOS): </span> <span class="sh">'</span><span class="s">obj</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">o</span><span class="sh">'</span><span class="p">,</span> <span class="p">}</span> <span class="p">],</span> <span class="p">],</span> <span class="p">},</span> <span class="c1"># Define compilation targets: </span> <span class="sh">'</span><span class="s">targets</span><span class="sh">'</span><span class="p">:</span> <span class="p">[</span> <span class="c1"># Define a target to generate an add-on: </span> <span class="p">{</span> <span class="c1"># The target name should match the add-on export name (see addon.c above): </span> <span class="sh">'</span><span class="s">target_name</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">addon</span><span class="sh">'</span><span class="p">,</span> <span class="c1"># List of source files: </span> <span class="sh">'</span><span class="s">sources</span><span class="sh">'</span><span class="p">:</span> <span class="p">[</span> <span class="c1"># Relative paths should be relative to this configuration file... </span> <span class="sh">'</span><span class="s">./addon.c</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">./mul.f90</span><span class="sh">'</span><span class="p">,</span> <span class="p">],</span> <span class="c1"># List directories which contain relevant headers to include during compilation: </span> <span class="sh">'</span><span class="s">include_dirs</span><span class="sh">'</span><span class="p">:</span> <span class="p">[</span> <span class="c1"># Relative paths should be relative to this configuration file... </span> <span class="sh">'</span><span class="s">./</span><span class="sh">'</span><span class="p">,</span> <span class="p">],</span> <span class="c1"># Define settings which should be applied when a target's object files are used as linker input: </span> <span class="sh">'</span><span class="s">link_settings</span><span class="sh">'</span><span class="p">:</span> <span class="p">{</span> <span class="c1"># Define linker flags for libraries against which to link (e.g., '-lm', '-lblas', etc): </span> <span class="sh">'</span><span class="s">libraries</span><span class="sh">'</span><span class="p">:</span> <span class="p">[],</span> <span class="c1"># Define directories in which to find libraries to link to (e.g., '/usr/lib'): </span> <span class="sh">'</span><span class="s">library_dirs</span><span class="sh">'</span><span class="p">:</span> <span class="p">[]</span> <span class="p">},</span> <span class="c1"># Define custom build actions for particular source files: </span> <span class="sh">'</span><span class="s">rules</span><span class="sh">'</span><span class="p">:</span> <span class="p">[</span> <span class="p">{</span> <span class="c1"># Define a rule name: </span> <span class="sh">'</span><span class="s">rule_name</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">compile_fortran</span><span class="sh">'</span><span class="p">,</span> <span class="c1"># Define the filename extension for which this rule should apply: </span> <span class="sh">'</span><span class="s">extension</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">f90</span><span class="sh">'</span><span class="p">,</span> <span class="c1"># Set a flag specifying whether to process generated output as sources for subsequent steps: </span> <span class="sh">'</span><span class="s">process_outputs_as_sources</span><span class="sh">'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="c1"># Define the pathnames to be used as inputs when performing processing: </span> <span class="sh">'</span><span class="s">inputs</span><span class="sh">'</span><span class="p">:</span> <span class="p">[</span> <span class="c1"># Full path of the current input: </span> <span class="sh">'</span><span class="s">&lt;(RULE_INPUT_PATH)</span><span class="sh">'</span><span class="p">,</span> <span class="p">],</span> <span class="c1"># Define the outputs produced during processing: </span> <span class="sh">'</span><span class="s">outputs</span><span class="sh">'</span><span class="p">:</span> <span class="p">[</span> <span class="c1"># Store an output object file in a directory for placing intermediate results (only accessible within a single target): </span> <span class="sh">'</span><span class="s">&lt;(INTERMEDIATE_DIR)/&lt;(RULE_INPUT_ROOT).&lt;(obj)</span><span class="sh">'</span><span class="p">,</span> <span class="p">],</span> <span class="c1"># Define the command-line invocation: </span> <span class="sh">'</span><span class="s">action</span><span class="sh">'</span><span class="p">:</span> <span class="p">[</span> <span class="sh">'</span><span class="s">gfortran</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">-fno-underscoring</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">-c</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">&lt;@(_inputs)</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">-o</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">&lt;@(_outputs)</span><span class="sh">'</span><span class="p">,</span> <span class="p">],</span> <span class="p">},</span> <span class="p">],</span> <span class="p">},</span> <span class="p">],</span> <span class="p">}</span> </code></pre> </div> <p>A few comments:</p> <ol> <li> GYP configuration files support variables, conditionals, and expressions. In the configuration file above, <code>&lt;(RULE_INPUT_PATH)</code>, <code>&lt;(INTERMEDIATE_DIR)</code>, and <code>&lt;(RULE_INPUT_ROOT)</code> are predefined variables provided by the GYP generator module. Variables such as <code>&lt;@(_inputs)</code> and <code>&lt;@(_outputs)</code> represent variable expansions and correspond to variables which should be expanded in list contexts.</li> <li> While GYP attempts to automate and abstract away the generation of build files tailored to the operating system on which to compile, this doesn't absolve us from needing to consider platform variability. For example, the configuration file above includes a conditional for resolving an appropriate object file filename extension based on the target operating system.</li> <li> Configuration files can quickly become complex depending on operating system variability, including the availability of specialized compilers, such as GFortran, and the need for bespoke rules for varying input file types.</li> </ol> <h3> Building an add-on </h3> <p>Now that we have a GYP configuration file, it's time to install <a href="proxy.php?url=https://github.com/nodejs/node-gyp" rel="noopener noreferrer">node-gyp</a>. In your terminal, run<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>npm <span class="nb">install</span> <span class="nt">--no-save</span> node-gyp </code></pre> </div> <p>The node-gyp executable will subsequently be available in the <code>./node_modules/.bin</code> directory. To generate the appropriate project build files for the current platform, run the following command<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>./node_modules/.bin/node-gyp configure </code></pre> </div> <p>This will generate a <code>./build</code> directory containing platform-specific build files. To build the native add-on, we can run<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>./node_modules/.bin/node-gyp build </code></pre> </div> <p>which will generate an <code>addon.node</code> file in a <code>./build/Release</code> sub-folder. To remove generated files, run<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>./node_modules/.bin/node-gyp clean </code></pre> </div> <p>As we continue to iterate on our <code>addon.c</code> file, we'll want to perform the <code>clean-configure-build</code> sequence each time we make changes. Accordingly, we can consolidate the above steps into a single command<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>./node_modules/.bin/node-gyp clean <span class="o">&amp;&amp;</span> <span class="se">\</span> ./node_modules/.bin/node-gyp configure <span class="o">&amp;&amp;</span> <span class="se">\</span> ./node_modules/.bin/node-gyp build </code></pre> </div> <h2> Calling a Fortran routine from JavaScript </h2> <p>At this point, we've got almost all of the core building blocks for calling a Fortran routine from JavaScript. We're only missing two things:</p> <ol> <li> Logic in <code>addon.c</code> which calls the Fortran routine.</li> <li> A JavaScript file which invokes the function exposed by our native add-on.</li> </ol> <h3> Updating the add-on file </h3> <p>To start, let's revisit our <code>addon.c</code> file. In this file, we need to make four changes:</p> <ol> <li> Retrieve provided arguments.</li> <li> Convert from JavaScript objects to native C types.</li> <li> Add logic to call our Fortran routine <code>mul</code>.</li> <li> Return a result as a JavaScript object.</li> </ol> <p>Luckily, we already have experience with (3) when we wrote <code>main.c</code> and linked against our compiled Fortran routine. As in <code>main.c</code>, we want to include the <code>mul_fortran.h</code> header, which we can do by making the following change in <code>addon.c</code><br> </p> <div class="highlight js-code-highlight"> <pre class="highlight diff"><code>// file: addon.c <span class="err"> </span><span class="gi">+ #include "mul_fortran.h" </span>#include &lt;node_api.h&gt; #include &lt;assert.h&gt; </code></pre> </div> <p>Next, we'll want to modify the <code>addon</code> function in <code>addon.c</code> to include logic for calling the <code>mul</code> Fortran routine. In the snippet below, we copy the invocation logic used in <code>main.c</code> into the implementation of the <code>addon</code> function.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight c"><code><span class="cm">/** * Receives JavaScript callback invocation data. * * @param env environment under which the function is invoked * @param info callback data * @return Node-API value */</span> <span class="k">static</span> <span class="n">napi_value</span> <span class="nf">addon</span><span class="p">(</span> <span class="n">napi_env</span> <span class="n">env</span><span class="p">,</span> <span class="n">napi_callback_info</span> <span class="n">info</span> <span class="p">)</span> <span class="p">{</span> <span class="c1">// ...</span> <span class="c1">// Call the Fortran routine:</span> <span class="kt">int</span> <span class="n">res</span><span class="p">;</span> <span class="n">mul</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">x</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">y</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">res</span> <span class="p">);</span> <span class="c1">// ...</span> <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>Now on to argument munging. Fortunately, Node-API provides several APIs for converting from JavaScript objects to native C data types. In particular, we're interested in converting JavaScript numbers to C integers, which is demonstrated in the following code snippet which defines the number of expected input arguments, retrieves those arguments from provided callback info using <a href="proxy.php?url=https://nodejs.org/api/n-api.html#napi_get_cb_info" rel="noopener noreferrer"><code>napi_get_cb_info</code></a>, and converts JavaScript objects to native C data types using <a href="proxy.php?url=https://nodejs.org/api/n-api.html#napi_get_value_int32" rel="noopener noreferrer"><code>napi_get_value_int32</code></a>.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight c"><code><span class="cm">/** * Receives JavaScript callback invocation data. * * @param env environment under which the function is invoked * @param info callback data * @return Node-API value */</span> <span class="k">static</span> <span class="n">napi_value</span> <span class="nf">addon</span><span class="p">(</span> <span class="n">napi_env</span> <span class="n">env</span><span class="p">,</span> <span class="n">napi_callback_info</span> <span class="n">info</span> <span class="p">)</span> <span class="p">{</span> <span class="n">napi_status</span> <span class="n">status</span><span class="p">;</span> <span class="c1">// Define the expected number of input arguments:</span> <span class="kt">size_t</span> <span class="n">argc</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="c1">// Retrieve the input arguments from the callback info:</span> <span class="n">napi_value</span> <span class="n">argv</span><span class="p">[</span> <span class="mi">2</span> <span class="p">];</span> <span class="n">status</span> <span class="o">=</span> <span class="n">napi_get_cb_info</span><span class="p">(</span> <span class="n">env</span><span class="p">,</span> <span class="n">info</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span> <span class="p">);</span> <span class="n">assert</span><span class="p">(</span> <span class="n">status</span> <span class="o">==</span> <span class="n">napi_ok</span> <span class="p">);</span> <span class="c1">// Convert each argument to a signed 32-bit integer:</span> <span class="kt">int</span> <span class="n">x</span><span class="p">;</span> <span class="n">status</span> <span class="o">=</span> <span class="n">napi_get_value_int32</span><span class="p">(</span> <span class="n">env</span><span class="p">,</span> <span class="n">argv</span><span class="p">[</span> <span class="mi">0</span> <span class="p">],</span> <span class="o">&amp;</span><span class="n">x</span> <span class="p">);</span> <span class="n">assert</span><span class="p">(</span> <span class="n">status</span> <span class="o">==</span> <span class="n">napi_ok</span> <span class="p">);</span> <span class="kt">int</span> <span class="n">y</span><span class="p">;</span> <span class="n">status</span> <span class="o">=</span> <span class="n">napi_get_value_int32</span><span class="p">(</span> <span class="n">env</span><span class="p">,</span> <span class="n">argv</span><span class="p">[</span> <span class="mi">1</span> <span class="p">],</span> <span class="o">&amp;</span><span class="n">y</span> <span class="p">);</span> <span class="n">assert</span><span class="p">(</span> <span class="n">status</span> <span class="o">==</span> <span class="n">napi_ok</span> <span class="p">);</span> <span class="c1">// Call the Fortran routine:</span> <span class="kt">int</span> <span class="n">res</span><span class="p">;</span> <span class="n">mul</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">x</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">y</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">res</span> <span class="p">);</span> <span class="c1">// ...</span> <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>And finally, we need to convert the integer result to a JavaScript object for use within JavaScript, which is demonstrated in the following code snippet which adds logic for converting a C signed 32-bit integer to an opaque object representing a JavaScript number using <a href="proxy.php?url=https://nodejs.org/api/n-api.html#napi_create_int32" rel="noopener noreferrer"><code>napi_create_int32</code></a>.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight c"><code><span class="cm">/** * Receives JavaScript callback invocation data. * * @param env environment under which the function is invoked * @param info callback data * @return Node-API value */</span> <span class="k">static</span> <span class="n">napi_value</span> <span class="nf">addon</span><span class="p">(</span> <span class="n">napi_env</span> <span class="n">env</span><span class="p">,</span> <span class="n">napi_callback_info</span> <span class="n">info</span> <span class="p">)</span> <span class="p">{</span> <span class="n">napi_status</span> <span class="n">status</span><span class="p">;</span> <span class="c1">// Define the expected number of input arguments:</span> <span class="kt">size_t</span> <span class="n">argc</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="c1">// Retrieve the input arguments from the callback info:</span> <span class="n">napi_value</span> <span class="n">argv</span><span class="p">[</span> <span class="mi">2</span> <span class="p">];</span> <span class="n">status</span> <span class="o">=</span> <span class="n">napi_get_cb_info</span><span class="p">(</span> <span class="n">env</span><span class="p">,</span> <span class="n">info</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span> <span class="p">);</span> <span class="n">assert</span><span class="p">(</span> <span class="n">status</span> <span class="o">==</span> <span class="n">napi_ok</span> <span class="p">);</span> <span class="c1">// Convert each argument to a signed 32-bit integer:</span> <span class="kt">int</span> <span class="n">x</span><span class="p">;</span> <span class="n">status</span> <span class="o">=</span> <span class="n">napi_get_value_int32</span><span class="p">(</span> <span class="n">env</span><span class="p">,</span> <span class="n">argv</span><span class="p">[</span> <span class="mi">0</span> <span class="p">],</span> <span class="o">&amp;</span><span class="n">x</span> <span class="p">);</span> <span class="n">assert</span><span class="p">(</span> <span class="n">status</span> <span class="o">==</span> <span class="n">napi_ok</span> <span class="p">);</span> <span class="kt">int</span> <span class="n">y</span><span class="p">;</span> <span class="n">status</span> <span class="o">=</span> <span class="n">napi_get_value_int32</span><span class="p">(</span> <span class="n">env</span><span class="p">,</span> <span class="n">argv</span><span class="p">[</span> <span class="mi">1</span> <span class="p">],</span> <span class="o">&amp;</span><span class="n">y</span> <span class="p">);</span> <span class="n">assert</span><span class="p">(</span> <span class="n">status</span> <span class="o">==</span> <span class="n">napi_ok</span> <span class="p">);</span> <span class="c1">// Call the Fortran routine:</span> <span class="kt">int</span> <span class="n">res</span><span class="p">;</span> <span class="n">mul</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">x</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">y</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">res</span> <span class="p">);</span> <span class="c1">// Convert the result to a JavaScript object:</span> <span class="n">napi_value</span> <span class="n">out</span><span class="p">;</span> <span class="n">status</span> <span class="o">=</span> <span class="n">napi_create_int32</span><span class="p">(</span> <span class="n">env</span><span class="p">,</span> <span class="n">res</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">out</span> <span class="p">);</span> <span class="n">assert</span><span class="p">(</span> <span class="n">status</span> <span class="o">==</span> <span class="n">napi_ok</span> <span class="p">);</span> <span class="k">return</span> <span class="n">out</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>Putting it all together, we have the following <code>addon.c</code> file which defines the entirety of our native add-on bindings.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight c"><code> <span class="c1">// file: addon.c</span> <span class="cp">#include</span> <span class="cpf">"mul_fortran.h"</span><span class="cp"> #include</span> <span class="cpf">&lt;node_api.h&gt;</span><span class="cp"> #include</span> <span class="cpf">&lt;assert.h&gt;</span><span class="cp"> </span> <span class="cm">/** * Receives JavaScript callback invocation data. * * @param env environment under which the function is invoked * @param info callback data * @return Node-API value */</span> <span class="k">static</span> <span class="n">napi_value</span> <span class="nf">addon</span><span class="p">(</span> <span class="n">napi_env</span> <span class="n">env</span><span class="p">,</span> <span class="n">napi_callback_info</span> <span class="n">info</span> <span class="p">)</span> <span class="p">{</span> <span class="n">napi_status</span> <span class="n">status</span><span class="p">;</span> <span class="c1">// Define the expected number of input arguments:</span> <span class="kt">size_t</span> <span class="n">argc</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="c1">// Retrieve the input arguments from the callback info:</span> <span class="n">napi_value</span> <span class="n">argv</span><span class="p">[</span> <span class="mi">2</span> <span class="p">];</span> <span class="n">status</span> <span class="o">=</span> <span class="n">napi_get_cb_info</span><span class="p">(</span> <span class="n">env</span><span class="p">,</span> <span class="n">info</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span> <span class="p">);</span> <span class="n">assert</span><span class="p">(</span> <span class="n">status</span> <span class="o">==</span> <span class="n">napi_ok</span> <span class="p">);</span> <span class="c1">// Convert each argument to a signed 32-bit integer:</span> <span class="kt">int</span> <span class="n">x</span><span class="p">;</span> <span class="n">status</span> <span class="o">=</span> <span class="n">napi_get_value_int32</span><span class="p">(</span> <span class="n">env</span><span class="p">,</span> <span class="n">argv</span><span class="p">[</span> <span class="mi">0</span> <span class="p">],</span> <span class="o">&amp;</span><span class="n">x</span> <span class="p">);</span> <span class="n">assert</span><span class="p">(</span> <span class="n">status</span> <span class="o">==</span> <span class="n">napi_ok</span> <span class="p">);</span> <span class="kt">int</span> <span class="n">y</span><span class="p">;</span> <span class="n">status</span> <span class="o">=</span> <span class="n">napi_get_value_int32</span><span class="p">(</span> <span class="n">env</span><span class="p">,</span> <span class="n">argv</span><span class="p">[</span> <span class="mi">1</span> <span class="p">],</span> <span class="o">&amp;</span><span class="n">y</span> <span class="p">);</span> <span class="n">assert</span><span class="p">(</span> <span class="n">status</span> <span class="o">==</span> <span class="n">napi_ok</span> <span class="p">);</span> <span class="c1">// Call the Fortran routine:</span> <span class="kt">int</span> <span class="n">res</span><span class="p">;</span> <span class="n">mul</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">x</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">y</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">res</span> <span class="p">);</span> <span class="c1">// Convert the result to a JavaScript object:</span> <span class="n">napi_value</span> <span class="n">out</span><span class="p">;</span> <span class="n">status</span> <span class="o">=</span> <span class="n">napi_create_int32</span><span class="p">(</span> <span class="n">env</span><span class="p">,</span> <span class="n">res</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">out</span> <span class="p">);</span> <span class="n">assert</span><span class="p">(</span> <span class="n">status</span> <span class="o">==</span> <span class="n">napi_ok</span> <span class="p">);</span> <span class="k">return</span> <span class="n">out</span><span class="p">;</span> <span class="p">}</span> <span class="cm">/** * Defines the Node.js module "exports" object for the native add-on. * * @param env environment under which the function is invoked * @param exports exports object * @return Node-API value */</span> <span class="k">static</span> <span class="n">napi_value</span> <span class="nf">Init</span><span class="p">(</span> <span class="n">napi_env</span> <span class="n">env</span><span class="p">,</span> <span class="n">napi_value</span> <span class="n">exports</span> <span class="p">)</span> <span class="p">{</span> <span class="n">napi_value</span> <span class="n">fcn</span><span class="p">;</span> <span class="c1">// Export the add-on function as a "default" export:</span> <span class="n">napi_status</span> <span class="n">status</span> <span class="o">=</span> <span class="n">napi_create_function</span><span class="p">(</span> <span class="n">env</span><span class="p">,</span> <span class="s">"exports"</span><span class="p">,</span> <span class="n">NAPI_AUTO_LENGTH</span><span class="p">,</span> <span class="n">addon</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">fcn</span> <span class="p">);</span> <span class="c1">// Verify that we successfully wrapped the `addon` function as a JavaScript function object:</span> <span class="n">assert</span><span class="p">(</span> <span class="n">status</span> <span class="o">==</span> <span class="n">napi_ok</span> <span class="p">);</span> <span class="c1">// Return the JavaScript function object to allow registering with the JavaScript runtime:</span> <span class="k">return</span> <span class="n">fcn</span><span class="p">;</span> <span class="p">}</span> <span class="cm">/** * Register a Node-API module which exports a function. */</span> <span class="n">NAPI_MODULE</span><span class="p">(</span> <span class="n">NODE_GYP_MODULE_NAME</span><span class="p">,</span> <span class="n">Init</span> <span class="p">)</span> </code></pre> </div> <p>To confirm that our Node.js add-on still compiles, we can re-run our build sequence defined above.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>./node_modules/.bin/node-gyp clean <span class="o">&amp;&amp;</span> <span class="se">\</span> ./node_modules/.bin/node-gyp configure <span class="o">&amp;&amp;</span> <span class="se">\</span> ./node_modules/.bin/node-gyp build </code></pre> </div> <h3> Creating a JavaScript file importing the native add-on </h3> <p>We're here! The moment that we've been waiting for! Time to create a JavaScript file which loads our Node.js native add-on and calls its public API. 🥁</p> <p>Thankfully, loading a native add-on is just like loading any other JavaScript module. To see this in action, let's create a <code>mul.js</code> file which imports the native add-on module, calls the function exposed by the add-on, and prints the result.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="c1">// file: mul.js</span> <span class="c1">// Import the native add-on module:</span> <span class="kd">const</span> <span class="nx">addon</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span> <span class="dl">'</span><span class="s1">./build/Release/addon.node</span><span class="dl">'</span> <span class="p">);</span> <span class="c1">// Compute the product of two integers:</span> <span class="kd">const</span> <span class="nx">res</span> <span class="o">=</span> <span class="nf">addon</span><span class="p">(</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">10</span> <span class="p">);</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span> <span class="dl">'</span><span class="s1">The product of %d and %d is %d</span><span class="dl">'</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="nx">res</span> <span class="p">);</span> </code></pre> </div> <p>To test whether everything works as expected, we can run the script by passing the script's file path to the Node.js executable.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>node ./mul.js </code></pre> </div> <p>If all went according to plan, you should see the following text as output<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>The product of 5 and 10 is 50 </code></pre> </div> <p>That's it! We did it. 😅</p> <p>Barring any platform quirks or dreaded compiler errors, we successfully called a Fortran routine from JavaScript. 🙌</p> <h2> Simplifying add-on authoring with stdlib </h2> <p>Depending on API complexity, authoring Node.js native add-ons can be verbose and error prone. This verbosity largely stems from the need for argument validation logic and status checks. For example, when handling typed arrays, one needs to perform multiple steps, such as verifying that an input argument is a typed array, verifying that an input argument is a typed array of the correct type, resolving the length of a typed array, converting a JavaScript object representing a typed array to a C pointer pointing to the start of the underlying typed array memory, and, for applications involving strided arrays, ensuring that typed array properties are consistent with other input arguments, such as strides and offsets.</p> <p>While some validation logic can be performed in JavaScript or omitted entirely, a general best practice is to include such logic in order to ensure data integrity when calling APIs outside of Node-API APIs and to avoid hard-to-track down bugs leading to segmentation faults and buffer overflows. And furthermore, best practice requires that, after each invocation of a Node-API function, one must check <code>napi_status</code> return values to ensure that the JavaScript engine was able to successfully perform the requested operation. As a consequence, lines of code add up, and you find yourself writing the same logic over and over.</p> <h3> Macros for module registration and data type conversion </h3> <p>To simplify add-on authoring, <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> provides several utilities, both functional APIs and macros, which abstract away common boilerplate. For example, we can refactor the <code>addon.c</code> file defined above to use stdlib's <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/napi" rel="noopener noreferrer"><code>napi</code></a> macros for retrieving input arguments, handling conversion to and from native C data types, and initializing and registering an exported function with Node.js.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight c"><code><span class="c1">// file: addon2.c</span> <span class="cp">#include</span> <span class="cpf">"mul_fortran.h"</span><span class="cp"> #include</span> <span class="cpf">"stdlib/napi/create_int32.h"</span><span class="cp"> #include</span> <span class="cpf">"stdlib/napi/argv_int32.h"</span><span class="cp"> #include</span> <span class="cpf">"stdlib/napi/argv.h"</span><span class="cp"> #include</span> <span class="cpf">"stdlib/napi/export.h"</span><span class="cp"> #include</span> <span class="cpf">&lt;node_api.h&gt;</span><span class="cp"> </span> <span class="k">static</span> <span class="n">napi_value</span> <span class="nf">addon</span><span class="p">(</span> <span class="n">napi_env</span> <span class="n">env</span><span class="p">,</span> <span class="n">napi_callback_info</span> <span class="n">info</span> <span class="p">)</span> <span class="p">{</span> <span class="n">STDLIB_NAPI_ARGV</span><span class="p">(</span> <span class="n">env</span><span class="p">,</span> <span class="n">info</span><span class="p">,</span> <span class="n">argv</span><span class="p">,</span> <span class="n">argc</span><span class="p">,</span> <span class="mi">2</span> <span class="p">);</span> <span class="c1">// retrieve function arguments</span> <span class="n">STDLIB_NAPI_ARGV_INT32</span><span class="p">(</span> <span class="n">env</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">argv</span><span class="p">,</span> <span class="mi">0</span> <span class="p">);</span> <span class="c1">// convert to C data type</span> <span class="n">STDLIB_NAPI_ARGV_INT32</span><span class="p">(</span> <span class="n">env</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">argv</span><span class="p">,</span> <span class="mi">1</span> <span class="p">);</span> <span class="c1">// convert to C data type</span> <span class="kt">int</span> <span class="n">res</span><span class="p">;</span> <span class="n">mul</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">x</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">y</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">res</span> <span class="p">);</span> <span class="n">STDLIB_NAPI_CREATE_INT32</span><span class="p">(</span> <span class="n">env</span><span class="p">,</span> <span class="n">res</span><span class="p">,</span> <span class="n">out</span> <span class="p">);</span> <span class="c1">// convert to JavaScript object</span> <span class="k">return</span> <span class="n">out</span><span class="p">;</span> <span class="p">}</span> <span class="n">STDLIB_NAPI_MODULE_EXPORT_FCN</span><span class="p">(</span> <span class="n">addon</span> <span class="p">)</span> </code></pre> </div> <h3> Specialized macros for common function signatures </h3> <p>The use case explored in this post—namely, calling a C/Fortran function which operates on and returns scalar values—is something that we do quite often in stdlib, especially for testing native C APIs and sharing test logic across JavaScript and C implementations. Accordingly, stdlib provides several more <a href="proxy.php?url=https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/math/base/napi" rel="noopener noreferrer">macro abstractions</a> which abstract away all argument retrieval, argument validation, and module registration logic for certain input/output data type combinations.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight c"><code><span class="c1">// file: addon3.c</span> <span class="cp">#include</span> <span class="cpf">"mul_fortran.h"</span><span class="cp"> #include</span> <span class="cpf">"stdlib/math/base/napi/binary.h"</span><span class="cp"> </span> <span class="k">static</span> <span class="kt">int</span> <span class="nf">multiply</span><span class="p">(</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="p">)</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">res</span><span class="p">;</span> <span class="n">mul</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">x</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">y</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">res</span> <span class="p">);</span> <span class="k">return</span> <span class="n">res</span><span class="p">;</span> <span class="p">}</span> <span class="n">STDLIB_MATH_BASE_NAPI_MODULE_II_I</span><span class="p">(</span> <span class="n">multiply</span> <span class="p">)</span> </code></pre> </div> <p>Two comments regarding the code above:</p> <ol> <li> <code>STDLIB_MATH_BASE_NAPI_MODULE_II_I</code> is a macro for registering a Node-API module for an exported function accepting two signed 32-bit integer input arguments and returning a signed 32-bit integer output value. This signature is encoded in the macro name as <code>II_I</code>.</li> <li> We need to wrap the Fortran routine in a C function, as the module registration macro assumes that a registered <code>II_I</code> function expects arguments to be passed by value, not by reference, and returns a scalar value.</li> </ol> <h3> Learning from real-world examples in stdlib </h3> <p>For more details on how we author Node-API native add-ons and leverage macros and various utilities for simplifying the add-on creation process, the best place to start is by browsing stdlib <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">source code</a>. For the examples explored in this post, we've brushed aside some of the complexity in ensuring cross-platform configuration portability (looking at you Windows!) and in specifying compiler options for optimizing compiled code. For those interested in learning more, you'll find many more examples throughout the codebase, and, if you have questions, don't be afraid to stop by and say hi! 👋</p> <h2> Conclusion </h2> <p>In this post, we explored several aspects when authoring Node.js native add-ons, with a particular eye toward being able to call Fortran routines from JavaScript. This effort involved compilation and linking, writing C interfaces, module registration, and build configuration. Along the way, we relied on a variety of tools for generating build artifacts, including Fortran and C compilers, Node-API, and node-gyp. We touched on best practices and potential pitfalls, and we observed how <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> can make authoring Node.js native add-ons much easier.</p> <p>All in all, it was a lot, with several moving parts and complex toolchains. But our exploration was well worth the effort. By leveraging Fortran's high-performance capabilities within Node.js, you can significantly enhance and accelerate your numerical and scientific computing tasks. With Node.js native add-ons, you can bridge the gap between modern web technologies and established scientific computing practices, providing a powerful toolset for you and others and opening the door to new and more powerful Node.js applications.</p> <p>In future posts, we'll explore more complex use cases, including the ability to leverage hardware-optimized routines for linear algebra and machine learning. There's still a lot to learn and more ground to cover. We hope that you'll continue to follow along as we share our insights and that you'll join us in our mission to realize a future where JavaScript and the web are preferred environments for numerical and scientific computation. 🚀</p> <p><a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">stdlib</a> is an open source software project dedicated to providing a comprehensive suite of robust, high-performance libraries to accelerate your project's development and give you peace of mind knowing that you're depending on expertly crafted, high-quality software.</p> <p>If you've enjoyed this post, give us a star 🌟 on <a href="proxy.php?url=https://github.com/stdlib-js/stdlib" rel="noopener noreferrer">GitHub</a> and consider <a href="proxy.php?url=https://opencollective.com/stdlib" rel="noopener noreferrer">financially supporting</a> the project. Your contributions and continued support help ensure the project's long-term success and are greatly appreciated!</p> <p>If you'd like to view the code covered in this post on GitHub, please visit the source code <a href="proxy.php?url=https://github.com/stdlib-js/blog-calling-fortran-from-nodejs-1/tree/main" rel="noopener noreferrer">repository</a>.</p> javascript node tutorial programming