Miscellaneous

Printing options

AbstractAlgebra supports printing to LaTeX using the MIME type "text/latex". To enable LaTeX rendering in Jupyter notebooks and query for the current state, use the following functions:

Updating the type diagrams

Updating the diagrams of the documentation can be done by modifying and running the script docs/create_type_diagrams.jl. Note that this requires the package Kroki.

Attributes

Often it is desirable to have a flexible way to attach additional data to mathematical structures such as groups, rings, fields, etc. beyond what the original implementation covers. To facilitate this, we provide an attributes system: for objects of suitable types, one may use set_attribute! to attach key-value pairs to the object, and query them using has_attribute, get_attribute and get_attribute!.

Attributes are supported for all singletons (i.e., instances of an empty struct type), as well as for instances of mutable struct type for which attribute storage was enabled. There are two ways to enable attribute storage for such types:

  1. By applying @attributes to a mutable struct declaration, storage is reserved inside that struct type itself (this increases the size of each struct by 8 bytes if no attributes are set).
  2. By applying @attributes to the name of a mutable struct type, methods are installed which store attributes to instances of the type in a WeakKeyDict outside the struct.
AbstractAlgebra.@attributesMacro
@attributes typedef

This is a helper macro that ensures that there is storage for attributes in the type declared in the expression typedef, which must be either a mutable struct definition expression, or the name of a mutable struct type.

The latter variant is useful to enable attribute storage for types defined in other packages. Note that @attributes is idempotent: when applied to a type for which attribute storage is already available, it does nothing.

For singleton types, attribute storage is also supported, and in fact always enabled. Thus it is not necessary to apply this macro to such a type.

Note

When applied to a struct definition this macro adds a new field to the struct. For structs without constructor, this will change the signature of the default inner constructor, which requires explicit values for every field, including the attribute storage field this macro adds. Usually it is thus preferable to add an explicit default constructor, as in the example below.

Examples

Applying the macro to a struct definition results in internal storage of the attributes:

julia> @attributes mutable struct MyGroup
           order::Int
           MyGroup(order::Int) = new(order)
       end

julia> G = MyGroup(5)
MyGroup(5, #undef)

julia> set_attribute!(G, :isfinite, :true)

julia> get_attribute(G, :isfinite)
true

Applying the macro to a typename results in external storage of the attributes:

julia> mutable struct MyOtherGroup
           order::Int
           MyOtherGroup(order::Int) = new(order)
       end

julia> @attributes MyOtherGroup

julia> G = MyOtherGroup(5)
MyOtherGroup(5)

julia> set_attribute!(G, :isfinite, :true)

julia> get_attribute(G, :isfinite)
true
AbstractAlgebra.@attrMacro
@attr [RetType] funcdef

This macro is applied to the definition of a unary function, and enables caching ("memoization") of its return values based on the argument. This assumes the argument supports attribute storing (see @attributes) via get_attribute!.

The name of the function is used as name for the underlying attribute.

Effectively, this turns code like this:

@attr RetType function myattr(obj::Foo)
   # ... expensive computation
   return result
end

into something essentially equivalent to this:

function myattr(obj::Foo)
  return get_attribute!(obj, :myattr) do
    # ... expensive computation
    return result
  end::RetType
end

Examples

julia> @attributes mutable struct Foo
           x::Int
           Foo(x::Int) = new(x)
       end;

julia> @attr Int function myattr(obj::Foo)
                println("Performing expensive computation")
                return factorial(obj.x)
             end;

julia> obj = Foo(5);

julia> myattr(obj)
Performing expensive computation
120

julia> myattr(obj) # second time uses the cached result
120
AbstractAlgebra.has_attributeFunction
has_attribute(G::Any, attr::Symbol)

Return a boolean indicating whether G has a value stored for the attribute attr.

AbstractAlgebra.get_attributeFunction
get_attribute(f::Function, G::Any, attr::Symbol)

Return the value stored for the attribute attr, or if no value has been set, return f().

This is intended to be called using do block syntax.

get_attribute(obj, attr) do
    # default value calculated here if needed
    ...
end
get_attribute(G::Any, attr::Symbol, default::Any = nothing)

Return the value stored for the attribute attr, or if no value has been set, return default.

AbstractAlgebra.get_attribute!Function
get_attribute!(f::Function, G::Any, attr::Symbol)

Return the value stored for the attribute attr of G, or if no value has been set, store key => f() and return f().

This is intended to be called using do block syntax.

get_attribute!(obj, attr) do
    # default value calculated here if needed
    ...
end
get_attribute!(G::Any, attr::Symbol, default::Any)

Return the value stored for the attribute attr of G, or if no value has been set, store key => default, and return default.

AbstractAlgebra.set_attribute!Function
set_attribute!(G::Any, data::Pair{Symbol, <:Any}...)

Attach the given sequence of key=>value pairs as attributes of G.

set_attribute!(G::Any, attr::Symbol, value::Any)

Attach the given value as attribute attr of G.

Advanced printing

Self-given names

We provide macros @show_name, @show_special and @show_special_elem to change the way certain objects are printed.

In compact and supercompact printing mode, @show_name tries to determine a suitable name to print instead of the object (see AbstractAlgebra.get_name).

@show_special checks if an attribute :show is present. If so, it has to be a function taking IO, optionally a MIME-type, and the object. This is then called instead of the usual show function.

Similarly, @show_special_elem checks if an attribute :show_elem is present in the object's parent. The semantics are the same as for @show_special.

All are supposed to be used within the usual show function, where @show_special_elem is only relevant for element types of algebraic structures.

function show(io::IO, A::MyObj)
   @show_name(io, A)
   @show_special(io, A)

   # ... usual stuff
end

function show(io::IO, ::MIME"text/plain", A::MyObj)
   @show_name(io, A)
   @show_special(io, MIME"text/plain"(), A)

   # ... usual stuff
end

Documentation

AbstractAlgebra.PrettyPrinting.@show_specialMacro
@show_special(io::IO, obj)

If the obj has a show attribute, this gets called with io and obj and returns from the current scope. Otherwise, does nothing.

It is supposed to be used at the start of show methods as shown in the documentation.

@show_special(io::IO, mime, obj)

If the obj has a show attribute, this gets called with io, mime and obj and returns from the current scope. Otherwise, does nothing.

It is supposed to be used at the start of show methods as shown in the documentation.

AbstractAlgebra.PrettyPrinting.@show_special_elemMacro
@show_special_elem(io::IO, obj)

If the parent of obj has a show_special_elem attribute, this gets called with io and obj and returns from the current scope. Otherwise, does nothing.

It is supposed to be used at the start of show methods as shown in the documentation.

@show_special_elem(io::IO, mime, obj)

If the parent of obj has a show attribute, this gets called with io, mime and obj and returns from the current scope. Otherwise, does nothing.

It is supposed to be used at the start of show methods as shown in the documentation.

AbstractAlgebra.PrettyPrinting.@show_nameMacro
@show_name(io::IO, obj)

If either property :compact or :supercompact is set to true for io (see IOContext), print the name get_name(obj) of the object obj to the io stream. This macro either prints the name and returns from the current scope, or does nothing.

It is supposed to be used at the start of show methods as shown in the documentation. ```

AbstractAlgebra.PrettyPrinting.set_name!Function
set_name!(obj, name::String; override::Bool=true)

Sets the name of the object obj to name. This name is used for printing using AbstractAlgebra.@show_name. If override is false, the name is only set if there is no name already set.

This function errors if obj does not support attribute storage.

set_name!(obj; override::Bool=true)

Sets the name of the object obj to the name of a variable in global (Main module) namespace with value bound to the object obj, if such a variable exists (see AbstractAlgebra.PrettyPrinting.find_name). This name is used for printing using AbstractAlgebra.@show_name. If override is false, the name is only set if there is no name already set.

This function errors if obj does not support attribute storage.

AbstractAlgebra.PrettyPrinting.find_nameFunction
find_name(obj, M = Main; all::Bool = false) -> Union{String,Nothing}

Return name of a variable in M's namespace with value bound to the object obj, or nothing if no such variable exists. If all is true, private and non-exported variables are also searched.

Note

If the object is stored in several variables, the first one will be used, but a name returned once is kept until the variable no longer contains this object.

For this to work in doctests, one should call AbstractAlgebra.set_current_module(@__MODULE__) in the value argument of Documenter.DocMeta.setdocmeta! and keep the default value of M = Main here.

Warning

This function should not be used directly, but rather through AbstractAlgebra.get_name.

Indentation and Decapitalization

To facilitate printing of nested mathematical structures, we provide a modified IOCustom object, that supports indentation and decapitalization.

Example

We illustrate this with an example

struct A{T}
  x::T
end

function Base.show(io::IO, a::A)
  io = AbstractAlgebra.pretty(io)
  println(io, "Something of type A")
  print(io, AbstractAlgebra.Indent(), "over ", AbstractAlgebra.Lowercase(), a.x)
  print(io, AbstractAlgebra.Dedent()) # don't forget to undo the indentation!
end

struct B
end

function Base.show(io::IO, b::B)
  io = AbstractAlgebra.pretty(io)
  print(io, LowercaseOff(), "Hilbert thing")
end

At the REPL, this will then be printed as follows:

julia> A(2)
Something of type A
  over 2

julia> A(A(2))
Something of type A
  over something of type A
    over 2

julia> A(B())
Something of type A
  over Hilbert thing

Documentation

AbstractAlgebra.PrettyPrinting.IndentType
Indent

When printed to an IOCustom object, increases the indentation level by one.

Examples

julia> io = AbstractAlgebra.pretty(stdout);

julia> print(io, AbstractAlgebra.Indent(), "This is indented")
  This is indented
AbstractAlgebra.PrettyPrinting.DedentType
Dedent

When printed to an IOCustom object, decreases the indentation level by one.

Examples

julia> io = AbstractAlgebra.pretty(stdout);

julia> print(io, AbstractAlgebra.Indent(), AbstractAlgebra.Dedent(), "This is indented")
This is indented
AbstractAlgebra.PrettyPrinting.LowercaseType
Lowercase

When printed to an IOCustom object, the next letter printed will be lowercase.

Examples

julia> io = AbstractAlgebra.pretty(stdout);

julia> print(io, AbstractAlgebra.Lowercase(), "Foo")
foo
AbstractAlgebra.PrettyPrinting.LowercaseOffType
LowercaseOff

When printed to an IOCustom object, the case of the next letter will not be changed when printed.

Examples

julia> io = AbstractAlgebra.pretty(stdout);

julia> print(io, AbstractAlgebra.Lowercase(), AbstractAlgebra.LowercaseOff(), "Foo")
Foo