{ "metadata": { "language": "Julia", "name": "", "signature": "sha256:ae9d3a181058f7385d3142d3b3cd1a27b27bf4c6bb72dcee608b228cbe0ff792" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "(Dynamic) Multiple Dispatch in Julia" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Multiple dispatch](http://en.wikipedia.org/wiki/Multiple_dispatch) can be thought of as a generalization of **object-oriented** (OO) programming.\n", "\n", "In a typical OO language like Python, an object type (class) *owns* certain *methods* (functions), and are typically called via\n", "```\n", "object.method(arg1, arg2)\n", "```\n", "Depending on the type of `object`, the runtime system will *dispatch* to different `method` definitions.\n", "\n", "In Julia, the same call would be \"spelled\" differently:\n", "```\n", "method(object, arg1, arg2)\n", "```\n", "Spelled this way, you should notice something odd about OO programming: why is the *first* argument so special?\n", "\n", "Traditional OO programming corresponds to **single dispatch**: the runtime chooses `method` based on the type of the *first* argument only. Julia implements **multiple dispatch**: the runtime chooses `method` based on the types of *all* the arguments." ] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Example: Binary mathematical operators" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A classic example of the need for multiple dispatch is the case of binary math operators. If you compute `x * y`, the definition of the `*` function depends upon *both* the arguments, not just on `x`.\n", "\n", "Julia defines *many versions* of the `*` function:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "methods(*)" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "130 methods for generic function *:" ], "metadata": {}, "output_type": "pyout", "prompt_number": 1, "text": [ "# 130 methods for generic function \"*\":\n", "*(x::Bool,y::Bool) at bool.jl:41\n", "*{T<:Unsigned}(x::Bool,y::T<:Unsigned) at bool.jl:56\n", "*(x::Bool,z::Complex{T<:Real}) at complex.jl:116\n", "*{T<:Number}(x::Bool,y::T<:Number) at bool.jl:52\n", "*(z::Complex{T<:Real},x::Bool) at complex.jl:117\n", "*(y::Number,x::Bool) at bool.jl:58\n", "*{T,S}(A::Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Int64,Range{Int64})...,)}),B::Union(SubArray{S,2,A<:DenseArray{T,N},I<:(Union(Int64,Range{Int64})...,)},DenseArray{S,2})) at linalg/matmul.jl:116\n", "*(A::Union(Hermitian{T,S},Symmetric{T,S}),B::Union(Hermitian{T,S},Symmetric{T,S})) at linalg/symmetric.jl:44\n", "*(A::Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Int64,Range{Int64})...,)}),B::Union(Hermitian{T,S},Symmetric{T,S})) at linalg/symmetric.jl:45\n", "*(A::Tridiagonal{T},B::Triangular{T,S<:AbstractArray{T,2},UpLo,IsUnit}) at linalg/triangular.jl:181\n", "*{T<:Union(Float64,Complex{Float64},Complex{Float32},Float32),S<:Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Int64,Range{Int64})...,)}),UpLo,IsUnit}(A::Triangular{T<:Union(Float64,Complex{Float64},Complex{Float32},Float32),S<:Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Int64,Range{Int64})...,)}),UpLo,IsUnit},B::Triangular{T<:Union(Float64,Complex{Float64},Complex{Float32},Float32),S<:Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Int64,Range{Int64})...,)}),UpLo,IsUnit}) at linalg/triangular.jl:22\n", "*{TA,TB,SA<:AbstractArray{T,2},SB<:AbstractArray{T,2},UpLoA,UpLoB,IsUnitA,IsUnitB}(A::Triangular{TA,SA<:AbstractArray{T,2},UpLoA,IsUnitA},B::Triangular{TB,SB<:AbstractArray{T,2},UpLoB,IsUnitB}) at linalg/triangular.jl:184\n", "*(Da::Diagonal{T},Db::Diagonal{T}) at linalg/diagonal.jl:50\n", "*(A::Union(Diagonal{T},Tridiagonal{T},SymTridiagonal{T},Bidiagonal{T},Triangular{T,S<:AbstractArray{T,2},UpLo,IsUnit}),B::Union(Diagonal{T},Tridiagonal{T},SymTridiagonal{T},Bidiagonal{T},Triangular{T,S<:AbstractArray{T,2},UpLo,IsUnit})) at linalg/bidiag.jl:114\n", "*{T<:Union(Int32,Int16,Int8)}(x::T<:Union(Int32,Int16,Int8),y::T<:Union(Int32,Int16,Int8)) at int.jl:18\n", "*{T<:Union(Uint16,Uint32,Uint8)}(x::T<:Union(Uint16,Uint32,Uint8),y::T<:Union(Uint16,Uint32,Uint8)) at int.jl:22\n", "*(x::Int64,y::Int64) at int.jl:47\n", "*(x::Uint64,y::Uint64) at int.jl:48\n", "*(x::Int128,y::Int128) at int.jl:586\n", "*(x::Uint128,y::Uint128) at int.jl:587\n", "*(x::Float32,y::Float32) at float.jl:123\n", "*(x::Float64,y::Float64) at float.jl:124\n", "*(z::Complex{T<:Real},w::Complex{T<:Real}) at complex.jl:112\n", "*(x::Real,z::Complex{T<:Real}) at complex.jl:118\n", "*(z::Complex{T<:Real},x::Real) at complex.jl:119\n", "*(x::Rational{T<:Integer},y::Rational{T<:Integer}) at rational.jl:118\n", "*(a::Float16,b::Float16) at float16.jl:132\n", "*(x::BigInt,y::BigInt) at gmp.jl:194\n", "*(a::BigInt,b::BigInt,c::BigInt) at gmp.jl:217\n", "*(a::BigInt,b::BigInt,c::BigInt,d::BigInt) at gmp.jl:223\n", "*(a::BigInt,b::BigInt,c::BigInt,d::BigInt,e::BigInt) at gmp.jl:230\n", "*(x::BigInt,c::Uint64) at gmp.jl:268\n", "*(c::Uint64,x::BigInt) at gmp.jl:272\n", "*(c::Union(Uint16,Uint32,Uint8,Uint64),x::BigInt) at gmp.jl:273\n", "*(x::BigInt,c::Union(Uint16,Uint32,Uint8,Uint64)) at gmp.jl:274\n", "*(x::BigInt,c::Int64) at gmp.jl:276\n", "*(c::Int64,x::BigInt) at gmp.jl:280\n", "*(x::BigInt,c::Union(Int32,Int64,Int16,Int8)) at gmp.jl:281\n", "*(c::Union(Int32,Int64,Int16,Int8),x::BigInt) at gmp.jl:282\n", "*(x::BigFloat,c::Uint64) at mpfr.jl:239\n", "*(c::Uint64,x::BigFloat) at mpfr.jl:243\n", "*(c::Union(Uint16,Uint32,Uint8,Uint64),x::BigFloat) at mpfr.jl:244\n", "*(x::BigFloat,c::Union(Uint16,Uint32,Uint8,Uint64)) at mpfr.jl:245\n", "*(x::BigFloat,c::Int64) at mpfr.jl:249\n", "*(c::Int64,x::BigFloat) at mpfr.jl:253\n", "*(x::BigFloat,c::Union(Int32,Int64,Int16,Int8)) at mpfr.jl:254\n", "*(c::Union(Int32,Int64,Int16,Int8),x::BigFloat) at mpfr.jl:255\n", "*(x::BigFloat,c::Float64) at mpfr.jl:259\n", "*(c::Float64,x::BigFloat) at mpfr.jl:263\n", "*(c::Float32,x::BigFloat) at mpfr.jl:264\n", "*(x::BigFloat,c::Float32) at mpfr.jl:265\n", "*(x::BigFloat,c::BigInt) at mpfr.jl:269\n", "*(c::BigInt,x::BigFloat) at mpfr.jl:273\n", "*(x::BigFloat,y::BigFloat) at mpfr.jl:328\n", "*(a::BigFloat,b::BigFloat,c::BigFloat) at mpfr.jl:339\n", "*(a::BigFloat,b::BigFloat,c::BigFloat,d::BigFloat) at mpfr.jl:345\n", "*(a::BigFloat,b::BigFloat,c::BigFloat,d::BigFloat,e::BigFloat) at mpfr.jl:352\n", "*(x::MathConst{sym},y::MathConst{sym}) at constants.jl:23\n", "*{T<:Number}(x::T<:Number,y::T<:Number) at promotion.jl:189\n", "*(x::Number,y::Number) at promotion.jl:159\n", "*{T<:Number}(x::T<:Number,D::Diagonal{T}) at linalg/diagonal.jl:47\n", "*(s::Real,p::Vec2) at graphics.jl:64\n", "*(s::Real,bb::BoundingBox) at graphics.jl:151\n", "*{P<:Period}(x::Real,y::P<:Period) at dates/periods.jl:58\n", "*(x::Number) at operators.jl:72\n", "*{T<:Union(Float64,Complex{Float64},Complex{Float32},Float32),S}(A::Union(SubArray{T<:Union(Float64,Complex{Float64},Complex{Float32},Float32),2,A<:DenseArray{T,N},I<:(Union(Int64,Range{Int64})...,)},DenseArray{T<:Union(Float64,Complex{Float64},Complex{Float32},Float32),2}),x::Union(SubArray{S,1,A<:DenseArray{T,N},I<:(Union(Int64,Range{Int64})...,)},DenseArray{S,1})) at linalg/matmul.jl:67\n", "*(A::SymTridiagonal{T},B::Number) at linalg/tridiag.jl:47\n", "*(A::Tridiagonal{T},B::Number) at linalg/tridiag.jl:209\n", "*{TX,TvA,TiA}(X::Tridiagonal{TX},A::SparseMatrixCSC{TvA,TiA}) at linalg/sparse.jl:128\n", "*(A::Tridiagonal{T},B::Union(AbstractArray{T,2},AbstractArray{T,1})) at linalg/tridiag.jl:251\n", "*{T<:Union(Float64,Complex{Float64},Complex{Float32},Float32),S<:Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Int64,Range{Int64})...,)}),UpLo,IsUnit}(A::Triangular{T<:Union(Float64,Complex{Float64},Complex{Float32},Float32),S<:Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Int64,Range{Int64})...,)}),UpLo,IsUnit},B::Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Int64,Range{Int64})...,)},SubArray{T,1,A<:DenseArray{T,N},I<:(Union(Int64,Range{Int64})...,)},DenseArray{T,1})) at linalg/triangular.jl:23\n", "*{T,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit},x::Number) at linalg/triangular.jl:156\n", "*{TvA,TiA}(X::Triangular{T,S<:AbstractArray{T,2},UpLo,IsUnit},A::SparseMatrixCSC{TvA,TiA}) at linalg/sparse.jl:129\n", "*{T,S<:AbstractArray{T,2},UpLo,IsUnit}(A::Triangular{T,S<:AbstractArray{T,2},UpLo,IsUnit},B::Union(AbstractArray{T,2},AbstractArray{T,1})) at linalg/triangular.jl:185\n", "*{TA,Tb}(A::Union(QRCompactWYQ{TA},QRPackedQ{TA}),b::Union(DenseArray{Tb,1},SubArray{Tb,1,A<:DenseArray{T,N},I<:(Union(Int64,Range{Int64})...,)})) at linalg/factorization.jl:158\n", "*{TA,TB}(A::Union(QRCompactWYQ{TA},QRPackedQ{TA}),B::Union(DenseArray{TB,2},SubArray{TB,2,A<:DenseArray{T,N},I<:(Union(Int64,Range{Int64})...,)})) at linalg/factorization.jl:164\n", "*{TA,TQ,N}(A::Union(SubArray{TA,N,A<:DenseArray{T,N},I<:(Union(Int64,Range{Int64})...,)},DenseArray{TA,N}),Q::Union(QRCompactWYQ{TQ},QRPackedQ{TQ})) at linalg/factorization.jl:226\n", "*(W::Woodbury{T},B::Union(DenseArray{T,2},SubArray{T,2,A<:DenseArray{T,N},I<:(Union(Int64,Range{Int64})...,)},SubArray{T,1,A<:DenseArray{T,N},I<:(Union(Int64,Range{Int64})...,)},DenseArray{T,1})) at linalg/woodbury.jl:70\n", "*{T<:Number}(D::Diagonal{T},x::T<:Number) at linalg/diagonal.jl:48\n", "*(D::Diagonal{T},V::Array{T,1}) at linalg/diagonal.jl:51\n", "*(A::Array{T,2},D::Diagonal{T}) at linalg/diagonal.jl:52\n", "*(D::Diagonal{T},A::Array{T,2}) at linalg/diagonal.jl:53\n", "*(A::Bidiagonal{T},B::Number) at linalg/bidiag.jl:108\n", "*{T}(A::Bidiagonal{T},B::AbstractArray{T,1}) at linalg/bidiag.jl:119\n", "*(B::BitArray{2},J::UniformScaling{T<:Number}) at linalg/uniformscaling.jl:68\n", "*(S::SparseMatrixCSC{Tv,Ti<:Integer},J::UniformScaling{T<:Number}) at linalg/uniformscaling.jl:70\n", "*{T}(G1::Givens{T},G2::Givens{T}) at linalg/givens.jl:197\n", "*(G::Givens{T},B::BitArray{2}) at linalg/givens.jl:198\n", "*{TBf,TBi}(G::Givens{T},B::SparseMatrixCSC{TBf,TBi}) at linalg/givens.jl:199\n", "*(R::Union(Givens{T},Rotation{T}),A::AbstractArray{T,2}) at linalg/givens.jl:200\n", "*{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti},B::SparseMatrixCSC{Tv,Ti}) at linalg/sparse.jl:143\n", "*{TvA,TiA,TvB,TiB}(A::SparseMatrixCSC{TvA,TiA},B::SparseMatrixCSC{TvB,TiB}) at linalg/sparse.jl:4\n", "*{TvA,TiA}(A::SparseMatrixCSC{TvA,TiA},X::BitArray{1}) at linalg/sparse.jl:11\n", "*{TvA,TiA}(A::SparseMatrixCSC{TvA,TiA},X::BitArray{2}) at linalg/sparse.jl:109\n", "*{TA,S,Tx}(A::SparseMatrixCSC{TA,S},x::AbstractArray{Tx,1}) at linalg/sparse.jl:32\n", "*(X::BitArray{1},A::SparseMatrixCSC{Tv,Ti<:Integer}) at linalg/sparse.jl:95\n", "*{TvA,TiA,TX}(A::SparseMatrixCSC{TvA,TiA},X::AbstractArray{TX,2}) at linalg/sparse.jl:111\n", "*{TvA,TiA}(X::BitArray{2},A::SparseMatrixCSC{TvA,TiA}) at linalg/sparse.jl:126\n", "*{T<:Number}(x::AbstractArray{T<:Number,2}) at abstractarray.jl:363\n", "*(B::Number,A::SymTridiagonal{T}) at linalg/tridiag.jl:48\n", "*(B::Number,A::Tridiagonal{T}) at linalg/tridiag.jl:210\n", "*{T,S,UpLo,IsUnit}(x::Number,A::Triangular{T,S,UpLo,IsUnit}) at linalg/triangular.jl:165\n", "*(B::Number,A::Bidiagonal{T}) at linalg/bidiag.jl:109\n", "*(A::Number,B::AbstractArray{T,N}) at abstractarray.jl:367\n", "*(A::AbstractArray{T,N},B::Number) at abstractarray.jl:368\n", "*(s::String...) at string.jl:76\n", "*{T,S<:AbstractArray{T,2},UpLo,IsUnit}(A::AbstractArray{T,2},B::Triangular{T,S<:AbstractArray{T,2},UpLo,IsUnit}) at linalg/triangular.jl:186\n", "*{TX,TvA,TiA}(X::AbstractArray{TX,2},A::SparseMatrixCSC{TvA,TiA}) at linalg/sparse.jl:131\n", "*{T,S}(A::AbstractArray{T,2},x::AbstractArray{S,1}) at linalg/matmul.jl:71\n", "*{T1,T2}(X::AbstractArray{T1,1},A::SparseMatrixCSC{T2,Ti<:Integer}) at linalg/sparse.jl:99\n", "*(A::AbstractArray{T,1},B::AbstractArray{T,2}) at linalg/matmul.jl:74\n", "*(J1::UniformScaling{T<:Number},J2::UniformScaling{T<:Number}) at linalg/uniformscaling.jl:67\n", "*(J::UniformScaling{T<:Number},B::BitArray{2}) at linalg/uniformscaling.jl:69\n", "*{Tv,Ti}(J::UniformScaling{T<:Number},S::SparseMatrixCSC{Tv,Ti}) at linalg/uniformscaling.jl:71\n", "*(A::AbstractArray{T,2},J::UniformScaling{T<:Number}) at linalg/uniformscaling.jl:72\n", "*(J::UniformScaling{T<:Number},A::Union(AbstractArray{T,2},AbstractArray{T,1})) at linalg/uniformscaling.jl:73\n", "*(x::Number,J::UniformScaling{T<:Number}) at linalg/uniformscaling.jl:75\n", "*(J::UniformScaling{T<:Number},x::Number) at linalg/uniformscaling.jl:76\n", "*{Tv<:Union(Float64,Complex{Float64},Complex{Float32},Float32)}(A::CholmodSparse{Tv<:Union(Float64,Complex{Float64},Complex{Float32},Float32),Int32},B::CholmodSparse{Tv<:Union(Float64,Complex{Float64},Complex{Float32},Float32),Int32}) at linalg/cholmod.jl:478\n", "*{Tv<:Union(Float64,Complex{Float64},Complex{Float32},Float32)}(A::CholmodSparse{Tv<:Union(Float64,Complex{Float64},Complex{Float32},Float32),Int64},B::CholmodSparse{Tv<:Union(Float64,Complex{Float64},Complex{Float32},Float32),Int64}) at linalg/cholmod.jl:478\n", "*{Tv<:Union(Float64,Complex{Float64},Complex{Float32},Float32)}(A::CholmodSparse{Tv<:Union(Float64,Complex{Float64},Complex{Float32},Float32),Ti<:Union(Int32,Int64)},B::CholmodDense{Tv<:Union(Float64,Complex{Float64},Complex{Float32},Float32)}) at linalg/cholmod.jl:835\n", "*{Tv<:Union(Float64,Complex{Float64},Complex{Float32},Float32)}(A::CholmodSparse{Tv<:Union(Float64,Complex{Float64},Complex{Float32},Float32),Ti<:Union(Int32,Int64)},B::Union(Array{Tv<:Union(Float64,Complex{Float64},Complex{Float32},Float32),1},Array{Tv<:Union(Float64,Complex{Float64},Complex{Float32},Float32),2})) at linalg/cholmod.jl:836\n", "*(p::Vec2,s::Real) at graphics.jl:62\n", "*(bb::BoundingBox,s::Real) at graphics.jl:147\n", "*{P<:Period}(x::P<:Period,y::P<:Period) at dates/periods.jl:55\n", "*{P<:Period}(x::P<:Period,y::Real) at dates/periods.jl:57\n", "*(x::Instant,y::Instant) at dates/arithmetic.jl:3\n", "*(x::TimeType,y::TimeType) at dates/arithmetic.jl:10\n", "*(a,b,c) at operators.jl:82\n", "*(a,b,c,xs...) at operators.jl:83" ] } ], "prompt_number": 1 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can add new methods to a given function at any time. The methods don't \"belong\" to a particular type, and aren't part of the type's definition.\n", "\n", "For example, string concatenation in Julia is done via `*`:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "\"hello\" * \"world\"" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 2, "text": [ "\"helloworld\"" ] } ], "prompt_number": 2 }, { "cell_type": "code", "collapsed": false, "input": [ "\"hello\" + \"world\"" ], "language": "python", "metadata": {}, "outputs": [ { "ename": "LoadError", "evalue": "`+` has no method matching +(::ASCIIString, ::ASCIIString)\nwhile loading In[3], in expression starting on line 1", "output_type": "pyerr", "traceback": [ "`+` has no method matching +(::ASCIIString, ::ASCIIString)\nwhile loading In[3], in expression starting on line 1", "" ] } ], "prompt_number": 3 }, { "cell_type": "markdown", "metadata": {}, "source": [ "But we can easily extend `+` to support a concatenation for strings, if we want:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "import Base.+ # we must import a method to add methods (as opposed to replacing it)\n", "+(x::String, y::String) = x * \" \" * y" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 4, "text": [ "+ (generic function with 147 methods)" ] } ], "prompt_number": 4 }, { "cell_type": "code", "collapsed": false, "input": [ "\"hello\" + \"world\"" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 5, "text": [ "\"hello world\"" ] } ], "prompt_number": 5 }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Not the same as C++ overloading: Dynamic vs. static" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This may look a lot like function overloading in languages like C++. The difference is that C++'s overloading is **static** (= *dispatch at compile-time*), whereas Julia's overloading is dynamic (= *dispatch at run-time*), like OO polymorphism.\n", "\n", "For example, now that we've defined `+`, we can use strings with any previously defined function that requires a `+` operation, like `sum` (summation):" ] }, { "cell_type": "code", "collapsed": false, "input": [ "sum([\"The\", \"quick\", \"brown\", \"fox\", \"jumped\", \"over\", \"the\", \"lazy\", \"dog.\"])" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 6, "text": [ "\"The quick brown fox jumped over the lazy dog.\"" ] } ], "prompt_number": 6 }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Type declarations are \"function filters\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Type declarations are *not* required for performance \u2014 Julia automatically specializes a function on its argument types during compilation. They act like **filters**, allowing us to specify *which functions are used when*.\n", "\n", "Without this, in a language like Python, you sometimes have to write *manual function filters* like this example from Matplotlib's [quiver.py](https://github.com/matplotlib/matplotlib/blob/43150ef012e405de5c2fb7b779d1bffa77f7af81/lib/matplotlib/quiver.py):\n", "```python\n", "def _parse_args(*args):\n", " X, Y, U, V, C = [None] * 5\n", " args = list(args)\n", " # The use of atleast_1d allows for handling scalar arguments while also\n", " # keeping masked arrays\n", " if len(args) == 3 or len(args) == 5:\n", " C = np.atleast_1d(args.pop(-1))\n", " V = np.atleast_1d(args.pop(-1))\n", " U = np.atleast_1d(args.pop(-1))\n", " if U.ndim == 1:\n", " nr, nc = 1, U.shape[0]\n", " else:\n", " nr, nc = U.shape\n", " if len(args) == 2: # remaining after removing U,V,C\n", " X, Y = [np.array(a).ravel() for a in args]\n", " if len(X) == nc and len(Y) == nr:\n", " X, Y = [a.ravel() for a in np.meshgrid(X, Y)]\n", " else:\n", " indexgrid = np.meshgrid(np.arange(nc), np.arange(nr))\n", " X, Y = [np.ravel(a) for a in indexgrid]\n", " return X, Y, U, V, C\n", "```\n", "In Julia, you could define different methods for differing numbers of arguments, arrays vs. scalars, etcetera (all eventually calling a single lower-level function to do the work once the arguments have been transformed)." ] } ], "metadata": {} } ] }