Skip to content

Commit

Permalink
More edits
Browse files Browse the repository at this point in the history
- change float environment of dispatch statistics table from figure to float
- move 'symbolic programming' subsection to be second to last ('implications' feels like a more natural ending)
- more precise subsection headings
- remove comments addressing points already made and rephrased
- tighten spacing of "(n-1)-array"
- less repetitive phrasing
- eliminate most parenthetical comments
- hyphenation
- cite issue JuliaLang/julia#5949
  • Loading branch information
jiahao committed Apr 12, 2014
1 parent 2f699a3 commit a1d3755
Showing 1 changed file with 77 additions and 103 deletions.
180 changes: 77 additions & 103 deletions main.tex
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@

\begin{abstract}

Arrays are such a rich and fundamental data type that they tend to be built in to
Arrays are such a rich and fundamental data type that they tend to be built into
a language, either in the compiler or in a large low-level library.
It would be better to define this functionality at the user level, providing more
Alternatively, defining this functionality at the user level provides greater

This comment has been minimized.

Copy link
@JeffBezanson

JeffBezanson Apr 14, 2014

Collaborator

I find this a bit weak. I'm not afraid to make the value judgment that this is better.

flexibility for application domains not envisioned by the language designer.
Only a few languages, such as C++ and Haskell, provide the necessary power to define
$n$-dimensional arrays, but these systems rely on compile-time abstraction,
sacrificing some amount of flexibility.
sacrificing some flexibility.
In contrast, dynamic languages make it straightforward for the user to define any
behavior they might want, but at the possible expense of performance.

Expand Down Expand Up @@ -121,7 +121,7 @@ \section{Array libraries}
\cite{Keller:2010rs,Lippmeier:2011ep, Lippmeier:2012gp}. These libraries
leverage the static semantics of their host languages to
define $n$-arrays inductively as the outer product of a 1-array with an
$(n-1)$-array \cite{Bavestrelli:2000ct}.
$(n\!\!-\!\!1)$-array \cite{Bavestrelli:2000ct}.
Array libraries typically handle dimensions recursively, one at a time;
knowing array ranks at compile-time allows the compiler to infer the amount
of storage needed for the shape information, and unroll index computations fully.
Expand Down Expand Up @@ -157,10 +157,10 @@ \subsection{Static tradeoffs}
%\cite{Garcia:2005ma, Lippmeier:2011ep}, which engenders much repetition in the
%codebase \cite{Lippmeier:2012gp}.

Some applications call for semantics that are not amenable
to static analysis. Certain applications require arrays whose ranks are known
Furthermore, there are applications which call for semantics that are not amenable

This comment has been minimized.

Copy link
@JeffBezanson

JeffBezanson Apr 14, 2014

Collaborator

The original wording was more active.

to static analysis. Some may require arrays whose ranks are known
only at run-time, and thus the data structures in these programs cannot be
guaranteed to fit in a constant amount of memory. Some programs may
guaranteed to fit in a constant amount of memory. Others may
wish to dynamically dispatch on the rank of an array, a need which a
library must anticipate by providing appropriate virtual methods.

Expand Down Expand Up @@ -248,7 +248,7 @@ \section{Julia arrays}
Julia\cite{Bezanson:2012jf} is dynamically typed and is based on dynamic
multiple dispatch. However, the language and its standard library have been
designed to take advantage of the possibility of static analysis
(Figure~\ref{fig:langdesign}), especially dataflow type inference. Such type
(Figure~\ref{fig:langdesign}), especially dataflow type inference \cite{Cousot:1977, kaplanullman}. Such type
inference, when combined with multiple dispatch, allows users and library
writers to produce a rich array of specialized methods to handle different
cases performantly. In this section we describe how this language feature
Expand Down Expand Up @@ -327,16 +327,12 @@ \subsection{The need for flexibility}
% matrix to become a vector by dropping a dimension.

In practice we may have to reach a consensus on what rules to use, but this
should not be forced by technical limitations.
should not be forced by technical limitations. The rules used by the Julia

This comment has been minimized.

Copy link
@JeffBezanson

JeffBezanson Apr 14, 2014

Collaborator

I think the addition here is a bit redundant. We say all this stuff many times.

base library are defined in a single place in the codebase, thus allowing for
the rules to be changed easily if necessary. Using multiple dispatch semantics
allows us this flexibility.

%% Instead of the compiler analyzing indexing expressions and determining an
%% answer using hard-coded logic, we would rather implement the behavior in
%% libraries, so that different kinds of arrays may be defined, or so that rules
%% of similar complexity may be defined for other kinds of objects. But these
%% kinds of rules are unusually diffMation will still have the flexibility to modify it to their
%needs.

\subsection{Multiple dispatch}
\subsection{Multiple dispatch in Julia}

Multiple dispatch (also known as generic functions, or multi-methods) is an
object-oriented paradigm where methods are defined on combinations of data
Expand All @@ -353,8 +349,8 @@ \subsection{Multiple dispatch}
\caption{\label{fig:dispatch}Class-based method dispatch (above) vs. multiple dispatch (below).}
\end{figure}

One can invent examples where multiple dispatch is useful in classic OO domains
such as GUI programming. A method for drawing a label onto a button might
Multiple dispatch is traditionally used in object-oriented domains

This comment has been minimized.

Copy link
@JeffBezanson

JeffBezanson Apr 14, 2014

Collaborator

My wording was more colorful. This is a bit boring.

such as GUI programming. For example, a method for drawing a label onto a button might
look like this in Julia syntax:

\begin{minipage}{\linewidth}
Expand All @@ -366,7 +362,7 @@ \subsection{Multiple dispatch}
\end{verbatim}
\end{minipage}

In a numerical setting, binary operators are ubiquitous and we can easily imagine
Another use case for multiple dispatch is binary numerical operators. We can easily imagine

This comment has been minimized.

Copy link
@JeffBezanson

JeffBezanson Apr 14, 2014

Collaborator

This isn't just another use case. It has a different character where it is much more natural to consider multiple arguments at once.

needing to define special behavior for some combination of two arguments:

\begin{verbatim}
Expand All @@ -377,21 +373,17 @@ \subsection{Multiple dispatch}
\emph{three} different types at once? Indeed, most language designers and
programmers seem to have concluded that multiple dispatch might be nice, but is
not essential, and the feature is not often used \cite{Muschevici:2008}.
%TODO: cite statistic from study of multiple dispatch showing it is lightly used
Perhaps the few cases that seem to need it can be handled using tricks like
Python's \code{\_\_add\_\_} and \code{\_\_radd\_\_} methods.

However, multiple dispatch looks quite different in the context of technical
computing. To a large extent, technical computing is characterized
by the prevalence of highly polymorphic, multi-argument operators. In many
cases, these functions are even more complex than the 2-argument
examples above might indicate. To handle these, we have added a few
features that are not always found in multiple dispatch implementations.

For this paper, perhaps the most important of these is support for
variadic methods. Combining multiple dispatch and variadic methods
seems straightforward
enough, and yet it permits surprisingly powerful definitions. For example,
In contrast to the simple two-argument examples above, multiple dispatch in
technical computing contexts must handle many polymorphic, multi-argument
operators. To handle these complicated cases, Julia's multiple dispatch
contains some features that are not always found in other implementations.

For array semantics, support for variadic methods is perhaps the most
important such feature. Combining multiple dispatch and variadic methods

This comment has been minimized.

Copy link
@JeffBezanson

JeffBezanson Apr 14, 2014

Collaborator

This is not the right sentiment. What we need to convey is that multiple dispatch and variadic functions, each by themselves are very standard features that everybody knows about, so combining them seems easy and obvious. But in fact it is difficult and powerful. It only seems simple.

is straightforward, yet permits surprisingly powerful definitions. For example,
consider a variadic \code{sum} function that adds up its arguments. We could
write the following two methods for it (note that in Julia, \code{Real} is
the abstract supertype of all real number types, and \code{Integer} is the
Expand All @@ -406,15 +398,15 @@ \subsection{Multiple dispatch}
arguments (currently, Julia only allows this at the end of a method signature).
In the first case, all arguments are integers and so we can use a naive
summation algorithm. In the second case, we know that at least one argument
is not an integer, so we might want to use some form of compensated
is not an integer (otherwise the first method would be used), so we might want to use some form of compensated
summation instead. Notice that these modest method signatures
capture a subtle property (at least one argument is non-integer)
\emph{declaratively}: there is no need to explicitly loop over the arguments
capture a subtle property (at least one argument is non-integral)
\emph{declaratively}, without needing to explicitly loop over the arguments
to examine their types. The signatures also provide useful type information:
at the very least, a compiler could know that all argument values inside
the first method are of type \code{Integer}. Yet the type annotations
are not redundant: they are necessary to specify the desired behavior. There
is also no loss of flexibility: \code{sum} can be called with any combination
are not redundant, but are necessary to specify the desired behavior. There
is also no loss of flexibility, since \code{sum} can be called with any combination
of number types, as users of dynamic technical computing languages would expect.

While the author of these definitions does not write a loop to examine
Expand All @@ -426,12 +418,12 @@ \subsection{Multiple dispatch}
just an optimization, but in practice it has a profound impact on how code
is written.

\subsection{\code{index\_shape}}
\subsection{Argument tuple transformations in \code{index\_shape}}

This comment has been minimized.

Copy link
@JeffBezanson

JeffBezanson Apr 14, 2014

Collaborator

I agree the original section heading sucked, but we need to think about this some more.


Multiple dispatch appears at first to be about operator overloading:
defining the behavior of functions on new, user-defined types.
But the fact that the compiler ``knows'' the types of function arguments leads
to a surprising, different application: performing elaborate, mostly-static,
to a surprising, different application: performing elaborate, mostly static,
transformations of argument tuples.

Determining the result shape of an indexing operation is just such a
Expand All @@ -442,7 +434,7 @@ \subsection{\code{index\_shape}}
determines the rank of the result array. Many different behaviors
are possible, but currently we use the rule that trailing dimensions
indexed with scalars are dropped\footnote{This rule is the subject of
some debate in the Julia community. Fortunately it is easy to change,
some debate in the Julia community \cite{issue5949}. Fortunately it is easy to change,
as we will see.}.
For example:

Expand Down Expand Up @@ -485,7 +477,7 @@ \subsection{\code{index\_shape}}
\end{verbatim}
}

Or we could immitate APL's behavior, where the rank of the result is the sum
Or we could imitate APL's behavior, where the rank of the result is the sum
of the ranks of the indexes, as follows:

{\small
Expand All @@ -501,16 +493,14 @@ \subsection{\code{index\_shape}}
so we are just concatenating shapes.


\subsection{Why it works}
\subsection{Synergy of multi-methods and dataflow type inference}

This comment has been minimized.

Copy link
@JeffBezanson

JeffBezanson Apr 14, 2014

Collaborator

The word "synergy" should just be banned.


Julia's multi-methods were designed with the idea that dataflow type inference
\cite{Cousot:1977, kaplanullman}
Julia's multi-methods were designed so that dataflow type inference

This comment has been minimized.

Copy link
@JeffBezanson

JeffBezanson Apr 14, 2014

Collaborator

Incorrect. You can apply dataflow type inference to anything; we designed our methods with the idea that it would be. This is a crucial difference for this paper.

would be applied to almost all concrete instances of methods, based on
run-time argument types or compile-time estimated argument types. Without this
piece of infrastructure, definitions like those above might be no more
than a perversely slow way to implement the functionality. But with it, the
definitions have the effect of ``forcing'' the analysis to deduce accurate
types. In effect, such definitions are designed to exploit the dataflow
run-time argument types or compile-time estimated argument types. The
type inference infrastructure ``forces'' the analysis to deduce accurate

This comment has been minimized.

Copy link
@JeffBezanson

JeffBezanson Apr 14, 2014

Collaborator

Incorrect. The type inference is the analysis; it forces itself to do something? Doesn't make sense.
"Perversely slow" is a fun bit of language; why do we have to remove it? You are determined to suck the life out of the writing.

types from the specific method definitions and enables reasonable
performance. In effect, such definitions are designed to exploit the dataflow
operation of matching inferred argument types against method signatures,
thereby destructuring and recurring through argument tuples at compile-time.

Expand All @@ -524,28 +514,41 @@ \subsection{Why it works}
\code{(T...)}, \code{(T,T...)}, \code{(T,T,T...)}, etc. This adds
significant complexity to our lattice operators.

\subsection{Implications}
\subsection{Similarities to symbolic pattern matching}

In a language with de-coupled design and analysis passes, a function like
Julia's multi-methods resemble symbolic pattern matching, such as those in
computer algebra systems. Pattern matching systems effectively
allow dispatch on the full structure of values, and so are in some sense
even more powerful than our generic functions. However, they lack a clear
separation between the type and value domains, leading to performance
opacity: it is not clear what the system will be able to optimize
effectively and what it won't.
Such a separation could be addressed by
designating some class of patterns as the ``types'' that the compiler
will analyze. However, more traditional type systems could be seen as
doing this already, while also gaining data abstraction in the bargain.

\subsection{Implications for Julia programmers}

In a language with decoupled design and analysis passes, a function like
\code{index\_shape} would be implemented inside the run-time system
(possibly scattered among many functions), and separately embodied in
a hand-written transfer function inside the compiler. Our design shows
that such arrangements can be replaced by a combination of high-level code and
a generic analysis (the Telescoping Languages project \cite{telescoping}
also recognized the value of incorporating analyzed library code into a
compiler).
a generic analysis. Similar conclusions on the value of incorporating analyzed library code into a
compiler were drawn by the Telescoping Languages project \cite{telescoping}.

This comment has been minimized.

Copy link
@JeffBezanson

JeffBezanson Apr 14, 2014

Collaborator

Changed to passive voice.


From the programmer's perspective, Julia's multi-methods are convenient
because they provide run-time and compile-time abstraction in a single
mechanism. Having learned Julia's ``object'' system, one has also learned
its ``template'' system, without different syntax or reasoning about
binding time. Semantically, methods always dispatch on run-time
types, so the same definitions are applicable whether types are known
statically or not. Initially, a programmer's intent may be for all types
to be known statically. But if needs change and one day array rank needs
to be a run-time property, the same definitions still work (with the only
difference being that the compiler will generate a dynamic dispatch or
two where there were none before). It is also possible to use popular
statically or not. Code initially written with all types
known statically can be changed on demand to produce new code with some types

This comment has been minimized.

Copy link
@JeffBezanson

JeffBezanson Apr 14, 2014

Collaborator

I don't understand this. What does it mean to change code on demand? It sounds like the user's code changes, which it doesn't. The original wording probably wasn't clear enough either. I think I just meant that a library can be designed to promote static-typeability, but you can still call it any way you want. Writing index_shape in a way that allows its type to be inferred doesn't force its caller to use arrays with statically-known ranks, or indexes with statically-known types.

known only at run-time, while still sharing all the same definitions. (The compiler
takes care of generating the necessary dynamic dispatches transparently.)
It is also possible to use popular
dynamic constructs such as \code{A[I...]} where \code{I} is a heterogeneous
array of indexes.
%Therefore users are free to reason about
Expand All @@ -558,29 +561,8 @@ \subsection{Implications}
\cite{Cousot:1977, widening}. In these cases, the deduced types are
still correct but imprecise, and in a way that depends on somewhat arbitrary
choices of widening operators (for example, such a type might look
like \code{(Int...)} or \code{(Int,Int...)}).


\subsection{Symbolic programming}

Similarities to symbolic pattern matching (as typically found in
computer algebra systems) are readily apparent. These systems effectively
allow dispatch on the full structure of values, and so are in some sense
even more powerful than our generic functions. However, they lack a clear
separation between the type and value domains, leading to performance
opacity: it is not clear what the system will be able to optimize
effectively and what it won't.

This could potentially be addressed by
designating some class of patterns as the ``types'' that the compiler
will analyze. However, more traditional type systems could be seen as
doing this already, while also gaining data abstraction in the bargain.

%TODO: point out how this combines the ``object part'' and the ``array part''
%into a coherent whole.
%This is really a statement about implementing array semantics in language X
%using language X itself, and in particular using intrinsic features for handling
%objects to also handle arrays.
like \code{(Int...)} or \code{(Int,Int...)}). Nevertheless, we believe that the
flexibility of Julia's multi-methods is of overall net benefit to programmers.

%This approach does not depend on any heuristics. Each call to
%\texttt{index\_shape} simply requires one recursive invocation of type
Expand All @@ -596,18 +578,9 @@ \subsection{Symbolic programming}
%This is an example of indexing behavior that is not amenable to useful static
%analysis, since each branch of \code{diverge()} has different types.

%TODO say something about how types of tuples in Julia are defined.

%Such code would throw a type error in languages requiring static
%checking such as Haskell. But in Julia, this is still allowed just that
%the compiler may not have useful information from static analysis and so may
%not run as fast. In Repa, the top priority is to appease the type system of
%Haskell, with performance and user interface secondary. We think it should be
%the other way round.

\section{Discussion}

\begin{figure}
\begin{table}
\label{dispatchratios}
\begin{center}
\begin{tabular}{|l|r|r|r|}\hline
Expand All @@ -633,24 +606,22 @@ \section{Discussion}
\hline
Julia & 5.86 & 51.44 & 1.54 \\
\hline
Julia Operators & 28.13 & 78.06 & 2.01 \\
Julia operators & 28.13 & 78.06 & 2.01 \\
\hline
\end{tabular}
\end{center}
\caption{
Comparison of Julia (1208 functions exported from the \code{Base} library)
to other languages with multiple dispatch \cite{Muschevici:2008}.
The ``Julia Operators'' row describes 47 functions with special syntax
(binary operators, indexing, and concatenation).
Numbers for other systems are copied from \cite{Muschevici:2008}; we did
not repeat their experiments.
to other languages with multiple dispatch.
The ``Julia operators'' row describes 47 functions with special syntax, such as

This comment has been minimized.

Copy link
@JeffBezanson

JeffBezanson Apr 14, 2014

Collaborator

It's not "such as"; it's exactly those functions. Since there are so few we should be precise.

binary operators, indexing, and concatenation.
Data for other systems are from Ref.~\cite{Muschevici:2008}.
}
\end{figure}
\end{table}

Multiple dispatch is used heavily throughout the Julia ecosystem.
Figure~\ref{dispatchratios} illustrates this point quantitatively.
Past work \cite{Muschevici:2008} has developed metrics for evaluating the use
of multiple dispatch, three of which we use here:
Multiple dispatch is used heavily throughout the Julia ecosystem. To quantify
this statement, we use the following metrics for evaluating the extent of
multiple dispatch \cite{Muschevici:2008}:

\begin{enumerate}
\item Dispatch ratio (DR): The average number of methods in a generic function.
Expand All @@ -663,6 +634,9 @@ \section{Discussion}
arguments per method.
\end{enumerate}

Table~\ref{dispatchratios} shows the mean of each metric over the entire Julia
\code{Base} library, showing a high degree of multiple dispatch compared with
corpora in other languages \cite{Muschevici:2008}.
Compared to most multiple dispatch systems, Julia functions tend to have a large
number of definitions. To see why this might be, it helps to compare results
from a biased sample of only operators. These functions are the most obvious
Expand Down

0 comments on commit a1d3755

Please sign in to comment.