Coming from the Python ecosystem, the range of packages available in Julia can seem somewhat limited. This is offset, however, by the ease of calling out to packages written in other languages from within Julia.
In particular, Python interoperability is very easy, thanks to the PyCall package. This is loaded with
In [1]:
    
using PyCall
    
PyCall has a high-level interface that is designed so that the "transport" between Julia and Python is transparent from the user's point of view. For example, to import the Python math module, we do
In [2]:
    
@pyimport math
    
In [3]:
    
macroexpand(:(@pyimport math))
    
    Out[3]:
We can now mix and match Python calls, labelled by the math. qualifier, and Julia calls:
In [3]:
    
math.sin(0.3*math.pi) - sin(0.3*pi)
    
    Out[3]:
Array objects are automatically converted:
In [9]:
    
@pyimport numpy.random as nprandom
nprandom.rand(3,4)
    
    Out[9]:
Let's define a Julia function:
In [4]:
    
objective = x -> cos(x) - x
    
    Out[4]:
This is the Julia syntax for an anonymous function (like lambda in Python).
In [5]:
    
objective(3)
    
    Out[5]:
We can pass this Julia function to a Python module:
In [6]:
    
?so.newton
    
    
In [7]:
    
@pyimport scipy.optimize as so
so.newton(objective, 1)
    
    Out[7]:
The main difference from Python is how to access member elements of Python structures.
Julia has ODE solvers in the ODE.jl and Sundials.jl packages. But we can also call Python solvers:
In [11]:
    
@pyimport scipy.integrate as integrate
    
In [12]:
    
f(x,t) = -x
    
    Out[12]:
In [13]:
    
t = [0:0.1:10];
    
In [14]:
    
soln = integrate.odeint(f, 1, t);
    
In [9]:
    
using PyPlot
    
Note that the PyPlot module provides a higher-level wrapper than PyCall around matplotlib.
In [15]:
    
plot(t, soln)
plot(t, exp(-t))
    
    
    Out[15]:
Accessing fields (properties) and methods of Python objects uses the obj.a and obj.b() syntax, where obj is a Python object.
However, currently the obj.b syntax in Julia is restricted to accessing fields of Julia composite types.
For this reason, to access fields and methods of Python objects via PyCall, it is necessary to use the syntax
obj[:a] for fields, and
obj[:a]() for methods
Here, we are using the Julia syntax :a to mean the Julia symbol a.
The high-level PyCall interface is built on top of a lower-level interface which deals with the "transport" of objects between Python and Julia, based on a PyObject Julia type that wraps PyObject* in C, and represents a reference to a Python object.
In [16]:
    
PyObject(3)
    
    Out[16]:
In [17]:
    
x = rand(5, 5)
    
    Out[17]:
In [18]:
    
xx = PyObject(x)
    
    Out[18]:
In [19]:
    
typeof(xx)
    
    Out[19]:
In [20]:
    
names(xx)
    
    Out[20]:
In [21]:
    
xx.o
    
    Out[21]:
In [11]:
    
# xx.shape  in Python becomes:
xx[:shape]
    
    Out[11]:
In [14]:
    
typeof(ans)  # the result has already been translated back into a Julia object
    
    Out[14]:
Julia arrays are passed into Python without a copy. By default the resulting Python array is copied when a result is requested in Julia; this can be avoided at a lower level using pycall and PyArray.
Exercise: Use your favourite Python package from Julia!
Julia has a simple way to call C and Fortran functions in shared libraries, via the ccall function.
In [25]:
    
help("ccall")
    
    
We see that we must specify:
the return type of the function
the argument types that the function accepts, as a tuple
and the arguments themselves
A simple example is to call the clock function:
In [4]:
    
t = ccall( (:clock, "libc"), Int32, ())
    
    Out[4]:
In [10]:
    
t
    
    Out[10]:
In [11]:
    
typeof(t)
    
    Out[11]:
In [36]:
    
help("ccall")
    
    
In [ ]:
    
Clong
    
In [12]:
    
path = ccall( (:getenv, "libc"), Ptr{Uint8}, (Ptr{Uint8},), "PATH")
    
    Out[12]:
Here, Ptr denotes a pointer to the given type.
In [31]:
    
path
    
    Out[31]:
In [13]:
    
bytestring(path)
    
    Out[13]:
In [34]:
    
?bytestring
    
    
In [37]:
    
Ptr
    
    Out[37]:
In [38]:
    
methods(Ptr)
    
    Out[38]:
In [39]:
    
methodswith(Ptr)
    
    Out[39]:
In [1]:
    
type F
    f::Function
    x::Float64
end
    
In [2]:
    
hello(x) = x^2
    
    Out[2]:
In [6]:
    
myfunc = F(hello, 3.5)
    
    Out[6]:
In [4]:
    
myfunc
    
    
In [27]:
    
myfunc.f
    
    Out[27]:
In [8]:
    
myfunc.f(myfunc.x)
    
    Out[8]:
In [ ]: