Author Archives: Steven Whitaker

Delving into Open Source Packages for Julia

By: Steven Whitaker

Re-posted from: https://glcs.hashnode.dev/learning-packages

Julia is a relatively new,free, and open-source programming language.It has a syntaxsimilar to that of other popular programming languagessuch as MATLAB and Python,but it boasts being able to achieve C-like speeds.

Julia comeswith a lot of functionality built-in.However, functionalitythat isn’t already built-inneeds to be createdfrom base Julia.Fortunately,Julia provides a simple, yet powerful, mechanismfor reusing and sharing code:packages.

Thanks to packages,we don’t have to write the codeto do many common tasks ourselves.(Imagine having to write a plotting function from scratch…)

We learned in a previous post about the Pkg REPL promptand how it can be used to install packages.

To install a package,you have to know it exists.And when using a packagefor the first time,how do you know where to begin?The purpose of this postis to address this question.

In this post,we will learn how to find useful packagesand demonstrate how to discovera package’s functionalityand learn how to use it.

This post assumes you already havea basic understanding of variables and functionsin Julia.You should also understand the differencebetween functions and methods.If you haven’t yet,check out our earlierpost on variables and functionsas well as our post on multiple dispatch,which explains the differencebetween functions and methods.

Package Discovery

The first step to learning a Julia packageis actually finding the package.

Essentially all Julia packages are registered,or made available for downloadvia the Pkg REPL prompt,in Julia’s General registry.Therefore, one could look through the registryto get a sense of the different packages available.

For a more powerful way to explore Julia packages,check out juliapackages.com.This website gathers information from GitHubto allow sorting by popularity(as measured by the number of GitHub stars)and when packages were last updated(which can help give a senseof how actively maintained or updated packages are).You can also explore packages by category.

Screenshot of juliapackages.com

Finally,another way to discover packagesis to visit Julia Discourse.You can look at package announcementsto see what packages are being created.You can also peruse the specific domains tagsto see what packages people are talking aboutand get a feel for what packages people usefor different applications.

Now that we have some toolsfor discovering packages,let’s discuss how to learnhow to use a package.

Learning Package Functionality

Look at Documentation

The first step to finding outwhat a package has to offeris to look at the package’s documentation.

Picture of an open book

Most packages will have at least a READMEthat will list package functionalityand provide some examplesof how to use the package.See Interpolation.jl’s READMEas an example.

Often, more established packageswill also have dedicated documentation(that typically is linkedin the README).Documentation typically includesmore in-depth examplesof how to perform specific tasksusing the package.For example,DataFrames.jl includes a “First Steps” pagein its documentation.

Another common feature of package documentationis a list of functions, types, constants, and other symbolsdefined by the package.See, for example,ForwardDiff.jl’s differentiation API.This list can be usefulfor discovering all possible package functionality,especially when the examples elsewhere in the documentationcover only a small portionof package functionality.

Explore in the REPL

Besides looking at online documentation,the REPL can also be usefulfor learning how to use a package.

After a package is loaded,an exhaustive list of symbolsdefined in a packagecan be obtained via tab completion:

julia> using Debuggerjulia> Debugger.<tab><tab>@bp                             @breakpoint                     @enter@make_frame                     @run                            DebugCompletionProviderDebuggerState                   HIGHLIGHT_24_BIT                HIGHLIGHT_256_COLORSHIGHLIGHT_OFF                   HIGHLIGHT_SYSTEM_COLORS         HighlightOptionLimitIO                         LimitIOException                LineNumbersMAX_BYTES_REPR                  NUM_SOURCE_LINES_UP_DOWN        RESETRunDebugger                     SEARCH_PATH                     WATCH_LIST__init__                        _current_theme                  _eval_code_iscall                         _isdotcall                      _make_frame_preprocess_enter               _print_full_path                _syntax_highlightingactive_frame                    add_breakpoint!                 add_watch_entry!append_any                      assert_allow_step               body_for_methodbreak_off                       break_on                        break_on_errorbreakpoint                      breakpoint_char                 breakpoint_linenumberscheck_breakpoint_index          clear_watch_list!               completionscompute_source_offsets          disable_breakpoint!             enable_breakpoint!eval                            execute_command                 get_function_in_module_or_Mainhighlight_code                  include                         interpret_variableinvalid_command                 julia_prompt                    locdesclocinfo                         maybe_quote                     parse_as_much_as_possiblepattern_match_apply_call        pattern_match_kw_call           print_codeinfoprint_frame                     print_lines                     print_localsprint_next_expr                 print_sourcecode                print_statusprint_var                       promptname                      remove_breakpoint!repr_limited                    set_highlight                   set_themeshow_breakpoint                 show_breakpoints                show_watch_liststacklength                     suppressed                      toggle_breakpoint!toggle_lowered                  toggle_mode                     write_prompt

As discussed in ourpost about the Julia REPL,the help prompt can be usedto display documentationfor individual functions and types:

# Press ? to enter help modehelp?> filtersearch: filter filter! fieldtype fieldtypes  filter(f, a)  Return a copy of collection a, removing elements for which f is false.  The function f is passed one argument.

If you want to find outwhat methods existfor a given function,you can use tab completion:

julia> print(<tab>print(io::IO, ex::Union{Core.GotoNode, Core.SSAValue, Expr, GlobalRef, Core.GotoIfNot, LineNumberNode, Core.PhiCNode, Core.PhiNode, QuoteNode, Core.ReturnNode, Core.Slot, Core.UpsilonNode}) @ Base show.jl:1384print(io::IO, s::Union{SubString{String}, String}) @ Base strings/io.jl:246print(io::IO, x::Union{Float16, Float32}) @ Base.Ryu ryu/Ryu.jl:128print(io::IO, n::Unsigned) @ Base show.jl:1144

You can also use the methods function:

julia> methods(print)# 35 methods for generic function "print" from Base:  [1] print(io::IO, ex::Union{Core.GotoNode, Core.SSAValue, Expr, GlobalRef, Core.GotoIfNot, LineNumberNode, Core.PhiCNode, Core.PhiNode, QuoteNode, Core.ReturnNode, Core.Slot, Core.UpsilonNode})     @ show.jl:1384  [2] print(io::IO, s::Union{SubString{String}, String})     @ strings/io.jl:246  [3] print(io::IO, x::Union{Float16, Float32})     @ Base.Ryu ryu/Ryu.jl:128  [4] print(io::IO, n::Unsigned)     @ show.jl:1144  

The methods functionalso allows filteringon input typesand on the modulein which the methods are defined.For example,to get a list of methods of printthat take two arguments,the second of which is an AbstractChar:

julia> methods(print, (Any, AbstractChar))# 5 methods for generic function "print" from Base: [1] print(io::IO, c::Char)     @ char.jl:252 [2] print(io::IO, c::AbstractChar)     @ char.jl:253 [3] print(io::IO, x)     @ strings/io.jl:32 [4] print(io::IO, xs...)     @ strings/io.jl:42 [5] print(xs...)     @ coreio.jl:3

(See also the related methodswith function.)

And to get methods of printdefined in the Dates package:

julia> using Datesjulia> methods(print, Dates)# 4 methods for generic function "print" from Base: [1] print(io::IO, x::Period)     @ Dates ~/programs/julia/julia-1.9.4/share/julia/stdlib/v1.9/Dates/src/periods.jl:48 [2] print(io::IO, t::Time)     @ Dates ~/programs/julia/julia-1.9.4/share/julia/stdlib/v1.9/Dates/src/io.jl:55 [3] print(io::IO, dt::Date)     @ Dates ~/programs/julia/julia-1.9.4/share/julia/stdlib/v1.9/Dates/src/io.jl:714 [4] print(io::IO, dt::DateTime)     @ Dates ~/programs/julia/julia-1.9.4/share/julia/stdlib/v1.9/Dates/src/io.jl:705

As another example,suppose you have a DataFrameand want to use the groupby functionbut aren’t sure what other argumentsgroupby expects.Tab completion (or the methods function) can help:

julia> using DataFramesjulia> x = DataFrame(a = 1:3, b = rand(3));julia> gx = groupby(x, <tab>groupby(df::AbstractDataFrame, cols; sort, skipmissing) @ DataFrames ~/.julia/packages/DataFrames/58MUJ/src/groupeddataframe/groupeddataframe.jl:218

After running a package’s function,you might want to learn more aboutwhat the function returned.The typeof functionreturns the type of its input,and fieldnamesreturns a list of propertiesthat can be accessed:

julia> d = Dict("a" => 1, "b" => 2)Dict{String, Int64} with 2 entries:  "b" => 2  "a" => 1julia> x = collect(d)2-element Vector{Pair{String, Int64}}: "b" => 2 "a" => 1julia> typeof(x)Vector{Pair{String, Int64}} (alias for Array{Pair{String, Int64}, 1})julia> fieldnames(typeof(x[1]))(:first, :second)julia> x[1].first"b"

Tab completion can also be usedto list properties:

julia> x[1].<tab><tab>first   second

You can also see where in the type hierarchyan object’s type lieswith the supertype function:

julia> supertype(typeof(x))DenseVector{Pair{String, Int64}} (alias for DenseArray{Pair{String, Int64}, 1})

Picture of source code

Read Source Code

In addition to reading documentationand experimenting in the REPL,sometimes the best way to learn a packageis to read the source code directly.While that may seem daunting at first,remember that Julia is a high-level language,making it somewhat easy to read(at least after getting used to it).

There are two main benefitsof reading the source code:

  1. You get to see how the packagecreates and usescustom types and functions.
  2. Typically code is organizedin a logical manner,so you get to see what symbolslogically belong together.For example,a file that defines a typetypically will also defineconstructors for that typeand functions that operate on it.

If you see a function calland want to know what method will be called,the @which command in the REPL can help.For example:

julia> using DataFramesjulia> df = DataFrame(a = 1:3);julia> @which hcat(df, df)hcat(df1::AbstractDataFrame, df2::AbstractDataFrame; makeunique, copycols)     @ DataFrames ~/.julia/packages/DataFrames/58MUJ/src/abstractdataframe/abstractdataframe.jl:1608

Now we know the file and line numberwhere the hcat methodthat acts on two DataFrames is defined,and we can look at the source codeto learn more about what the method does.

If you don’t have any objects to work withbut know the types of the inputs,you can use the which method instead:

julia> which(hcat, (DataFrame, DataFrame))hcat(df1::AbstractDataFrame, df2::AbstractDataFrame; makeunique, copycols)     @ DataFrames ~/.julia/packages/DataFrames/58MUJ/src/abstractdataframe/abstractdataframe.jl:1608

Summary

That wraps up our discussionabout how to find useful packagesand how to discover and learn to usea package’s functionality.We listed a few tools for finding packagesand walked through some different methodsfor learning how to use a package,including looking at documentation,exploring in the REPL,and reading source code.

Do you have any tips or tricksfor learning how to use packages in Julia?Let us know in the comments below!

Now that you have a better ideaof how to learn Julia packages,move on to thenext post to learn about parallel processing in Julia!Or,feel free to take a lookat our other Julia tutorial posts.

Additional Links

Exploring Modules and Variable Scope in Julia Programming

By: Steven Whitaker

Re-posted from: https://glcs.hashnode.dev/modules-variable-scope

Julia is a relatively new,free, and open-source programming language.It has a syntaxsimilar to that of other popular programming languagessuch as MATLAB and Python,but it boasts being able to achieve C-like speeds.

One way to organize Julia codeis to split functionalityinto individual functions.When enough functions exist,it may become usefulto group the functions together,along with any relevant global variables,constants,and type definitions.Julia provides modules for this purpose.

Modules form the backbone of Julia packages,helping to organize codeand minimize namespace collisions.

In this post,we will learn aboutmodules in Julia,and we will discuss how to create and use them.Because modules each have their own global scope,we will also learn aboutscoping rules for variables.

This post assumes you already havea basic understanding of variables and functionsin Julia.You should also understand the differencebetween functions and methods.If you haven’t yet,check out our earlierpost on variables and functionsas well as our post on multiple dispatch,which explains the differencebetween functions and methods.

Modules

The syntax for creating a module is

module ModuleName# Code goes here.end

Here’s an example module:

module MyModuleusing Statisticsconst A = "A global constant"const B = [1, 2, 3]func(x) = println("A: ", A, "\nmean(B): ", mean(B), "\nx: ", x)export A, funcend

Let’s walk through this code.

  • First,the module loads another package:

    using Statistics

    Modules can load packages,just like we can do in the REPL.When a package is loaded in a module,the package is brought into the module’s namespace,meaning the loaded symbols(i.e., names referring to functions, types, constants, etc.)are not visible outside of the module.For example:

    julia> module StatsModule       using Statistics       endMain.StatsModulejulia> mean([1, 2, 3])ERROR: UndefVarError: `mean` not defined
  • Next,the module defines its own data and functionality:

    const A = "A global constant"const B = [1, 2, 3]func(x) = println("A: ", A, "\nmean(B): ", mean(B), "\nx: ", x)

    This is the codewe want to organize into a module.Typically,there are more lines of code,and they are saved in one or more separate filesthat are just included by the module via

    include("mycode.jl")
  • Finally,the module specifies some exports:
    export A, func

    Modules can export symbolsthat are then made availablewhen the module is loaded with using.

Using Modules

When a module is created,it can be referred toby its name,and any symbols in its namespacecan be accessedby prepending the module name,e.g., MyModule.func.(This is called a qualified name.)

julia> MyModuleMain.MyModulejulia> MyModule.func(1)A: A global constantmean(B): 2.0x: 1

If we want to make exported symbolsavailable without using a qualified name,we can load the module with using:

julia> using .MyModulejulia> A"A global constant"

(Here we note one differencebetween packages and modules:packages can be loaded with using PackageName,whereas modules need their nameto be prepended with a period,as seen above.)

After loading a module with using,unexported symbolsare not made directly available,but they can still be accessedvia a qualified name:

julia> BERROR: UndefVarError: `B` not definedjulia> MyModule.B3-element Vector{Int64}: 1 2 3

If we want to make an unexported symbolavailable without using a qualified name,we can explicitly load it:

julia> using .MyModule: Bjulia> B3-element Vector{Int64}: 1 2 3

import Statements

import is another keywordthat can be used to load modules and packages.

import .MyModule will make availablejust the name MyModule,not any exported symbols:

julia> import .MyModulejulia> func(false) # Error, even though `func` is exportedERROR: UndefVarError: `func` not definedjulia> MyModule.func(false) # Qualified names still workA: A global constantmean(B): 2.0x: false

Using vs. import

import also allows methodsto be added to a module’s functionswithout using a qualified name:

julia> import .MyModule: funcjulia> func() = println("Method 2")func (generic function with 2 methods)julia> func()Method 2julia> func("MyModule.func")A: A global constantmean(B): 2.0x: MyModule.func

For comparison,below are two similar examples that use using:

  1. julia> using .MyModule: funcjulia> func() = println("Method 2")ERROR: error in method definition: function MyModule.funcmust be explicitly imported to be extended

    Here,we learn that we cannot add a methodto a function from another modulewithout importing the function,as we did earlier,or referring to the functionwith its qualified name,as shown below:

    julia> using .MyModulejulia> MyModule.func() = println("Method 2")julia> func()Method 2julia> func("MyModule.func")A: A global constantmean(B): 2.0x: MyModule.func
  2. julia> using .MyModulejulia> func() = println("Method 2")func (generic function with 1 method)julia> func()Method 2julia> func("MyModule.func")ERROR: MethodError: no method matching func(::String)julia> MyModule.func("MyModule.func")A: A global constantmean(B): 2.0x: MyModule.func

    Here,we see that,even though func is exported from MyModule,we created a different func functionin the REPLbecause we did not import funcor use a qualified name.As a result,future uses of func from MyModulemust use its qualified name.

Adding methods to a module's function

Finally,import enables renaming symbols:

julia> import .MyModule as MMjulia> MM.A"A global constant"julia> import .MyModule: B as NEWNAMEjulia> NEWNAME3-element Vector{Int64}: 1 2 3

Common Modules

Now that we know how modules work,let’s learn about a few modulesthat every Julia programmer will come across:Main, Base and Core.

  • Main:It turns out that all Julia codeexecutes within a module.When Julia starts,a module named Main is created,and code that runsthat isn’t explicitly contained in a module(e.g., code in the REPL)is executed within Main.
  • Base:Much of Julia’s basic functionality,including functions like+, print, and getindex,is defined in a module named Base.This module is automatically loadedinto all modules(with few exceptions).
  • Core:Code that is considered built-in to Julia,i.e., code Julia needs to be able to function,lives in a module named Core.This module also is automatically loadedinto all modules(with even fewer exceptions).

Variable Scope

Variable scope refers to where in codea variable is accessible.It therefore has implicationsfor when two pieces of codecan use the same variable namewithout referring to the same thing.

There are three types of scopes in Julia:global scope, hard local scope, and soft local scope.We will discuss each of these in turn.

Global Scope

Symbols defined within a global scopecan be accessed within the global scopeand any local scopescontained in the global scope.

Each module defines its own global scope.Importantly,there is no universal global scope,meaning there is nowherewe can define, e.g., xand have x refer to the same thing everywhere,even across modules.

Also note that global scopes do not nest,in the sense that a nested modulecannot refer to a containing module’s global variable:

julia> module A           a = 1           module B               b = a # `a` is undefined here, even though `B` is nested within `A`           end       endERROR: UndefVarError: `a` not defined

Hard Local Scope

Symbols defined within a hard local scopecan be accessed within the local scopeand any contained local scopes.

Functions, let blocks, and comprehensionseach introduce a hard local scope.

In a hard local scope,variable assignment always assignsto a local variableunless the variable is explicitly declared as globalusing the global keyword:

julia> let           x = 1 # Assigns to a local variable `x`       end;julia> xERROR: UndefVarError: `x` not definedjulia> let           global x           x = 1 # Assigns to a global variable `x`       end;julia> x1

Furthermore,if there is a local variable, e.g., x,in an outer local scope,assignment to x in an inner local scopewill assign to the same xin the outer local scope:

julia> let           x = 0           for i = 1:10               s = x + i               x = s # `x` is the existing local variable, not a new one           end           x # 55, not 0       end55

In particular,it will not create a new local named xunless x is explicitly declared localin the inner scope.This is called shadowing,where names can be reusedto refer to different things:

julia> x = 1; # A global `x`julia> let           x = 2 # A local `x` shadowing the global `x`           let               local x               x = 3 # An inner local `x` shadowing the outer local `x`               @show x # Shows `x = 3`           end           @show x # Shows `x = 2`, not `x = 3`       end;x = 3x = 2julia> @show x; # Shows `x = 1`, not `x = 2` or `x = 3`x = 1

Another example of shadowing:

julia> x = 1; # A global `x`julia> function f(x)           x + 1 # This `x` refers to the local `x`, not the global one       end;julia> f(3) # Computes `3 + 1`, not `1 + 1`4

Soft Local Scope

for, while, and try blockseach introduce a soft local scope.

Soft local scope is the same as hard local scopeexcept in interactive contexts(e.g., when running code in the REPL)and when assigning to a variable(let’s call it x)while the following conditions are met:

  1. x is not already a local variable.
  2. All enclosing local scopes are soft.(To illustrate,nested for loops within the REPLwould satisfy this condition,while a for loop in a functionwould not.)
  3. A global variable x is defined.

In this case,the global variable x is assigned(as opposed to creating a new local variable x,as would be done in a hard local scope).(See the Julia documentationfor the rationale.)

Variable scope

Summary

In this post,we learned how to create and use modules in Juliafor organizing code.We also learned about Julia’s scoping rulesfor global, hard local, and soft local scopes.

How do you use modules in your code?Let us know in the comments below!

Have a better feel for howmodules and variable scope work?Move on to thenext post to learn how to learn new Julia packages!Or,feel free to take a lookat our other Julia tutorial posts.

Additional Links

Harnessing the Power of Multiple Dispatch in Julia Programming

By: Steven Whitaker

Re-posted from: https://glcs.hashnode.dev/multiple-dispatch

Julia is a relatively new,free, and open-source programming language.It has a syntaxsimilar to that of other popular programming languagessuch as MATLAB and Python,but it boasts being able to achieve C-like speeds.

One of the core design philosophies of Juliathat distinguishes itfrom a lot of other languagesis multiple dispatch.In essence,multiple dispatch allowswriting multiple implementationsof a single functionbased on different input types.But unlike single dispatchwhich relies on the static, compile-time typesof all but one of the inputs,multiple dispatch chooses what implementation to callusing the runtime types.

It makes writing generic code a cinch,allowing packages to work together seamlesslyand enabling a high degree of composability.

In this post,we will learn aboutmultiple dispatchin Julia.We will discuss how to utilize itand how it compares to single dispatch,typically usedin object-oriented languages.

This post assumes you already havea basic understandingof Julia’s type system.If you haven’t yet,check out our earlierpost to learn more.

Functions and Methods

To understand multiple dispatch,we first need to understandthe difference between functions and methods.

In Julia,a function essentially is just a name.This name is used to refer to the functionand to call the function.What a function does when calledis determined by the methodsassociated with the function.A method is an implementationof a functionfor a given set of input types.

Let’s illustrate this concept with code.

First, we will define a functionwith no methods,just to show that it can be done:

julia> function no_methods endno_methods (generic function with 0 methods)

When declaring a function, however,we typically also define an associated method:

julia> function func(x, y) # Method 1           x + y       endfunc (generic function with 1 method)

Here,we created the function funcand defined a methodthat adds the two function inputs.Call this method 1.

Now let’s add another method,method 2.To do so,we need to provide type annotationsto the input variables:

julia> function func(x::String, y::String) # Method 2           x * y       endfunc (generic function with 2 methods)

Notice that we didn’t createa new function with one method.Because we used the same function name,we added a method to the already existing function.

Let’s add one more method,method 3:

julia> function func(x::String, y::Integer) # Method 3           x^y       endfunc (generic function with 3 methods)

Methods can also have different numbers of arguments.The previous methods all had two arguments,so let’s create a method with three arguments,method 4:

julia> function func(x::String, y::Integer, z::Integer) # Method 4           func(x, y)[1:z]       endfunc (generic function with 4 methods)

Now let’s see multiple dispatch in action.

Multiple Dispatch

When func is called,Julia uses the types of the input argumentsto determine which method to dispatch to.In particular,the input types of multiple arguments(actually, all of them)are considered,hence the name multiple dispatch.If the types of the inputsmatch one of the method signatures,that method will be called.

Multiple dispatch

But what about func("hi", "there")?In this case,both inputs are Strings,so that matches method 2.But String is a subtype of Any,so the inputs also match method 1!In cases like thiswhere multiple methods match,the most specific method will be called.So, in this case,func("hi", "there") will dispatch to method 2because String is a more specific type than Any.

If no methods matchor if there is not one single most specific method,a MethodError will be thrown.

So,the methods we defined for funcwill be called in the following cases:

  • Method 1:When two inputs are givenand no more specific methods exist.Examples:

    julia> func(1, 2)3julia> func([3, 4], [5, 6])2-element Vector{Int64}:  8 10
  • Method 2:When two String inputs are given.Example:
    julia> func("hi ", "there")"hi there"
  • Method 3:When two inputs are given:first a String and then an Integer.(Remember, though, that abstract types,like Integer,cannot be instantiated,so when we say the second input is an Integer,we really mean it is of a typethat is a subtype of Integer.In other words,y isa Integer must evaluate to true.)Examples:

    julia> func("eat ", 3)"eat eat eat "julia> func("speak ", UInt32(2))"speak speak "
  • Method 4:When three inputs are given:first a String, then an Integer,and then another Integer.(Note that y and zdo not have to be of the same type,i.e., y could be an Int64,while z could be a Bool.)Examples:

    julia> func("repeat", 5, 6)"repeat"julia> func("first letter", 1, true)"f"

Now, you may be thinkingthat this just looks like method overloading.And you would be correctthat the above exampleshaven’t really demonstratedthe power of multiple dispatch.So,let’s compare multiple dispatchto single dispatch.

Multiple Dispatch vs Single Dispatch

Having many method definitionsis also possiblewith single dispatch.However,with single dispatch,the compile-time types of the inputs(except the first)are used to decide which method to call.

Single dispatch uses the runtime typeof just the first function argument,while multiple dispatch uses the runtime typesof all the function arguments.

Consider the following Java example:

abstract public class A{    public void func(A a)    {        System.out.println("Method AA");    }}public class B extends A{    public void func(A a)    {        System.out.println("Method BA");    }    public void func(B b)    {        System.out.println("Method BB");    }}public class Main{    public static void main(String[] args)    {        A ab = new B();        B b = new B();        ab.func(ab); // Method BA        b.func(b); // Method BB    }}

In this example,even though both ab and bare instantiated as B objects,b.func(ab) prints Method BA,whereas b.func(b) prints Method BB.This difference is becauseab and b have different compile-time types(also called declared or static types):ab is of type A,whereas b is of type B.

Now let’s compare this to Julia:

# From package PkgA.jlabstract type A endfunc(a1::A, a2::A) = println("Method AA")# From package PkgB.jlimport PkgA: A, funcstruct B <: A end # `B <: A` means `B` is a subtype of `A`func(b::B, a::A) = println("Method BA")func(b1::B, b2::B) = println("Method BB")# In the REPL or in a .jl fileusing PkgA, PkgBab::A = B()b::B = B()func(ab, ab) # Method BBfunc(b, b) # Method BB

In this case,both func(ab, ab) and func(b, b) print Method BB,despite annotating ab to be of type A,because both ab and bare of type B at runtime.

Again,multiple dispatch uses the runtime typesof all input argumentsto determine the method to call.

One of the main resulting benefitsis that composability is easy in Julia.

Composability

Composability is the abilityto use different pieces of code togetherto provide new functionality.

Puzzle pieces

First,we will see an instancewhere composability is difficult to achieve.

Let’s add to our Java example.We will add a class Cthat depends on Abut is independent of B:

public class C{    public static void run(A a1, A a2)    {        a1.func(a2);    }}

And we will add the following lines to main:

C.run(ab, ab); // Method BAC.run(b, b); // Method BA - `C.run` cannot call Method BB!

Notice that,even though b has a runtime type of B,C.run(b, b) sill prints out Method BA.This is because in class C,the static typeof the second input to run is A, not B.There are two ways to get run to print out Method BB:

  1. The author of class C changes their codeto add better support for using objects of type B,e.g., by adding additional methods to run.
  2. The author of class Main reimplements runto take into account the existence of class B.

In either case,code needs to be modifiedfor class B and class Cto compose well.That’s not ideal for composability.

Now let’s see how this works out in Julia.Suppose someone writes a package PkgC.jlthat contains the following:

using PkgArun(a1::A, a2::A) = func(a1, a2)

Then we can update our example:

# In the REPL or in a .jl fileusing PkgA, PkgB, PkgCab::A = B()b::B = B()PkgC.run(ab, ab) # Method BBPkgC.run(b, b) # Method BB

Notice that PkgC.run(b, b) prints Method BB,even though PkgC.jlknows nothing about PkgB.jl!No code changes were necessaryfor PkgB.jl and PkgC.jl to compose.That’s the power of multiple dispatch!

More Tips, Tricks, and General Advice

Now we will discuss various additional aspectsof multiple dispatch in Julia.

More Fine-Tuned Dispatching

Previously,we saw that the methodfunc(x::String, y::Integer, z::Integer)allowed y and z to be of different types(as long as both were Integers).

What if we want a methodwhere y and z need to beof the same type?We can do soby introducing a type parameter:

julia> function func(x::String, y::T, z::T) where T<:Integer # Method 5           println(x, ": y and z are both of type ", T)       endfunc (generic function with 5 methods)julia> func("welcome", 1, true) # Calls method 4"w"julia> func("welcome", 1, 1) # Calls method 5welcome: y and z are both of type Int64

This new method will be calledwhen y and z are both Integersof the same type.

Type parameters are also usefulfor constraining the typescontained in container types.For example,suppose we want a methodthat operates on an Arraythat contains only real numbers.The following method signatureaccomplishes this:

another_func(a::Array{T}) where T<:Real

If T isn’t used anywhere else,the following method signatureworks as well:

another_func(a::Array{<:Real})

Method Ambiguities

When adding different methods,it’s possible to introducemethod ambiguities,where, for a given set of input types,there is no single most specific method.

Recall that method 3 of funchad the following method signature:

func(x::String, y::Integer)

Adding the following methodwill introduce a method ambiguitywhen calling funcwith a String and an Int:

julia> function func(x, y::Int)           println(x, ": ", y)       endfunc (generic function with 6 methods)julia> func("hello", 2)ERROR: MethodError: func(::String, ::Int64) is ambiguous.Candidates:  func(x::String, y::Integer)    @ Main REPL[4]:1  func(x, y::Int64)    @ Main REPL[16]:1Possible fix, define  func(::String, ::Int64)

Notice the list of candidate methodsin the error message;neither is more specific than the other.

  • For the first argument,String is more specific than Any,so the first method is more specific.
  • For the second argument,Int64 is more specific than Integer,so the second method is more specific.

Method ambiguity

To overcome this issue,either remove one of the offending methodsor add the method suggestedin the error message.

Beware of Type Piracy

Another thing to be careful aboutwhen defining methodsis type piracy.Type piracy occurs whenyou add a method to a functionthat isn’t yours(i.e., that you didn’t originally define)using types that aren’t yours.

For example,defining simple_func(x::String)is fine because simple_funcis a new function,so it’s fine to use String,a type that we didn’t define,in the method signature.

Similarly,defining Base.sum(x::MyNewType)is fine because we defined(in this hypothetical scenario)MyNewType ourselves,so it’s fine to add this methodto Julia’s sum function.

But what’s wrong with adding a method likeBase.sum(x::Array)?The problem occurs when someone elsetries to use your code.If they call sum on any Arrays,your new method will be calledinstead of Julia’s built-in sum!That’s a nasty bug waiting to happen.

(If you want to live on the edge,try definingBase.:+(a::Int, b::Int) = error("pirate invasion")and then watch as Julia crashes fantastically.It turns out that being ableto correctly add two integersis necessary for Julia to work properly!)

Summary

In this post,we learned aboutmultiple dispatchin Julia.We discussed how to utilize itand how it compares to single dispatch,typically usedin object-oriented languages.

Do you have any further questionsabout multiple dispatch?Let us know in the comments below!

Want to learn more about Julia?Move on to thenext post to learn about modules and variable scope!Or,feel free to take a lookat out other Julia tutorial posts!

Additional Links