user2138149
user2138149

Reputation: 16484

How to implement the newtype pattern in Julia?

This concept has a several different possible names which might be used to describe the concept.

"Newtype" usually specifically refers to the implementation of the strong type pattern in a language which has convenient support for it.

One such language is Rust, which has the concept of tuple-structs.

struct SecurityId(i64);

struct SecurityInMarketId(i64);

The "strong type pattern" (I think there are other, better names for this, but I don't recall what they are) aka so-called "strong-typing" is effectively an alias for a type, except that the language treats that type as being different to the type it is aliased from.

BTW - if you know of alternative names for this pattern please post a comment.

Julia has type aliasing.

const MyType = DataFrame

Unfortunately, this is not an example of strong typing, because Julia does not treat MyType as an independent type to DataFrame. It really is just a name alias.

To show this, we can write the following code to prove this cannot be used for type-based function dispatch.

function exampleFunction(df::DataFrame)
    println("DataFrame")
end

function exampleFunction(df::MyType)
    println("MyType")
end

Running this code will result in 1 function exampleFunction with only 1 (not 2) methods. If you run

julia> exampleFunction(DataFrame())
MyType

as shown above, MyType will be printed. This demonstrates the second method implementation of exampleFunction replaced the original definition.

In C++, as far as I recall, the only way to implement this strong typing pattern is to wrap a type in a struct (or class).

class SecurityInMarketId {
    private:
    int _security_in_market_id;
};

Aside: Actually, there are other ways to do it using template metaprogramming. However, this approach is likely to make it more difficult to understand and debug. It is not a straightforward solution in the same way that Rust provides.


The only way I have found to do it in Julia is essentially the same. Wrapping an existing type in a struct.

mutable struct SecurityInMarketId
    _security_in_market_id::Int64
end

This approach has shortcomings. While it does provide a new type for the type system, there is a choice of two compromises to be made:

  1. Access the (supposed to be) "implied" private data
  2. Re-implement all required functions for the newly defined struct type SecurityInMarketId. For example, if wrapping a DataFrame, we are likely to require implementations of functions for element access, filtering, selecting columns and rows, reading and writing to file, etc. There could be a large number of functions which require new implementations just to use the functions of the internal data type which we have wrapped in a struct.

Option 2 clearly requires significant amounts of boilerplate code to be written. Code which has no functionality, it just forwards function calls to the internal data of the struct.

Option 1 results in an "ugly" looking API. For example

println("SecurityInMarketId=$(security_in_market_id._security_in_market_id)")

What would be the canonical way to implement this in Julia? (Or is it simply wrapping in a struct and making one of the two above compromises?)


Why Strong Type Pattern?

The main purpose of this is as a safety rail in contexts where there are function calls which take multiple parameters of the same data type.

This is likely to occur when there are lots of "ids" in use.

For example, I used a SecurityId and a SecurityInMarketId. These are conceptually different things and it is obviously an error to use the wrong one or to swap them when calling a function.

function example(securityId::Int64, securityInMarketId::Int64) ... end

We can prevent use of the wrong variable in the wrong slot with the strong type pattern.

Upvotes: 0

Views: 73

Answers (0)

Related Questions