Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand documentation on Val{T} #15729

Merged
merged 3 commits into from
Apr 21, 2016
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions doc/manual/performance-tips.rst
Original file line number Diff line number Diff line change
@@ -587,6 +587,8 @@ loop. There are several possible fixes:
- Declare the type of ``x``: ``x::Float64 = 1``
- Use an explicit conversion: ``x = one(T)``

.. _man-performance-kernel-functions:

Separate kernel functions
-------------------------

@@ -637,6 +639,87 @@ Functions like ``strange_twos`` occur when dealing with data of
uncertain type, for example data loaded from an input file that might
contain either integers, floats, strings, or something else.

.. _man-performance-val:

Using the ``Val{T}`` trick
--------------------------

Let's say you want to create an ``N``-dimensional array that
has size 3 along each axis. Such arrays can be created like this:

.. doctest::

A = fill(5.0, (3, 3))

This approach works very well: the compiler can figure out that ``A``
is an ``Array{Float64,2}`` because it know the type of the fill value
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"knows" I think.

(``5.0::Float64``) and the dimensionality (``(3, 3)::NTuple{2,Int}``).
This implies that the compiler can generate very efficient code for
any future usage of ``A`` in the same function.

But now let's say you want to support this for arbitrary dimensions;
you might be tempted to write a function

.. doctest::

function array3(fillval, N)
fill(fillval, ntuple(d->3, N))
end

This works, but (as you can verify for yourself using ``@code_warntype
array3(5.0, 2)``) the problem is that the output type cannot be
inferred: the argument ``N`` is a *value* of type ``Int``, and
type-inference does not (and cannot) predict its value in
advance. This means that code using the output of this function has to
be conservative, checking the type on each access of ``A``; such code
will be very slow.

Now, one very good way to solve such problems is by using the
:ref:`function-barrier technique
<man-performance-kernel-functions>`. However, in some cases you might
want to eliminate the type-instability altogether. In such cases, one
approach is to make use of ``Val{T}`` (see :ref:`man-val-trick`):

.. doctest::

function array3{N}(fillval, ::Type{Val{N}})
fill(fillval, ntuple(d->3, Val{N}))
end

Julia has a specialized version of ``ntuple`` that accepts a
``Val{::Int}`` as the second parameter; by passing ``N`` as a
type-parameter, you make its "value" known to the compiler.
Consequently, this version of ``array3`` allows the compiler to
predict the return type.

However, making use of ``Val`` can be surprisingly subtle: it would be
of no help if you called ``array3`` from a function like this:

.. doctest::

function call_array3(fillval, n)
A = array3(fillval, Val{n})
end

Here, you've created the same problem all over again: the compiler
can't guess the type of ``n``, so it doesn't know the type of
``Val{n}``. Attempting to use ``Val``, but doing so incorrectly, can
easily make performance *worse* in many situations.

An example of correct usage of ``Val`` would be:

.. doctest::

function filter3{T,N}(A::AbstractArray{T,N})
kernel = array3(1, Val{N})
filter(A, kernel)
end

In this example, ``N`` is specified as a parameter, so its "value" is
known to the compiler. Essentially, ``Val{T}`` works only when ``T``
is either hard-coded (``Val{3}``) or already specified in the
type-domain.

Access arrays in memory order, along columns
--------------------------------------------

25 changes: 11 additions & 14 deletions doc/manual/types.rst
Original file line number Diff line number Diff line change
@@ -1237,24 +1237,15 @@ If you apply :func:`supertype` to other type objects (or non-type objects), a
julia> supertype(Union{Float64,Int64})
ERROR: `supertype` has no method matching supertype(::Type{Union{Float64,Int64}})

.. _man-val-trick:

"Value types"
-------------

As one application of these ideas, Julia includes a parametric type,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never understood what kind of "these ideas" are being referred to. I guess many people who just followed the _man-val-trick link have the same experience.
Also, I find it a little bit confusing that Val{T} is introduced as some sort of Julia type system quirk, whereas in fact it's just an empty immutable, and the same effect could be achieved with any user-defined type. Maybe it's worth clarifying.

``Val{T}``, designated for dispatching on bits-type *values*. For
example, if you pass a boolean to a function, you have to test the
value at run-time:

.. doctest::

function firstlast(b::Bool)
return b ? "First" : "Last"
end

println(firstlast(true))

You can instead cause the conditional to be evaluated during function
compilation by using the ``Val`` trick:
``Val{T}``, designated for dispatching on bits-type *values*.
Normally, you can't dispatch on a value such as ``true`` or ``false``,
but the ``Val`` type makes this possible:

.. doctest::

@@ -1270,6 +1261,12 @@ For consistency across Julia, the call site should always pass a
``Val`` type rather than creating an instance, i.e., use
``foo(Val{:bar})`` rather than ``foo(Val{:bar}())``.

It's worth noting that it's extremely easy to mis-use the ``Val``
trick, and you can easily end up making the performance of your code
much *worse*. If you're contemplating using ``Val``, please read the
more extensive discussion in :ref:`the performance tips
<man-performance-val>`.

.. _man-nullable-types:

Nullable Types: Representing Missing Values