Interfaces and Lexical vs Dynamic Scope

Interfaces and Lexical vs Dynamic Scope

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.

Re: Interfaces and Lexical vs Dynamic Scope

> 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!

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