<p dir="auto">This is partly inspired by the discussions in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="96957776" data-permission-text="Title is private" data-url="https://github.com/JuliaLang/julia/issues/12292" data-hovercard-type="pull_request" data-hovercard-url="/JuliaLang/julia/pull/12292/hovercard" href="https://github.com/JuliaLang/julia/pull/12292">#12292</a> and <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="97404453" data-permission-text="Title is private" data-url="https://github.com/JuliaLang/julia/issues/12322" data-hovercard-type="pull_request" data-hovercard-url="/JuliaLang/julia/pull/12322/hovercard" href="https://github.com/JuliaLang/julia/pull/12322">#12322</a>.</p>
<p dir="auto">The <code class="notranslate">@generated</code> macro makes it much easier to generate specialized functions. Hence, I think it might be time for us to upgrade our functor system. So I wrote a small program for concept proof:</p>
<div class="highlight highlight-source-julia notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="# functors specialized based on symbol
immutable SFunc{N,Sym} &lt;: Base.Func{N} end

@generated function call{S}(f::SFunc{1,S}, x)
    :($(S)(x))
end

# a map function that takes advantage of specialized fun
function mymap(f::Function, x)
    if isgeneric(f) &amp;&amp; is(f.env.module, Base)
        map(SFunc{1,f.env.name}(), x)
    else
        map(f, x)
    end
end

# Benchmark
function benchmark(X::Array, ntimes::Int)
    # warming
    map(abs2, X)
    mymap(abs2, X)
    abs2(X)

    # measuring
    gc()
    gc_enable(false)
    @time for i = 1:ntimes map(abs2, X) end
    @time for i = 1:ntimes mymap(abs2, X) end
    @time for i = 1:ntimes abs2(X) end
    gc_enable(true)
end
benchmark(randn(1_000_000), 10)"><pre class="notranslate"><span class="pl-c"><span class="pl-c">#</span> functors specialized based on symbol</span>
immutable SFunc{N,Sym} <span class="pl-k">&lt;:</span> <span class="pl-c1">Base.Func{N}</span> <span class="pl-k">end</span>

<span class="pl-c1">@generated</span> <span class="pl-k">function</span> <span class="pl-en">call</span><span class="pl-c1">{S}</span>(f<span class="pl-k">::</span><span class="pl-c1">SFunc{1,S}</span>, x)
    :(<span class="pl-k">$</span>(S)(x))
<span class="pl-k">end</span>

<span class="pl-c"><span class="pl-c">#</span> a map function that takes advantage of specialized fun</span>
<span class="pl-k">function</span> <span class="pl-en">mymap</span>(f<span class="pl-k">::</span><span class="pl-c1">Function</span>, x)
    <span class="pl-k">if</span> <span class="pl-c1">isgeneric</span>(f) <span class="pl-k">&amp;&amp;</span> <span class="pl-c1">is</span>(f<span class="pl-k">.</span>env<span class="pl-k">.</span>module, Base)
        <span class="pl-c1">map</span>(<span class="pl-c1">SFunc</span><span class="pl-c1">{1,f.env.name}</span>(), x)
    <span class="pl-k">else</span>
        <span class="pl-c1">map</span>(f, x)
    <span class="pl-k">end</span>
<span class="pl-k">end</span>

<span class="pl-c"><span class="pl-c">#</span> Benchmark</span>
<span class="pl-k">function</span> <span class="pl-en">benchmark</span>(X<span class="pl-k">::</span><span class="pl-c1">Array</span>, ntimes<span class="pl-k">::</span><span class="pl-c1">Int</span>)
    <span class="pl-c"><span class="pl-c">#</span> warming</span>
    <span class="pl-c1">map</span>(abs2, X)
    <span class="pl-c1">mymap</span>(abs2, X)
    <span class="pl-c1">abs2</span>(X)

    <span class="pl-c"><span class="pl-c">#</span> measuring</span>
    <span class="pl-c1">gc</span>()
    <span class="pl-c1">gc_enable</span>(<span class="pl-c1">false</span>)
    <span class="pl-c1">@time</span> <span class="pl-k">for</span> i <span class="pl-k">=</span> <span class="pl-c1">1</span><span class="pl-k">:</span>ntimes <span class="pl-c1">map</span>(abs2, X) <span class="pl-k">end</span>
    <span class="pl-c1">@time</span> <span class="pl-k">for</span> i <span class="pl-k">=</span> <span class="pl-c1">1</span><span class="pl-k">:</span>ntimes <span class="pl-c1">mymap</span>(abs2, X) <span class="pl-k">end</span>
    <span class="pl-c1">@time</span> <span class="pl-k">for</span> i <span class="pl-k">=</span> <span class="pl-c1">1</span><span class="pl-k">:</span>ntimes <span class="pl-c1">abs2</span>(X) <span class="pl-k">end</span>
    <span class="pl-c1">gc_enable</span>(<span class="pl-c1">true</span>)
<span class="pl-k">end</span>
<span class="pl-c1">benchmark</span>(<span class="pl-c1">randn</span>(<span class="pl-c1">1_000_000</span>), <span class="pl-c1">10</span>)</pre></div>
<p dir="auto">Result:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content=" 444.635 milliseconds (20000 k allocations: 381 MB)
  46.907 milliseconds (20 allocations: 78126 KB)
  49.847 milliseconds (20 allocations: 78126 KB)"><pre class="notranslate"><code class="notranslate"> 444.635 milliseconds (20000 k allocations: 381 MB)
  46.907 milliseconds (20 allocations: 78126 KB)
  49.847 milliseconds (20 allocations: 78126 KB)
</code></pre></div>
<p dir="auto">So, it looks like it can dramatically improve the performance of many higher-level functions (e.g. <code class="notranslate">map</code>, <code class="notranslate">reduce</code>, <code class="notranslate">broadcast</code>, etc), making their performance on par with manually specialized ones, without even changing the external interface. Also, a big advantage of this as opposed to the current system is that it allows arbitrary named function to be used (instead of just a prescribed list of functions).</p>
<p dir="auto">I am proposing:</p>
<ul dir="auto">
<li>Introduce the new functor system with a PR (I can do this).</li>
<li>Have the operation-dependent promotion (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="96957776" data-permission-text="Title is private" data-url="https://github.com/JuliaLang/julia/issues/12292" data-hovercard-type="pull_request" data-hovercard-url="/JuliaLang/julia/pull/12292/hovercard" href="https://github.com/JuliaLang/julia/pull/12292">#12292</a>) depend on the new functor.</li>
</ul>
<p dir="auto">I think the utility of this specialized functor system is far beyond better type promotion.</p>
<p dir="auto">By keeping <code class="notranslate">Func{N}</code> as an abstract type, and define a bunch of type alias like below, most of the client codes that depend on old functors would still be working as expected.</p>
<div class="highlight highlight-source-julia notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="typealias AddFun SFunc{2, :+}
typealias AbsFun SFunc{1, :abs} 
# ..."><pre class="notranslate">typealias AddFun SFunc{<span class="pl-c1">2</span>, :<span class="pl-k">+</span>}
typealias AbsFun SFunc{<span class="pl-c1">1</span>, <span class="pl-c1">:abs</span>} 
<span class="pl-c"><span class="pl-c">#</span> ...</span></pre></div>
<p dir="auto">cc: <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/timholy/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/timholy">@timholy</a></p>
<p dir="auto"><code class="notranslate">make testall</code> after latest <code class="notranslate">git pull</code> results in a process that stops at testing <code class="notranslate">cmdlineargs</code> as in</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="...
error (2)                 |    1.12  |  0.00  |  0.0 | 11.33      | 1103.82 
cartesian (2)             |    0.11  |  0.02  | 17.8 |  1.14      | 1103.82 
asmvariant (2)            |    0.14  |  0.00  |  0.0 |  2.30      | 1103.82 
osutils (2)               |    0.02  |  0.00  |  0.0 |  0.23      | 1103.82 
channels (2)              |   10.55  |  0.70  |  6.6 | 428.04     | 1114.27 
iostream (2)              |    0.14  |  0.00  |  0.0 |  2.80      | 1114.27 
fft (2)                   |   19.29  |  0.87  |  4.5 | 723.95     | 1118.11 
dsp (2)                   |    6.26  |  0.24  |  3.8 | 266.36     | 1118.11 
examples (2)              |   13.79  |  0.47  |  3.4 | 648.76     | 1118.11 
distributed (4)           |   85.59  |  0.00  |  0.0 |  5.12      | 1003.38 
cmdlineargs (5)           |  111.27  |  0.07  |  0.1 | 65.80      | 952.91  "><pre class="notranslate"><code class="notranslate">...
error (2)                 |    1.12  |  0.00  |  0.0 | 11.33      | 1103.82 
cartesian (2)             |    0.11  |  0.02  | 17.8 |  1.14      | 1103.82 
asmvariant (2)            |    0.14  |  0.00  |  0.0 |  2.30      | 1103.82 
osutils (2)               |    0.02  |  0.00  |  0.0 |  0.23      | 1103.82 
channels (2)              |   10.55  |  0.70  |  6.6 | 428.04     | 1114.27 
iostream (2)              |    0.14  |  0.00  |  0.0 |  2.80      | 1114.27 
fft (2)                   |   19.29  |  0.87  |  4.5 | 723.95     | 1118.11 
dsp (2)                   |    6.26  |  0.24  |  3.8 | 266.36     | 1118.11 
examples (2)              |   13.79  |  0.47  |  3.4 | 648.76     | 1118.11 
distributed (4)           |   85.59  |  0.00  |  0.0 |  5.12      | 1003.38 
cmdlineargs (5)           |  111.27  |  0.07  |  0.1 | 65.80      | 952.91  
</code></pre></div>
<p dir="auto">The system monitor shows there is no activity on any CPU apart from baseline. I force a halt with ctrl+c. Same thing repeated twice in a row reliably. and in each case cmdlineargs was running on a different worker.</p>
<p dir="auto">In</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Julia Version 0.6.0-pre.alpha.250
Commit 43fb9f0* (2017-03-25 05:22 UTC)
Platform Info:
  OS: Linux (x86_64-suse-linux)
  CPU: Intel(R) Core(TM) i5-4460  CPU @ 3.20GHz
  WORD_SIZE: 64
  BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY Haswell)
  LAPACK: libopenblas64_
  LIBM: libopenlibm
  LLVM: libLLVM-3.9.1 (ORCJIT, haswell)"><pre class="notranslate"><code class="notranslate">Julia Version 0.6.0-pre.alpha.250
Commit 43fb9f0* (2017-03-25 05:22 UTC)
Platform Info:
  OS: Linux (x86_64-suse-linux)
  CPU: Intel(R) Core(TM) i5-4460  CPU @ 3.20GHz
  WORD_SIZE: 64
  BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY Haswell)
  LAPACK: libopenblas64_
  LIBM: libopenlibm
  LLVM: libLLVM-3.9.1 (ORCJIT, haswell)
</code></pre></div>