Discussion:
[julia-dev] Interfaces and Lexical vs Dynamic Scope
Joshua Ballanco
2016-07-22 01:37:00 UTC
Permalink
Hi all!

Apologies in advance if there’s already a ticket where this discussion
should be had (pointers to such greatly appreciated).

I was recently thinking about what interfaces might eventually look like,
and specifically one of the ideas that was batted about during the JuliaCon
hack day. Writing a function that takes an array-like thing might be
implemented as:

function foo(myarray::ANY{getindex(), setindex(), iterate()})
#

end

where the functions mentioned in the type signature are required to exist
for any argument passed to the function.

One potential “big win” I see in this sort of interface is that it gives
the programmer the opportunity to massage types that normally wouldn’t
qualify for the interface by providing custom implementations for any
missing methods. For example, say you had an immutable array-like that
didn’t implement `setindex()` on its own. One could, if they desired,
provide an implementation of `setindex()` that was a no-op, or returned a
copy with the desired update, etc.

What I particularly like with this approach is that instead of having to
implement many variants of a method to handle different types, one could
write the core logic in a single, clearly understandable function and
segregate the custom edge-case logic elsewhere.

This works well if the non-adhering type might be passed to the method you
are writing and you can add the missing methods in the same module. e.g.:

module Bar
setindex(a::ImmutableArray) = #

function foo(myarray::ANY{getindex(), setindex(), iterate()})
#

end
end

But what if the code you are writing is merely glue between a non-adhering
type and a function that expects a particular interface? e.g.:

module Bar
function foo(myarray::ANY{getindex(), setindex(), iterate()})
#

end
end

module Qux
setindex(a::ImmutableArray) = #

function doit(somearray::ANY)
Bar.foo(somearray)
#

end
end

module Main
data = ImmutableArray()
Qux.doit(data)
end

In this case I think I’d like `Bar.foo()` to use `Qux.setindex()` when
called from within `Qux` with an `ImmutableArray`
but this seems like
treading on dangerous scoping territory!

I don’t have a solution to propose at the moment, but I wanted to raise the
issue because I see some potential parallels with Ruby’s refinements. The
concept of refinements is potentially rather powerful, but Ruby’s
implementation was dogged by a literally years-long debate over how scoping
should work and how to implement such scoping without completely crippling
performance.

Cheers,

Josh
Joshua Ballanco
2016-07-22 02:14:21 UTC
Permalink
 On July 21, 2016 at 21:37:00, Joshua Ballanco
Post by Joshua Ballanco
module Bar
function foo(myarray::ANY{getindex(), setindex(), iterate()})
#…
end
end
module Qux
setindex(a::ImmutableArray) = #…
function doit(somearray::ANY)
Bar.foo(somearray)
#…
end
end
module Main
data = ImmutableArray()
Qux.doit(data)
end
In this case I think I’d like `Bar.foo()` to use `Qux.setindex()` when called from within `Qux` with an `ImmutableArray`…but this seems like treading on dangerous scoping territory!
Upon further consideration, I think it may have been wrong to limit
this to some potential implementation of interfaces/protocols. Indeed,
being able to inject a scope into a called method could potentially be
useful today, and is very analogous to the situation of Ruby’s
refinements. That is, say you wanted to provide a custom
implementation of `capitalize(s::String)` to be used by methods you
call. How might this be achieved? In Ruby this looks like

    module MyStringExtension
      refine String do
        def capitalize
          #…
        end
      end
    end

    class MyClass
      using MyStringExtension
      “foo”.capitalize
    end

Would it be useful to consider something similar for Julia?

Cheers,

Josh

Loading...