Extending third-party APIs in different languages
Tags: api, language, design, extension
Published: 2021-11-07 17:58:23

The need for shorter and shorter Time-To-Market requires to integrate more and more third-party libraries. There's no time for the [NIH syndrom](https://en.wikipedia.org/wiki/Not_invented_here) anymore if it ever was. While most of the time, the library's API is ready to use, it happens that one needs to "adapt" it to the codebase sometimes. How easy the adaptation is depends a lot on the language.

For example, in the JVM, there are a couple of Reactive-Programming libraries: RxJava, Project Reactor, Mutiny, and coroutines. You might need a library that uses types of one library, but you based your project on another.

In this post, I'd like to describe how to add new behavior to an existing object/type. I won't use any reactive type to make it more general but add `toTitleCase()` to `String`. When it exists, inheritance is **not** a solution as it creates a new type.

I apologize in advance that the below implementations are pretty simple: they are meant to highlight my point, not to handle corner cases, _e.g._, empty strings, non-UTF 8, etc.

## JavaScript

JavaScript is an interpreted dynamically- and weakly-typed language, which runs the World Wide Web - until WASM takes over? As far as I know, its design is unique, as it's prototype-based. A prototype is a mold for new "instances" of that type.

You can easily add properties, either state or behavior, to a prototype.

```javascript
Object.defineProperty(String.prototype, "toTitleCase", {
    value: function toTitleCase() {
        return this.replace(/\w\S*/g, function(word) {
            return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase();
        });
    }
});

console.debug("OncE upOn a tImE in thE WEst".toTitleCase());
```

Note that objects created from this prototype *after* the call to `defineProperty` will offer the new property; objects created *before* won't.

## Ruby

Ruby is an interpreted dynamically- and strongly-typed language. While not as popular as it once was with the Ruby On Rails framework, I still use it with the Jekyll system that powers this blog.

Adding methods or attributes to an existing class is pretty standard in the Ruby ecosystem. I found two mechanisms to add a method to an existing type in Ruby:

1. Use [class_eval](https://apidock.com/ruby/Module/class_eval):
    
    >Evaluates the string or block in the context of mod, except that when a block is given, constant/class variable lookup is not affected. This can be used to add methods to a class

2. Just implement the method on the existing class.

Here's the code for the second approach:

```ruby
class String
  def to_camel_case()
    return self.gsub(/\w\S*/) {|word| word.capitalize()}
  end
end

puts "OncE upOn a tImE in thE WEst".to_camel_case()
```

## Python

Python is an interpreted dynamically- and strongly-typed language. I guess every developer has heard of Python nowadays.

Python allows you to add functions to existing types - with limitations. [Let's try](https://www.online-python.com/yv52IK4Mux) with the `str` built-in type:

```python
import re

def to_title_case(string):
    return re.sub(
        r'\w\S*',
        lambda word: word.group(0).capitalize(),
        string)

setattr(str, 'to_title_case', to_title_case)

print("OncE upOn a tImE in thE WEst".to_title_case())
```

Unfortunately, the above code fails during execution:

```
Traceback (most recent call last):
  File "<string>", line 9, in <module>
TypeError: can't set attributes of built-in/extension type 'str'
```

Because `str` is a _built-in_ type, we cannot dynamically add behavior. We can update [the code](https://www.online-python.com/w4G0We7EYh) to cope with this limitation:

```python
import re

def to_title_case(string):
    return re.sub(
        r'\w\S*',
        lambda word: word.group(0).capitalize(),
        string)

class String(str):
    pass

setattr(String, 'to_title_case', to_title_case)

print(String("OncE upOn a tImE in thE WEst").to_title_case())
```

It now becomes possible to extend `String`, because it's a class we have created. Of course, it defeats the initial purpose: we had to extend `str` in the first place. Hence, it works with third-party libraries.

With interpreted languages, it's reasonably easy to add behavior to types. Yet, Python already touches the limits because the built-in types are implemented in C.

## Java

Java is a compiled statically- and strongly-typed language that runs on the JVM. Its static nature makes it impossible to add behavior to a type.

The workaround is to use `static` methods. If you've been a Java developer for a long time, I believe you probably have seen custom `StringUtils` and `DateUtils` classes early in your career. These classes look something like that:

```java
public class StringUtils {

    public static String toCamelCase(String string) {
        // The implementation is not relevant
    }

    // Other string transformations here
}
```

I hope that by now, using Apache Commons and Guava have replaced all those classes:

```java
System.out.println(WordUtils.capitalize("OncE upOn a tImE in thE WEst"));
```

In both cases, *the usage of static methods prevents fluent API usage* and thus impairs developer experience. But other JVM languages do offer exciting alternatives.

## Scala

Like Java, Scala is a compiled, statically- and strongly-typed language that runs on the JVM. It was initially designed to bridge between Object-Oriented Programming and Functional Programming. Scala provides many powerful features. Among them, _implicit_ classes allow adding behavior and state to an existing class. [Here](https://scastie.scala-lang.org/razUhHKRRcqamn9qlA0mhw) is how to add the `toCamelCase()` function to `String`:

```scala
import Utils.StringExtensions

object Utils {
  implicit class StringExtensions(thiz: String) {
    def toCamelCase() = "\\w\\S*".r.replaceAllIn(
      thiz,
      { it => it.group(0).toLowerCase().capitalize }
    )
  }
}

println("OncE upOn a tImE in thE WEst".toCamelCase())
```

Though I dabbled a bit in Scala, I was never a fan. As a developer, I've always stated that a big part of my job was to make _implicit_ requirements _explicit_. Thus, I frowned upon the on-purpose usage of the `implicit` keyword. Interestingly enough, it seems that I was not alone. Scala 3 keeps the [same capability](https://scastie.scala-lang.org/18abIFMKSvWiz8gpbVx2gg) using a more appropriate syntax:

```scala
extension(thiz: String)
  def toCamelCase() = "\\w\\S*".r.replaceAllIn(
    thiz,
    { it => it.group(0).toLowerCase().capitalize }
  )
```

Note that the _bytecode_ is somewhat similar to Java's _static_ method approach in both cases. Yet, API usage is fluent, as you can chain method calls one after another.

## Kotlin

Like Java and Scala, Kotlin is a compiled, statically- and strongly-typed language that runs on the JVM. Several other languages, including Scala, inspired its design.

My opinion is that Scala is more powerful than Kotlin, but the trade-off is an additional cognitive load. On the opposite, Kotlin has a lightweight approach, more pragmatic. Here's the [Kotlin version](https://pl.kotl.in/b67HIw06t):

```kotlin
fun String.toCamelCase() = "\\w\\S*"
    .toRegex()
    .replace(this) {
        it.groups[0]
            ?.value
            ?.lowercase()
            ?.replaceFirstChar { char -> char.titlecase(Locale.getDefault()) }
            ?: this
    }

println("OncE upOn a tImE in thE WEst".toCamelCase())
```

If you wonder why the Kotlin code is more verbose than the Scala one despite my earlier claim, here are two reasons:

1. I don't know Scala well enough, so I didn't manage corner cases (empty capture, etc.), but Kotlin leaves you no choice
2. The Kotlin team removed the `capitalize()` function from the `stdlib` in Kotlin 1.5

## Rust

Last but not least in our list, Rust is a compiled language, statically and strongly typed. It was initially designed to produce native binaries. Yet, with the relevant configuration, it also allows to generate <abbr title="WebAssembly">Wasm</abbr>. In case you're interested, I've taken link:/focus/start-rust/[a couple of notes] while learning the language.

Interestingly enough, though statically-typed, Rust also allows extending third-party APIs as [the following code shows](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=8d1daecd7bd46d6352c131cbf8186839):
```rust
trait StringExt {                                                  // 1
    fn to_camel_case(&self) -> String;
}

impl StringExt for str {                                           // 2
    fn to_camel_case(&self) -> String {
        let re = Regex::new("\\w\\S*").unwrap();
        re.captures_iter(self)
            .map(|capture| {
                let word = capture.get(0).unwrap().as_str();
                let first = &word[0..1].to_uppercase();
                let rest = &word[1..].to_lowercase();
                first.to_owned() + rest
            })
            .collect::<Vec<String>>()
            .join(" ")
    }
}

println!("{}", "OncE upOn a tImE in thE WEst".to_camel_case());
```

1. Create the abstraction to hold the function reference. It's known as a _trait_ in Rust.
2. Implement the trait for an existing structure.

Trait implementation has one limitation: our code must declare at least one of either the trait or the structure. You cannot implement an existing trait for an existing structure.

## Conclusion

Before writing this post, I thought that interpreted languages would allow extending external APIs, while compiled languages wouldn't - with Kotlin the exception. After gathering the material, my understanding has changed drastically.

I realized that all mainstream languages provide such a feature. While I didn't include a C# section, it also does. My conclusion is sad, as Java is the only language that doesn't offer anything in this regard.

I've regularly stated that Kotlin's most significant benefit over Java is extension properties/methods. While the Java team continues to add features to the language, it still doesn't offer a developer experience close to any of the above languages. As I've used Java for two decades, I find this conclusion a bit sad, but it's how it is, unfortunately.

**To go further:**

* [Scala 3 language reference: extension methods](https://docs.scala-lang.org/scala3/reference/contextual/extension-methods.html)
* [Kotlin extensions](https://kotlinlang.org/docs/extensions.html)

_Originally published at [A Java Geek](https://blog.frankel.ch/extending-third-party-apis/) on November 7<sup>th</sup>, 2021_