Conversions
One of the main ideas of GAP.jl is that automatic conversions of Julia objects to GAP objects and vice versa shall be avoided whenever this is possible. For a few types of objects, such conversions are unavoidable, see Automatic GAP-to-Julia and Julia-to-GAP Conversions.
In all other situations, the user must explicitly convert between GAP objects and corresponding Julia objects. This is typically done by "type coercion", also just called "coercion": to convert a Julia object x into a GAP object, you may write GapObj(x), see GapObj. Conversely, if y is a GAP object, then e.g. Vector{Int}(y) will attempt to convert it into a Vector{Int}. This will success if e.g. y is a GAP range or a plain list of integers. See also Constructor Methods for GAP-to-Julia Conversions.
For interactive use it may also be convenient to use the function gap_to_julia with a single argument, which will attempt to "guess" a suitable Julia type for the conversion (e.g. GAP strings will be converted to Julia strings). However, we generally recommend against using it, as usually it is better to coerce to a specific type, as that makes it easier to reason about the code, and helps code to become "type stable" (an important concept for writing performant Julia code).
Automatic GAP-to-Julia and Julia-to-GAP Conversions
When one calls a GAP function with Julia objects as arguments, or a Julia function with GAP objects as arguments, the arguments are in general not automatically converted to GAP objects or Julia objects, respectively. The exceptions are as follows.
GAP's immediate integers (in the range -2^60 to 2^60-1) are automatically converted to Julia's
Int64objects; Julia'sInt64objects are automatically converted to GAP's immediate integers if they fit, and to GAP's large integers otherwise.GAP's immediate finite field elements are automatically converted to Julia's
GAP.FFEobjects, and vice versa.GAP's
trueandfalseare automatically converted to Julia'strueandfalse, and vice versa.
Explicit GAP-to-Julia and Julia-to-GAP Conversions
The following rules hold for explicit conversions.
Julia types control the conversions.
For conversions from Julia to GAP, there is at most one possibility on the GAP side, and the type of the given Julia object determines which code is used.
For conversions from GAP to Julia, several Julia types can be possible for the result, for example a GAP integer can be converted to several Julia integer types, or a GAP string can be converted to a Julia
StringorTuple. Usually one wants to specify the target type, and then this type determines which code is used for the conversion. If one does not specify the target type, a default type will be chosen.Certain Julia types are not supported, for technical reasons. For example, converting a nonempty GAP list to a Julia object of type
Set{GapObj}is not possible because nohashmethod is defined forGapObj.
Subobjects, recursive conversions
GAP lists and records can have subobjects, the same holds for various Julia objects such as vectors, matrices, tuples, and dictionaries. One may or may not want to convert the subobjects recursively, this is controlled by the
recursivekeyword argument of the functionsGAP.gap_to_juliaandGapObj, which can be set totrueorfalse.For both GAP-to-Julia and Julia-to-GAP conversion, the default is non-recursive conversion.
For Julia-to-GAP conversion, recursion stops at subobjects of type
GapObj.For GAP-to-Julia conversion, recursion stops at subobjects that do not have the type
GAP.Obj.For GAP-to-Julia conversion, the given target type may force a conversion of subobjects up to a certain level also if non-recursive conversion is requested. In this case, recursive conversion means to convert subobjects to Julia also if the result has already the target type.
For example, converting a GAP list of lists
lto a Julia object of typeVector{Vector{Any}}means to convert the entries ofltoVector{Any}objects, and non-recursive conversion means that the entries of thel[i]will be kept in the result since the type requirementAnyis satisfied, whereas these entries will get converted to Julia objects in the case of recursive conversion.When recursive conversion is requested, identical subobjects in the given object correspond to identical subobjects in the result of the conversion.
In order to achieve this, a dictionary gets created in the case of recursive conversion, which stores the subobjects and their conversion results. Some of the implications are as follows.
Recursive conversion is more expensive than non-recursive conversion.
It can happen that the results of recursive and non-recursive conversion are equal, but they differ w.r.t. the identity of subobjects. For example, the two entries of the GAP list
GAP.evalstr("[ [ 1, 2 ], ~[1] ]")are identical, the same holds for the two entries of the vector obtained by recursive conversion of this list to an object of typeVector{Vector{Int}}; however, the two entries of the vector obtained by non-recursive conversion of this list to an object of typeVector{Vector{Int}}are equal but not identical.
(Note that "identity of objects" has different meanings in GAP and Julia. For example, converting a GAP list of equal but nonidentical strings to a Julia vector of symbols will yield an object with identical subobjects.)
Mutability of results of conversions
In GAP, mutability is defined for individual objects. GAP objects that are newly created by Julia-to-GAP conversions are mutable whenever this is possible.
In Julia, mutability is defined for types. (The type
GapObjis a mutable type.)
Implementation of conversion methods
In order to install a new GAP-to-Julia conversion for some prescribed target type
T, one has to install aGAP.gap_to_julia_internalmethod whereTis specified as the first argument.In order to install a new Julia-to-GAP conversion for objects of type
T, one has to install aGAP.GapObj_internalmethod. If one knows that objects of typeTneed not support recursive conversion then one can alternatively use theGAP.@installmacro for the installation.
GAP.gap_to_julia — Functiongap_to_julia([type, ]x; recursive::Bool=false)Try to convert the object x to a Julia object of type type. If x is a GapObj then the conversion rules are defined in the manual of the GAP package JuliaInterface. If x is another GAP.Obj (for example a Int64) then the result is defined in Julia by type.
For GAP lists and records, it makes sense to either convert also the subobjects recursively, or to keep the subobjects as they are; the behaviour is controlled by recursive, which can be true or false.
Examples
julia> GAP.gap_to_julia(GapObj(1//3))
1//3
julia> GAP.gap_to_julia(GapObj("abc"))
"abc"
julia> val = GapObj([1 2 ; 3 4])
GAP: [ [ 1, 2 ], [ 3, 4 ] ]
julia> GAP.gap_to_julia(val, recursive = true)
2-element Vector{Any}:
Any[1, 2]
Any[3, 4]
julia> GAP.gap_to_julia(val)
2-element Vector{Any}:
GAP: [ 1, 2 ]
GAP: [ 3, 4 ]
julia> GAP.gap_to_julia(Vector{GapObj}, val)
2-element Vector{GapObj}:
GAP: [ 1, 2 ]
GAP: [ 3, 4 ]
julia> GAP.gap_to_julia(Matrix{Int}, val)
2×2 Matrix{Int64}:
1 2
3 4The following gap_to_julia conversions are supported by GAP.jl. (Other Julia packages may provide conversions for more GAP objects.)
| GAP filter | default Julia type | other Julia types |
|---|---|---|
IsInt | BigInt | T <: Integer |
IsFFE | FFE | |
IsBool | Bool | |
IsRat | Rational{BigInt} | Rational{T} |
IsFloat | Float64 | T <: AbstractFloat |
IsChar | Cuchar | Char |
IsStringRep | String | Symbol, Vector{T} |
IsRangeRep | StepRange{Int64,Int64} | Vector{T} |
IsBListRep | BitVector | Vector{T} |
IsList | Vector{Any} | Vector{T} |
IsMatrixObj | Matrix{Any} | Matrix{T} |
IsVectorObj | Vector{Any} | Vector{T} |
IsRecord | Dict{Symbol, Any} | Dict{Symbol, T} |
GAP.GapObj — MethodGapObj(input, recursive::Bool = false)One can use the type GapObj as a constructor, in order to convert the julia object input to an appropriate GAP object.
If recursive is set to true, recursive conversion of nested Julia objects (arrays, tuples, and dictionaries) is performed.
Examples
julia> GapObj(1//3)
GAP: 1/3
julia> GapObj("abc")
GAP: "abc"
julia> GapObj([1 2; 3 4])
GAP: [ [ 1, 2 ], [ 3, 4 ] ]
julia> GapObj([[1, 2], [3, 4]])
GAP: [ <Julia: [1, 2]>, <Julia: [3, 4]> ]
julia> GapObj([[1, 2], [3, 4]], true)
GAP: [ [ 1, 2 ], [ 3, 4 ] ]
julia> GapObj([[1, 2], [3, 4]], recursive = true)
GAP: [ [ 1, 2 ], [ 3, 4 ] ]Note that this conversion is not restricted to outputs that actually are of type GapObj, also GAP integers, finite field elements, and booleans can be created by the constructor GapObj.
julia> res = GapObj(42); res isa GapObj
false
julia> res isa GAP.Obj
trueThe following GapObj conversions are supported by GAP.jl. (Other Julia packages may provide conversions for more Julia objects.)
| Julia type | GAP filter |
|---|---|
Int8, Int16, ..., BigInt | IsInt |
FFE | IsFFE |
Bool | IsBool |
Rational{T} | IsRat |
Float16, Float32, Float64 | IsFloat |
AbstractString | IsString |
Symbol | IsString |
Char | IsChar |
Vector{T} | IsList |
Vector{Bool}, BitVector | IsBList |
Set{T} | IsList |
Tuple{T} | IsList |
Matrix{T} | IsList |
Dict{String, T}, Dict{Symbol, T} | IsRecord |
UnitRange{T}, StepRange{T, S} | IsRange |
Function | IsFunction |
Constructor Methods for GAP-to-Julia Conversions
(For Julia-to-GAP conversions, one can use GapObj and GAP.Obj as constructors.)
Core.Int128 — TypeInt128(obj::GapObj)Return the Int128 converted from the GAP integer obj. (Note that small GAP integers are represented by Julia Int64 objects, in particular they are not GapObjs; their conversion is not handled by methods installed in GAP.jl.)
Examples
julia> val = GAP.Globals.Factorial(25)
GAP: 15511210043330985984000000
julia> Int128(val)
15511210043330985984000000
julia> Int(val)
ERROR: InexactError: Int64(15511210043330985984000000)Base.GMP.BigInt — TypeBigInt(obj::GapObj)Return the big integer converted from the GAP integer obj. (Note that small GAP integers are not represented by GapObjs, their conversion with BigInt is handled by Julia's methods.)
Examples
julia> val = GAP.Globals.Factorial(25)
GAP: 15511210043330985984000000
julia> BigInt(val)
15511210043330985984000000
julia> val = GAP.Globals.Factorial(10)
3628800
julia> isa(val, GapObj)
false
julia> BigInt(val)
3628800
Base.Rational — TypeRational{T}(obj::GapObj) where {T<:Integer}Return the rational converted from the GAP integer or the GAP rational obj,
Examples
julia> val = GAP.Globals.Factorial(25)
GAP: 15511210043330985984000000
julia> Rational{Int128}(val)
15511210043330985984000000//1
julia> Rational{BigInt}(val)
15511210043330985984000000//1
julia> val = GAP.Obj(1//3)
GAP: 1/3
julia> Rational{Int64}(val)
1//3
Core.Float64 — TypeFloat64(obj::GapObj)Return the float converted from the GAP float obj.
Examples
julia> val = GAP.Obj(2.2)
GAP: 2.2
julia> Float64(val)
2.2
julia> Float32(val)
2.2f0
Core.Char — TypeChar(obj::GapObj)Return the character converted from the GAP character obj.
Examples
julia> val = GAP.Obj('x')
GAP: 'x'
julia> Char(val)
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)
Base.Cuchar — TypeCuchar(obj::GapObj)Return the UInt8 that belongs to the GAP character obj.
Examples
julia> val = GAP.Obj('x')
GAP: 'x'
julia> Cuchar(val)
0x78
Core.String — TypeString(obj::GapObj)Return the Julia string converted from the GAP string obj. Note that GAP's String function can be applied to arbitrary GAP objects, similar to Julia's string function; this behaviour is not intended for this String constructor.
Examples
julia> val = GAP.Obj("abc")
GAP: "abc"
julia> String(val)
"abc"
julia> val = GAP.Obj([])
GAP: [ ]
julia> String(val) # an empty GAP list is a string
""
Core.Symbol — TypeSymbol(obj::GapObj)Return the symbol converted from the GAP string obj.
Examples
julia> str = GAP.Obj("abc")
GAP: "abc"
julia> Symbol(str)
:abc
Base.UnitRange — TypeUnitRange(obj::GapObj)Return the unit range converted from the GAP range obj, which has step width 1.
Examples
julia> val = GAP.Obj(1:10)
GAP: [ 1 .. 10 ]
julia> UnitRange(val)
1:10
julia> UnitRange{Int32}(val)
1:10
Base.StepRange — TypeStepRange(obj::GapObj)Return the step range converted from the GAP range obj, which may have arbitrary step width.
Examples
julia> val = GAP.Obj(1:2:11)
GAP: [ 1, 3 .. 11 ]
julia> StepRange(val)
1:2:11
julia> r = StepRange{Int8,Int8}(val)
1:2:11
julia> typeof(r)
StepRange{Int8, Int8}
Core.Tuple — TypeTuple{Types...}(obj::GapObj; recursive::Bool = false)Return the tuple converted from the GAP list obj. The entries of the list are converted to the required types Types.... If recursive is true then the entries of the list are converted recursively, otherwise non-recursively.
Examples
julia> val = GAP.Obj([1, 5])
GAP: [ 1, 5 ]
julia> Tuple{Int64,Int64}(val)
(1, 5)
julia> val = GAP.Obj([[1], [2]]; recursive=true)
GAP: [ [ 1 ], [ 2 ] ]
julia> Tuple{Any,Any}(val; recursive=true)
(Any[1], Any[2])
julia> Tuple{GapObj,GapObj}(val)
(GAP: [ 1 ], GAP: [ 2 ])
Base.BitVector — TypeBitVector(obj::GapObj)Return the bit vector converted from the GAP list of booleans obj.
Examples
julia> val = GAP.Obj([true, false, true])
GAP: [ true, false, true ]
julia> BitVector(val)
3-element BitVector:
1
0
1
Base.Vector — TypeVector{T}(obj::GapObj; recursive::Bool = false)Return the 1-dimensional array converted from the GAP list obj. The entries of the list are converted to the type T. If recursive is true then the entries of the list are converted recursively, otherwise non-recursively.
If T is UInt8 then obj may be a GAP string.
Examples
julia> val = GAP.Obj([[1], [2]]; recursive=true)
GAP: [ [ 1 ], [ 2 ] ]
julia> Vector{Any}(val; recursive=true)
2-element Vector{Any}:
Any[1]
Any[2]
julia> Vector{Any}(val)
2-element Vector{Any}:
GAP: [ 1 ]
GAP: [ 2 ]
julia> Vector{Vector{Int64}}(val)
2-element Vector{Vector{Int64}}:
[1]
[2]
julia> val = GAP.evalstr( "NewVector( IsPlistVectorRep, Integers, [ 0, 2, 5 ] )" )
GAP: <plist vector over Integers of length 3>
julia> Vector{Int64}(val)
3-element Vector{Int64}:
0
2
5
julia> val = GAP.Obj("abc")
GAP: "abc"
julia> Vector{UInt8}(val)
3-element Vector{UInt8}:
0x61
0x62
0x63
Base.Matrix — TypeMatrix{T}(obj::GapObj; recursive::Bool = false)Return the 2-dimensional array converted from the GAP matrix obj, which can be a GAP list of lists or a GAP matrix object. The entries of the matrix are converted to the type T. If recursive is true then the entries are converted recursively, otherwise non-recursively.
Examples
julia> val = GAP.Obj([[1, 2], [3, 4]]; recursive=true)
GAP: [ [ 1, 2 ], [ 3, 4 ] ]
julia> Matrix{Int64}(val)
2×2 Matrix{Int64}:
1 2
3 4
julia> val = GAP.evalstr( "NewMatrix( IsPlistMatrixRep, Integers, 2, [ 0, 1, 2, 3 ] )" )
GAP: <2x2-matrix over Integers>
julia> Matrix{Int64}(val)
2×2 Matrix{Int64}:
0 1
2 3
Base.Set — TypeSet{T}(obj::GapObj; recursive::Bool = false)Return the set converted from the GAP list or GAP collection obj. The elements of obj are converted to the required type T. If recursive is true then the elements are converted recursively, otherwise non-recursively.
This constructor method is intended for situations where the result involves only native Julia objects such as integers and strings. Dealing with results containing GAP objects will be inefficient.
Examples
julia> Set{Int}(GAP.Obj([1, 2, 1]))
Set{Int64} with 2 elements:
2
1
julia> Set{Vector{Int}}(GAP.Obj([[1], [2], [1]]))
Set{Vector{Int64}} with 2 elements:
[1]
[2]
julia> Set{String}(GAP.Obj(["a", "b"]; recursive=true))
Set{String} with 2 elements:
"b"
"a"
julia> Set{Any}(GAP.Obj([[1], [2], [1]]; recursive=true))
Set{Any} with 2 elements:
Any[1]
Any[2]Base.Dict — TypeDict{Symbol,T}(obj::GapObj; recursive::Bool = false)Return the dictionary converted from the GAP record obj. If recursive is true then the values of the record components are recursively converted to objects of the type T.
Examples
julia> val = GAP.Obj(Dict(:a => 1, :b => 2))
GAP: rec( a := 1, b := 2 )
julia> Dict{Symbol,Int}(val)
Dict{Symbol, Int64} with 2 entries:
:a => 1
:b => 2
julia> val = GAP.Obj(Dict(:l => GAP.Obj([1, 2])))
GAP: rec( l := [ 1, 2 ] )
julia> Dict{Symbol,Any}(val)
Dict{Symbol, Any} with 1 entry:
:l => GAP: [ 1, 2 ]
julia> Dict{Symbol,Any}(val; recursive=true)
Dict{Symbol, Any} with 1 entry:
:l => Any[1, 2]
julia> Dict{Symbol,Vector{Int}}(val; recursive=true)
Dict{Symbol, Vector{Int64}} with 1 entry:
:l => [1, 2]