Extensions are the way that Mirah adds methods to existing classes. When you call methods like each on collections, you are actually using extensions. They are helpful when you want to write DSLs and when you want to make a library you are using feel more Mirah-ish.
How do you use them?
Mirah comes with a number of built in extensions for Java's base types. For example, the methods each
, map
and empty
are added to Collection
s. The built in extensions need no special invocation to be taken advantage of, they're already available. If you want to use extensions from a library, or optional extensions, you'll need to tell Mirah which ones you want to use.
The way you do that is by "using" the extension. using
works just like import, but instead of making an imported class available, they extend other classes. For example, say you wanted to pluralize a string using an activesupport-like library. You could do it like so:
using org.dubious.active_support.StringInflections
fruit_count=5
puts "banana".pluralize(fruit_count)
The methods added by extensions are only visible by scopes that import them. If you pass an object that has extensions on it to a method defined in a different file, that extension won't be carried along with the object. It only exists in that scope. For example, the following code would be a compile error because pluralize only is available in uses_extension
and not in no_extension
.
def uses_extension
using org.mirah_on_rails.active_support.StringInflections
no_extension("banana")
end
def no_extension(fruit: String)
puts fruit.pluralize(count)
end
Extensions are implemented as static methods on static classes, so they'll show up in your stacktraces. That also means that if you use extensions, you need to make sure that the associated runtime jars are on the CLASSPATH, or they will not work.
How are extensions different from interfaces? Extensions exist partially at runtime and partly at compile time. They don't affect the class that they extend, and they only available in scopes that are using
them. Interfaces, on the other hand, are included as part of the class they are added to. If they have default implementations, the class defers to those methods, and any instance of that class will have that interface as part of its definition.
Extensions wouldn't be very interesting if they could only exist in Mirah's compiler code. They provide a way to change dispatch of methods.
extension HelloExt
extending String do
def hello
puts "Hello, #{self}"
end
end
end
"Steve".hello
###Providing a DSL with extensions
Creating internal DSLs is easyish with extensions and macros. You can even make it so that the extensions are only in scope within a block kind of like a powerful instance_eval+using sort of thing. That might look something like this:
class App
macro def config(block: Block)
quote do
using MyDSLExtensions
result = `block.body`
`@call.target`.config = result
end
end
end
extension MyDSLExtensions
extending Numeric do # Numeric will work on primitive numbers somehow
def mb
"{self} in megabytes"
end
end
end
TODO adding a using like this should be hygenic--ie the quote should introduce a new scope so that using isn't added to the surrounding scope
What we're doing here is creating a config macro that takes a block, and introduces a using call that works on the body of the block. Users of this can import App, and use config with the config DSL without using MyDSLExtensions for their whole file. It could look something like this
import org.mycool.App
app = App.new
app.config do
12.mb
end
Besides methods, extensions can also define macros. For example:
extension Foo
extending Bar
macro def baz
quote { puts `@call.target` }
end
end
end
Helpers
extensions are implemented as a combination of static classes with static methods and macros. As such producing an extension library is a little tricky. You'll want to produce two jars--classes and macros--if you want the resulting binaries to not require mirahc on their classpath for analysis. java and the jvm don't care because macro's dependencies on mirahc come through compile time annotations which are ignored by java. javac on the other hand, will get unhappy.
Extensions can't implement interfaces since they only exist as additional method lookup where they are used. Extensions can't have instance variables. Extensions can have inner classes, constants and class variables. They can't inherit from classes. Some of these could theoretically be loosened in the future.
Methods defined in extensions are static methods and can be called from the methods in extending blocks
extension Foo
def bar
1
end
extending Baz do
def wut
bar
end
end
end
Baz.new.wut == Foo.bar
Quirkily, you can import extensions as classes and use them as utility classes full of static methods directly.
import Foo
puts Foo.bar