Home

Awesome

FieldMetadata

Build Status codecov.io

This package lets you define metadata about fields in a struct, like tags in Go. It uses a similar syntax to Parameters.jl, with a | bar instead of =. You can use it as a minimalist replacement for Parameters.jl with the aid of FieldDefaults.jl.

Note that development effort has shifted to ModelParameters.jl, which achieves similar goals in an arguably cleaner way.

FieldMetadata on nested structs can be flattened into a vector or tuple very efficiently with Flatten.jl, where they are also used to exclude fields from flattening.

This example that adds string description metadata to fields in a struct:

using FieldMetadata
@metadata describe ""

@describe mutable struct Described
   a::Int     | "an Int with a description"  
   b::Float64 | "a Float with a description"
end

d = Described(1, 1.0)

julia> describe(d, :a) 
"an Int with a description"  

julia> describe(d, :b) 
"a Float with a description"  

julia> describe(d, :c) 
""  

A more complex example. Here we type-check metadata for describe to be String and bounds to be Tuple, by passing an extra argument to the macro:

using Parameters
@metadata describe "" String
@metadata bounds (0, 1) Tuple

@bounds @describe @with_kw struct WithKeyword{T}
    a::T = 3 | (0, 100) | "a field with a range, description and default"
    b::T = 5 | (2, 9)   | "another field with a range, description and default"
end

k = WithKeyword()

julia> describe(k, :b) 
"another field with a range, description and default"

julia> bounds(k, :a) 
(0, 100)
""  

You can chain as many metadata macros together as you want. As of FieldMetadata.jl v0.2, macros are written in the same order as the metadata columns, as opposed to the opposite order which was the syntax in v0.1

However, @with_kw from Parameters.jl must be the last macro and the first field, if it is used. Additionally, any field with a default value must also have a metadata annotation. If you assign a default value but no metadata to any field, it will raise a LoadError with a message type XXX has no field head. You can use the default value by adding _ as an annotation, e.g.

@bounds @with_kw struct DefaultWithKeyword{T}
    a::T = 0 | _  # omitting the `| _` will cause an errow
    b::T = 0 | (0, 1)
end

You can also update or add fields on a type that is already declared using a begin block syntax. You don't need to include all fields or their types.

This is another change from the syntax in v0.1, where @re was prepended to update using the same struct syntax.

julia> describe(d)                                                                                                     
("an Int with a description", "a Float with a description")  

@describe Described begin
   b | "a much better description"
end

julia> d = Described(1, 1.0)

julia> describe(d)
("an Int with a description", "a much better description")

We can use typeof(x) and a little meta-programming instead of the type name, which can be useful for anonymous function parameters:

@describe :($(typeof(d))) begin
   a | "a description without using the type"
end

julia> describe(d)
("a description without using the type", "a much better desc ription")

Metadata placeholders

FieldMetadata provides an api of some simple metadata tags to be used across packages:

MetadataDefaultTypeUse case
defaultnothingAnyDefault values (see FieldDefaults.jl)
units1AnyUnitful.jl unit
priornothingAnyPrior probability distributions
label""AbstractStringShort labels
description""AbstractStringComplete descriptions
bounds(0.0, 1.0)TupleUpper and lower bounds in optimisers
limits(0.0, 1.0)TupleLegacy - use bounds
logscaledfalseBoolFor log sliders or log plots
flattenabletrueBoolFor flattening structs with Flatten.jl
plottabletrueBoolFor finding plottable content in nested structs
selectableNothingBoolSupertypes to select child constructors from

To use them, call:

import FieldMetadata: @prior, prior

You must import at least the function to use these placeholders, using is not enough as you are effectively adding methods for you own types.

Calling @prior or similar on someone else's struct may be type piracy and shouldn't be done in a published package unless the macro is also defined there. However, it can be useful in scripts.