User Tools

Site Tools


inquiring
snippet.juliarepl
julia> pkgchk( [ "julia" => v"1.0.2" ] )

Inquiring About Objects

The Julia Essentials are good first reading.

Julia is a language built around many novel ideas. One of these ideas is treating types as first-order objects. With all the resulting advantages, it also creates a novel problem: Types abound (with potentials for many conversion fails). For example, there are types for the following:

  • Substrings are “views” into strings. They can be treated almost the same, except when they cannot. If you want to define functions that can operate on the subtypes, too, you need to refer to the AbstractTypes—yet another set of types. Thus, you might want to type a function argument as an AbstractString, in which case it can work on both substrings and strings.
  • :ab is the unevaluated Symbol 'ab', "ab" is the string—although strings could have been used for the Symbols. Symbols have limitations on characters (e.g., they cannot begin with a number, contains spaces, etc.). Then there are special strings, like regular expressions (r"abc"), substitution expressions (s"def"), version strings (v"1.2.0"), and so on.
  • There are about a dozen different integer types depending on signed and unsigned, and bit-width (range)—not even counting the Union types which allow “missing” values (of the Missing type).
  • There are first-class arrays of many types, such as Vectors and Matrices. This is wonderful, but it can cause headaches with too many choices—a vector of vectors is not a matrix, row vectors are not column vectors, a 1-dimensional matrix is not a vector, etc.
  • There are iterators and subiterators for all sorts of objects. These are “almost” but not quite like an array of indexes or pointers.

and so on. The existence of different types and instantiated objects of types makes it important to understand how to interrogate and distinguish among types and objects.

To be clear, despite all the drawbacks and occasional head-scratchers, strictly typed language errors are wonderful. Compile-time syntax errors are far less harmful than run-time errors in more lax languages [like those common in R]. This is because type mismatches alert the programmer immediately to the problem. It makes it much simpler to extinguish bugs. (If anything, I wish that Julia allowed me to set a pragma to force all variables to be type-defined.)

Because this is “only” a cookbook, this chapter can first discuss how to interrogate existing objects, instead of first explaining the objects themselves. You can always read later chapters first, and then return here.

In the course of the explanations in this chapter, we will define some variables. The details may remain mysterious to you until the later chapters, in which we will explain them in detail.

Some Julia Language Guidance

  • Comments follow # to the end of the line.
  • A semi-colon at the end means that no output should be printed.
  • Symbols and Variable names can include '_' but not '.'.
  • UTF8 is allowed in identifiers. For example, π is a constant that is an alias for pi.

Determining Your Version of Julia and Packages

This was already covered in Installation and Packages:

snippet.juliarepl
julia> VERSION
v"1.0.2"

julia> if ( VERSION <= v"2.0.0" ) println("Your Julia version is too old (in the future)") end#if#
Your Julia version is too old (in the future)
snippet.julianoeval
[download only julia statements]
julia> using Pkg
 
julia> Pkg.installed()				## list all installed (well, we only have one installed)
Dict{String,Union{Nothing, VersionNumber}} with 1 entry:
  "Distributions" => v"0.16.4"
 
julia> @assert( Pkg.installed()["Distributions"] >= v"0.15", "Your Distributions package is outdated" )

Determining your Linear Algebra Libraries

snippet.julianoeval
[download only julia statements]
julia> using LinearAlgebra; LinearAlgebra.versioninfo()
BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY Haswell MAX_THREADS=16)
LAPACK: libopenblas64_

Learning the Types and Contents of Existing Objects

WARNING: Julia's assignments, such as se2= se, not for primitives (such as numbers or strings), but for containers (such as vectors, structures, dictionaries, etc.) create aliases, not copies. Use copy() and deepcopy() instead of = when you want to change se2 while leaving se undisturbed.

Basic Use of typeof()

The two most important ways of interrogating types and objects are typeof() and dump().

snippet.juliarepl
julia> typeof( VERSION )
VersionNumber

julia> typeof( pi )
Irrational{}

julia> typeof( 12.0 )
Float64

julia> typeof( [ "x"=> Dict( :a => 2, :b => 3 ), "y" => Dict() ] )
Array{Pair{String,Dict},1}

julia> typeof( Int64 )		## even types have type (DataType) in Julia!
DataType

Basic Use of dump()

snippet.juliarepl
julia> dump( VERSION )		## VERSION is a struct with multiple components
VersionNumber
  major: UInt32 0x00000001
  minor: UInt32 0x00000000
  patch: UInt32 0x00000002
  prerelease: Tuple{} ()
  build: Tuple{} ()

julia> dump(pi)
Irrational{} π = 3.1415926535897...

julia> dump( 12.0 )
Float64 12.0

julia> dump( [ "x"=> [ 2, 3 ], "y" => [ 4, 5] ] )	## an array of pairs
Array{Pair{String,Array{Int64,1}}}((2,))
  1: Pair{String,Array{Int64,1}}
    first: String "x"
    second: Array{Int64}((2,)) [2, 3]
  2: Pair{String,Array{Int64,1}}
    first: String "y"
    second: Array{Int64}((2,)) [4, 5]

julia> dump( Int64 )		## and types can be dumped, too!  Int64 is a subtype of Signed
Int64 <: Signed

Arrays and Tuples

Arrays and tuples are discussed in extensive detail in the arrays chapter.

The two most important arrays are vectors and matrices, which are one-dimensional and two-dimensional arrays. They are ordered lists and always start from 1. A Vector is an alias to Array{ Type, 1 }, a Matrix is an alias to Array{ Type, 2 }.

Tuples are like arrays, but they are read-only.

Tuple of Anys

snippet.juliarepl
julia> tuple_of_many = (12 , 3 , "a" , Float64)		## define a tuple (=read-only vector), holding manything.
(12, 3, "a", Float64)

julia> typeof(tuple_of_many)				## a tuple containing different types
Tuple{Int64,Int64,String,DataType}

julia> isa(tuple_of_many, Tuple)			## check type
true

julia> isa(tuple_of_many, Vector)			## but a tuple is not a vector
false

julia> isa(tuple_of_many, Vector{Any})			## and thus also not this one.
false

julia> eltype(tuple_of_many)				## contents of each cell in the tuple
Any

julia> eltype(tuple_of_many) <: Real			## does Any fit into Real numbers (like Ints, Floats?)
false

julia> dump(tuple_of_many)				## "dump()" is the universal inquisitor
Tuple{Int64,Int64,String,DataType}
  1: Int64 12
  2: Int64 3
  3: String "a"
  4: Float64 <: AbstractFloat
  • The <: checks whether the LHS type is a subtype of the RHS type

Tuple (Read-Only) of Numbers

snippet.juliarepl
julia> tuple_of_int = (12 , 3 , 14)			## define a tuple of integers---like a readonly vector
(12, 3, 14)

julia> typeof(tuple_of_int)				## tuple_of_int holds a 1-dim tuple
Tuple{Int64,Int64,Int64}

julia> isa(tuple_of_int, Tuple)				## obvious
true

julia> isa(tuple_of_int, Tuple{Any})			## not, this is an "int" tuple, not an "any" tuple
false

julia> isa(tuple_of_int, Tuple{Int})			## sorry, but this would mean only one element in the tuple
false

julia> eltype(tuple_of_int)				## contents of each cell now
Int64

julia>  eltype(tuple_of_int) <: Real			## Ints are a type of Real number!
true

julia>  eltype(tuple_of_int) <: Int16			## but not a type of Int16
false

Vector (Read-Write) of Numbers

An Any object can hold anything. A vector can be one of Any objects.

snippet.juliarepl
julia> vector_of_int = [12 , 3 , 14]			## define a vector of integers
3-element Array{Int64,1}:
 12
  3
 14

julia> typeof(vector_of_int)				## vector_of_int holds a 1-dim array
Array{Int64,1}

julia> eltype(vector_of_int)				## see?
Int64

julia> isa(vector_of_int, Vector{Any})			## it is an "int" vector, not an "any" vector
false

julia> isa(vector_of_int, Vector{Int})			## see?
true

julia> eltype(vector_of_int) <: Real			## Ints are a type of Real number
true

julia> eltype(vector_of_int) <: Int16			## but not a type of Int16
false

Structs

Structs are also called “composite types” in Julia and hold compile-time keys with values. Here is a nested struct:

snippet.juliarepl
julia> struct SI; si::Int8; m::Vector{String}; end#struct	## SI is a type (a struct)

julia> struct SE; v1::Int32; v2::Float64; v3::SI; end#struct	## SE type struct nests an SI type struct

julia> typeof(SI)						## all types, just like typeof(Float64) == DataType
DataType

julia> dump(SE)							## the universal inquisitor on the Type
SE <: Any
  v1::Int32
  v2::Float64
  v3::SI

julia> const se= SE(  12, 12.2, SI( 2, ["ab", "cd"] )  )	## se is a now-initialized variable of type SE
SE(12, 12.2, SI(2, ["ab", "cd"]))

julia> dump(se)							## the universal inquisitor on the se object of SE Type
SE
  v1: Int32 12
  v2: Float64 12.2
  v3: SI
    si: Int8 2
    m: Array{String}((2,))
      1: String "ab"
      2: String "cd"

Dictionaries

Dictionaries are unordered key-value lists, with unique keys. They are like run-time structs.

snippet.juliarepl
julia> const DT= Dict{String, Int}				## dictionary TYPE (like run-time struct, called a hash in perl)
Dict{String,Int64}

julia> dump(DT)							## the universal inquisitor, here on the TYPE
Dict{String,Int64} <: AbstractDict{String,Int64}
  slots::Array{UInt8,1}
  keys::Array{String,1}
  vals::Array{Int64,1}
  ndel::Int64
  count::Int64
  age::UInt64
  idxfloor::Int64
  maxprobe::Int64

julia> const d= DT("hi" => 2, "ho" => 3)			## an instantiated object of the DT type
Dict{String,Int64} with 2 entries:
  "hi" => 2
  "ho" => 3
  • dump() is not so useful on dicts objects (variables), because it shows all the empty slots and therefore reports a lot of (useless) output.

Is it a Number?

Is an object a (real) number?

snippet.juliarepl
julia> isa(12.0, Real)
true

julia> isa("a", Real)
false

julia> 12 <: Real		## 12 is an integer value, not a type
ERROR: TypeError: in <:, expected Type, got Int64
Stacktrace:
 [1] top-level scope at none:0

julia> typeof(12) <: Real	## but the type of 12 is a real
true

julia> typeof(12.0) <: Union{Real, Missing}	## Missing will be important.
true

Hierarchy of Numerical Types (and '<:' and '>:')

Also called “supertypes” and “subtypes”, inquirable via the <: and >: operators (already shown repeatedly above).

snippet.juliarepl
julia> subtypes(Integer)				## there are 4 types of Ints
3-element Array{Any,1}:
 Bool
 Signed
 Unsigned

julia> supertype(Integer)				## Integers are a type of Real, as are Floats
Real

julia> function showtypetree(T, level= 0)		## Here is a function to print the hierarchy
	    println("	" ^ level, T)
	    for t in subtypes(T)
		if (t != Any)
		    showtypetree(t, level+1)
		end#if
	    end#for
	end#function showtypetree##
showtypetree (generic function with 2 methods)

julia> showtypetree(Signed)				## For example, there are 5 explicitly signed types
Signed
	BigInt
	Int128
	Int16
	Int32
	Int64
	Int8
  • Int is an alias for Int64 on 64-bit systems and Int32 on 32-bit systems.
  • Float64 and Float32 are the most common scientific types, supported natively by modern CPUs. This makes them fast and good. Please avoid most other Floats, unless you really need them.
  • Like objects, (numerical) types have hierarchies. See also Numeric Scalars. Knowing about the hierarchy is useful when you want to write functions that can work on different types.
  • The structs chapter shows how the user can also define structs that inherit from (are subtypes of) other structs.

Functions

Functions are discussed in detail in the two functions chapters. Unlike many interpreted languages (like R or python), Julia compiles its source. Therefore (in Julia 1.0), it does not retain the source. Once compiled, it cannot spit back the source code of the compiled function.

snippet.juliarepl
julia> map
map (generic function with 58 methods)

Ouch! Which one do you mean? To find out, see methods() explained below.

Finding a Function that does X

For now, google is probably your best friend.

Getting Help for a Known Function

Upon typing ?, the prompt changes to help?>.

snippet.julianoeval
[download only julia statements]
julia> ?map
[lots of help output, often including working examples)

Learning More About a Known Function

snippet.julianoeval
[download only julia statements]
julia> apropos("sqrt")			## where is sqrt mentioned?
Base.isqrt
Base.Rounding.rounding
Base.Rounding.setrounding
Base.Broadcast.@__dot__
Base.sqrt
Base.Math.hypot
Core.DomainError
LinearAlgebra.pinv
Base.sqrt
Base.acos
Base.asin
Distributed.remotecall_fetch
Statistics.std

Finding all Methods (Type Versions) of a Known Function

snippet.julianoeval
[download only julia statements]
julia> methods(sqrt)			## list all versions of sqrt(...)
# 19 methods for generic function "sqrt":
sqrt(x::BigInt) in Base.MPFR at mpfr.jl:486
sqrt(x::BigFloat) in Base.MPFR at mpfr.jl:477
...

To find out whether a function would work with a particular type, use

snippet.juliarepl
julia> @which( sqrt(1) )				## which function would be called with this arg?
sqrt(x::Real) in Base.Math at math.jl:518

julia> applicable(sqrt, 1)				## would this function call work?
true

julia> applicable(sqrt, 1, "secondarg")                 ## would this function call work?
false

julia> hasmethod(sqrt, Tuple{Int})
true

julia> hasmethod(sqrt, Tuple{Int,String})
false

Finding all Functions Working on or with Type T

snippet.julianoeval
[download only julia statements]
julia> methodswith( Set )			## what functions can operate on Set(s)?
38-element Array{Method,1}:
 <(l::Set, r::Set) in Base at set.jl:97
 <=(l::Set, r::Set) in Base at set.jl:98
 ==(l::Set, r::Set) in Base at set.jl:96
 allunique(::Set) in Base at set.jl:224
 convert(::Type{Set{T}}, s::Set{T}) where T in Base at set.jl:255
... (other 33 are omitted) ...

Julia's Function Names (@!.)

Explained in the Functions chapter, there are four types of function names:

  1. At-Prefix functions, such as @assert( false, "die! die! die!"), are macros implemented via the Julia preprocessor.
  2. Dot-Postfix functions (and dot-prefix operators), such as sqrt.( [1,4,9] ), operate element-wise on each component of an array. sqrt. is actually not really a function name, but syntax for broadcast the sqrt function to the argument.
  3. Exclamation-Postfix functions, such as push!(x, 3), signal that they alter their first argument (here x).
  4. None of the above, ordinary functions, like sqrt(2).

Finding the Location of a Function Definition

snippet.juliarepl
julia> functionloc(sqrt, (Float64,))	## many types, so Float64 must be given as Tuple
("/Applications/Julia–1.0.app/Contents/Resources/julia/bin/../share/julia/base/math.jl", 492)

Showing the Function Source of a Method

Define on your command-line interface the variable 'JULIA_EDITOR'. For example, on macos or linux, you might export JULIA_EDITOR=atom. Then try

snippet.julianoeval
[download only julia statements]
julia> function f(x); y= x*x; end;
 
julia> @edit( f(12) )
  • You must give the complete function call. Otherwise julia does not know which method you mean.
  • This does not work for emacs under macos.

Finding the Function Name

snippet.juliarepl
julia> f= sqrt; g= f; h= g; Symbol(h)	## or use string(h)
:sqrt

Listing all Current Global Objects

snippet.julianoeval
[download only julia statements]
julia> struct SI; si::Int8; m::Vector{String}; end; struct SE; v1::Int32; v2::Float64; v3::SI; end;
 
julia> const se= SE(  12, 12.2, SI( 2, ["ab", "cd"] )  );		## see above
 
julia> varinfo()
name                    size summary
–––––––––––––––– ––––––––––– ––––––––––––––
Base                         Module
Core                         Module
InteractiveUtils 154.419 KiB Module
Main                         Module
SE                 204 bytes DataType
SI                 196 bytes DataType
ans                116 bytes SE
se                 116 bytes SE
 
julia> varinfo(r"se")
name      size summary
–––– ––––––––– –––––––
Base           Module
se   116 bytes SE

InteractiveCodeSearch.jl adds @search to the REPL. Use @search * to get to a QUERY prompt, and then you can search for function names, argument names, etc.

Finding An Object's (Memory) Size

snippet.juliarepl
julia> struct SI; si::Int8; m::Vector{String}; end; struct SE; v1::Int32; v2::Float64; v3::SI; end;

julia> const se= SE(  12, 12.2, SI( 2, ["ab", "cd"] )  );

julia> sizeof(se)						## only the direct elements: Int32, Float64, SI-reference: 24 bytes
24

julia> Base.summarysize(se)					## includes referenced structure SI with Int8 and Vector{String}: 76 bytes
116

Checking if an Object Exists

Checking for object existence is less useful in a firmly-typed compiled language than in an interpreted language in which most variables are arbitrary untyped objects that therefore can hold “empty” and/or be undefined (such as in perl or R). In Julia, a Float64 always holds a float, possibly uninitialized or nothing, but never itself nil.

snippet.juliarepl
julia> isdefined(Main, :x)
false

julia> x= 1
1

julia> isdefined(Main, :x)
true

Removing an Object

It is possible to remove an object's memory allocation by assigning nothing to it. Any use of this variable will then trigger an exception. In Julia 1, it is not possible to remove the object name itself from the namespace.

Saving and Restoring Julia Objects (in Binary Format)

snippet.juliarepl
julia> using Serialization						## this is part of the base Julia

julia> const x= [ [1.0,"String",'c'], [1] ];				## any kind of object in julia, here a pretty useless one

julia> fo= open("sample.jls", "w"); serialize( fo, x ); close(fo);	## save x to disk

julia> const y= deserialize( open("sample.jls") )			## shortened version; gc will close file
2-element Array{Array{Any,1},1}:
 [1.0, "String", 'c']
 [1]
  • WARNING serialize() and deserialize() are fast, but they are not good long-term storage formats. The binary object format representation can change with every Julia release. Therefore, for long-term data storage, please use something else.
  • WARNING The shortened open version means Julia does not close the stream at the end of the deserialize(). Instead, Julia closes it (automatically) only when it runs its regular garbage collection (which the user can also force with gc()). Leaving a stream open is not dangerous when reading files (as in the example above), and wastes just a tiny amount of memory (for the unreleased operating system streampointer). Leaving a stream open when writing is superbad. Make sure to flush “w” files at the end (usually by closing the files, or (if you use the above shortcut) by invoking GC.gc() after their streampointers have gone out of scope).
  • Different ways of opening and closing files are discussed in [fileio].

Backmatter

Useful Packages on Julia Repository

Notes

References

inquiring.txt · Last modified: 2018/12/07 15:17 (external edit)