Codify best practices for custom types

The announcement for CSV.jl v0.7 includes this new capability:

Custom types can now be passed in the … types keyword …, fast parsing is supported for all Integer and AbstractFloat types; other custom types need to support zero(T) and parse(T, str) to be parsed correctly.

That a custom numeric type T support zero(T) is entirely reasonable and of great utility to clients and other users of that type. With T a natural (whole) number type, zero(T) would throw a DomainError; and imo that is entirely preferable to the “unsupported situation” where users encounter a MethodError.

For numeric types, there are additional methods that, were their provision best practice, would lift package developers’ ease in assuring more nearly effortless interworking . This need not be a long list. I have found it important to provide custom floating point types with these methods:

  • zero, one, abs, signbit, sign, copysign, flipsign
  • <, <=, ==, +, *, string, and Float64(x::T)

For each type-kind (numerics, containers, organizers, symbolics, …) there should be a small set of methods that belong. This would extend Julia’s already epic collaborative reach.


I think that the right approach is specifying interfaces, and declaring that your type supports them. Currently both the specification and the declaration are informal (ie not something you can do in Julia, just a convention you write up in the docs), and this works well.

Specifically for CSV.jl, I think what they do is fine: even if the interface is not explicitly given a name, it is very clear what is required of custom types.

Numbers have no explicit/formalized interface in Julia (yet). I guess that the reason for this is that the number of supported functions is large (eg you missed / and -, which some would argue are useful for numbers) and there are corner cases which can be tricky (eg see the issues in various packages with float).

I entirely agree with this. Alongside a list of methods that are worth implementing for a new numeric type, I think it would also be useful to know which default implementations one gets by making a numeric type a subtype of Number or Real or any abstract type in the number hierarchy.

For now, the technique I tend to use when implementing a new numeric type (say NewNumber) consists in trying to make a snippet like the following work:

a = rand(NewNumber, 10, 10) # or any other sensible way to create a matrix
b = rand(NewNumber, 10)     # and a vector of numbers of the new type
c = a \ b
@assert a * c ≈ b

I tend to think that, once all MethodError are fixed, NewNumber provides a reasonable implementation for numbers. The funny thing is: I don’t think such an approach leads to the same list of methods as yours… which I find a bit worrying. I’d love to have a more formalized way of doing things.

1 Like