Metadata for julia fields
Author rafaqz
23 Stars
Updated Last
1 Year Ago
Started In
April 2018


Build Status

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"

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"

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)

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"

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"

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:

Metadata Default Type Use case
default nothing Any Default values (see FieldDefaults.jl)
units 1 Any Unitful.jl unit
prior nothing Any Prior probability distributions
label "" AbstractString Short labels
description "" AbstractString Complete descriptions
bounds (0.0, 1.0) Tuple Upper and lower bounds in optimisers
limits (0.0, 1.0) Tuple Legacy - use bounds
logscaled false Bool For log sliders or log plots
flattenable true Bool For flattening structs with Flatten.jl
plottable true Bool For finding plottable content in nested structs
selectable Nothing Bool Supertypes 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.

Required Packages

No packages found.