APLearn: The Winning APL Forge 2025 Project

By: Borna Ahmadzadeh

Borna is the 2025 winner of the APL Forge, our annual competition designed to promote the use and development of APL by challenging participants to create innovative open-source libraries and commercial applications. Winners of the APL Forge are invited to present their winning work at the next Dyalog user meeting. As this will not be held until October 2026, he has written a blog post about his work as a preview of what to expect from his Dyalog ’26 presentation.


As a machine learning (ML) developer, I regularly utilise the popular Python package scikit-learn to handle many aspects of ML workflows, including data pre-processing, model training and evaluation, and more. Its simple, uniform interface makes it straightforward to design ML pipelines or switch between the myriad algorithms that it supplies without significant changes to the code. This means that scikit-learn has witnessed extensive adoption across many domains and industries.

Having to deal with large arrays and costly operations, scikit-learn, though written in Python, is heavily dependent on NumPy, an APL-inspired numerical computing package. NumPy itself delegates expensive routines to C and potentially Fortran to maximise performance. Portions of scikit-learn are also written in Cython, a statically compiled, C-like superset of Python, to further improve efficiency. All of this means that a complete understanding of scikit-learn requires, in addition to a knowledge of Python, familiarity with Cython, C, and even Fortran. Although you can develop professional scikit-learn applications without having such a profound knowledge, modifying an algorithm or implementing one from scratch to experiment with research ideas can be very challenging without expertise in those languages. From a didactic perspective this situation leaves much to be desired, as learners will need to dedicate as much effort to the software-specific, low-level details of their code as to the actual algorithm. For instance, matrix multiplication, which can be described in a handful of steps – this constitutes its algorithmic intent – takes hundreds of lines to efficiently implement in C… then there is the issue of portability!

This forces ML developers or students, the majority of whom don’t have the technical aptitude to write well-tuned low-level code, to compromise between efficiency versus tweakability, flexibility, readability, and so on. My winning APL Forge submission, APLearn, is a case study to investigate whether APL can reconcile these seemingly opposed objectives.

Why APL?

To many outsiders, APL is an arcane write-once-read-never language that is exclusively suited to manipulating tabular (typically financial) data; a glyph-heavy alternative to Microsoft Excel. There are many blog posts and real-world projects that dispel this mistaken belief, but I’d like to enumerate a few of APL’s advantages that are of particular relevance to APLearn:

  • Arrays: ML data is usually best represented as multi-dimensional arrays, which are manipulated in various ways to optimise model parameters, make predictions, and so on. Array programming is a natural fit for this.
  • Performance: Data parallelism is at the heart of APL, that is, operations are carried out across entire arrays at once. This can be exploited to run code very efficiently on single instruction, multiple data hardware, especially GPUs (for example, Co-dfns).
  • Algorithms, not software: Languages such as C have a tendency to pollute algorithmic intent with software “noise”, that is, they often get stuck on how to communicate with the computer, neglecting the algorithm itself. Careful memory management, complicated pointer usage, cache-friendly practices… they shift the focus away from what the code is doing (the algorithm) to how it’s doing it (the software). APL doesn’t suffer from this problem because it resembles mathematical notation and, therefore, the implementation is tied to the description, akin to declarative programming.

This led me to ask: Can we efficiently and elegantly develop end-to-end ML workflows in APL to simultaneously facilitate practical applications, research exploration, and pedagogy?

Goals

APLearn is my attempt at recreating a small sub-set of scikit-learn in APL while being mindful of the distinct style called for by array programming. Key features include training and inference methods for common models, pre-processing modules for normalising or encoding data, and miscellaneous utilities like evaluation metrics. The core design principles of APLearn are:

  • Uniformity: Models and utilities follow a uniform interface for training, prediction, or data transformation, thereby reducing the learning curve for new users.
  • Transparency: The full algorithm behind a model is available in a single file with minimal software details.
  • Composability: Components can be chained together, reading like a sentence with verbs as ML methods (for example, make classifications using logistic regression) and nouns as data (for example, input samples).

The first of these isn’t unique to APLearn, and is a cornerstone of many mainstream data science or ML packages such as scikit-learn. However, APL’s true power shows when considering transparency and composability. With a concise grammar that is analogous to a supercharged form of linear algebra, the reader is spared the necessity of cutting through layers of software noise to arrive at the algorithm underlying the code. Additionally, APL’s syntax natively supports chaining without resorting to special objects like pipes in R or Pipeline in scikit-learn. These two work together to surmount the problems (discussed above) that scikit-learn faces.

Example

A user guide and several examples showing APLearn in action can be found in the APLearn GitHub repository. A video on YouTube demonstrates one of these examples.

Development Journey

In my (biased) opinion, the APLearn user experience is relatively smooth, and one only needs a very basic knowledge knowledge of scikit-learn and APL to be able to set up basic ML workflows. That leaves out my own developer experience working on this project – after all, the initial inquiry was how easy it would be to build, not use, something like scikit-learn in APL. I have multiple takeaways and observations to share:

  • Do arrays suffice?
    APL isn’t appropriate for problems that can’t be adequately reduced to array-like structures, so this entire project would be futile if ML processes couldn’t be captured through arrays. The good news is that, for the majority of algorithms, arrays are the ideal representation. Concrete examples include generalised linear models, statistic-based methods like normalisation or naïve Bayes, and classification or regression metrics. There is, however, one major exception – trees. The naïve way to represent trees in APL, employed by APLearn, is a recursive approach where the first element of a vector stands for the tree node and subsequent elements are sub-trees or leaves. The following, used in APLearn’s decision tree implementation, sets the current parent node using the threshold function and recurses to construct the left and right children. Despite its simplicity, this scheme is undesirable for two reasons. First, it’s contrary to the data-parallel spirit of APL. Second, trees can quickly grow in size, and highly-nested arrays are notoriously inefficient. Smarter alternatives exist, but they’re not easy to get the hang of, hurting the didactic aspect of APLearn. Although not many models rely on trees (the main exceptions being decision trees and space partitioning algorithms), this is an important caveat to consider.

    (thresh i) ((⍺⍺ ∇∇ ⍵⍵)X_l y_l) ((⍺⍺ ∇∇ ⍵⍵)X_r y_r)
  • Is APL efficient?
    I didn’t expect (Dyalog) APL to keep up with scikit-learn performance-wise, and I wasn’t wrong; APLearn is about an order of magnitude slower on average, or even thousands of times for k-means clustering and random forests. The culprit for the inefficiency in k-means is the expensive inner product in the update, depicted below. Luckily, this doesn’t make it unusable, and many real-world workflows can be feasibly executed using APLearn, albeit more slowly. Parts of the code are incompatible with Co-dfns at the time of writing, so that remains an exciting future avenue for unlocking substantial performance enhancements.

    upd←{
        inds←⊃⍤1⍋⍤1⊢X+.{2*⍨⍺-⍵}⍉⍵
        {(+⌿⍵)÷≢⍵}∘⊃⍤0⊢{X⌿⍨inds=⍵}¨⍳st.k
    }
  • How easy is the implementation process?
    I began with the impression that models such as ridge regression or k-nearest neighbors would be easy to implement because they’re based on standard linear algebra operations like matrix multiplication and Euclidean distance. Others, however, like lasso or k-means, daunted me at first because it wasn’t immediately obvious how they could be implemented without explicit loops or branches. Many struggles and clumsy lines of code later, I realized where my error lay: I was trying to translate the imperative pseudo-code of those models bit by bit into APL. This strategy is fine when porting, say, JavaScript to Python, but array processing is in a class of its own and must be treated as such. Instead of fruitlessly endeavouring to translate imperative instructions into array instructions, I decided to start off with the conceptual, non-imperative description of an algorithm and directly implement it APL. Thereafter, my job became significantly easier, and there were no more major hurdles in my way. This is the most impactful lesson I learned – being an effective array programmer requires thinking like one.
  • What degree of noise is there?
    Recall that “noise” in this context refers to code that is not integral to the algorithm but is necessary for effective communication with the computer. If our algorithmic intent is vector addition, for example, noise in the C implementation could comprise advancing pointers or allocating and freeing heap memory. APL mostly dispenses with this type of artifact, and there are no frills in the APLearn codebase; only the essentials are there. This helps the developer to focus on the algorithm instead of the software. For example, the following code is the implementation of APLearn’s metrics, containing practically nothing that would detract attention from the mathematical definitions of each. In contrast, a C implementation would be much longer and more distracting.

    mae←{{(+/⍵)÷≢⍵},|⍺-⍵}
    mse←{{(+/⍵)÷≢⍵},2*⍨⍺-⍵}
    rmse←{0.5*⍨⍺ mse ⍵}
    acc←{{(+/⍵)÷≢⍵},⍺=⍵}
    prec←{(+/,⍺⊥⍵)÷+/,⍵}
    rec←{(+/,⍺⊥⍵)÷+/,⍺}
    f1←{2×p×r÷(p←⍺ prec ⍵)+(r←⍺ rec ⍵)}
  • Is it readable?
    Unreadability is the top accusation leveled against APL. To an extent, that’s a subjective judgement that can’t be fought against, although it’s not fair to call a language unreadable simply because its syntax or characters appear strange at first glance. Ultimately, the reader needs to deliver the final verdict on readability, but one thing is certain: Assuming a firm grasp on APL syntax, it’s far easier to understand what a piece of code written in APL is meant to achieve than that written in most other languages. For example, the APLearn snippet below calculates the parameters of ridge regression. This is a perfect mirror of the mathematical solution, reinforcing the argument above that APL, being concise and noise-free, directly reflects algorithmic intent, aiding comprehension.

    st.w←y+.×⍨(⌹(X+.×⍨⍉X)+reg×∘.=⍨⍳≢⍉X)+.×⍉X

Conclusion

I wrote APLearn to assess APL’s viability for tackling ML problems in a manner that is friendly to learners, researchers, and practitioners. APLearn is not flawless, and there is plenty of room for improvement. For example, some models have fixed hyperparameters that ought to be customisable, random forests are slow, and errors should be handled more gracefully. However, I believe that APLearn proves that APL is a reasonable choice for ML development:

  • It has the potential to achieve cutting-edge performance thanks to data parallelism.
  • With an array-first mindset, it’s faster and easier to implement many ML algorithms in APL than other languages.
  • APL acts like an extension of linear algebra, so model descriptions seamlessly translate to code. This is an incredible benefit for students and researchers who would like to know how it works or modify existing algorithms without being overwhelmed by software-exclusive details.

In the future, I’m planning on improving the performance of APLearn, especially random forests. I’d also like to incorporate better documentation and more robust unit tests.


Relevant links:

Leave a Reply

Your email address will not be published. Required fields are marked *