Tutorials

Adam Parrott edited this page Jan 6, 2014 · 2 revisions

A simple class

Classes look basically like they do in Ruby, using < for extension.

class Foo
end

class Bar < Foo
end

Interfaces are implemented by using the implements keyword, as in Java, but within the body of the method. This visually separates interfaces from type definition, and avoids long chains of interfaces when multiple are implemented.

class Baz
  implements Runnable

  def run:void
  end
end

Printing text to stdout

Mirah uses the puts keyword as a shortcut for System.out.println. Of course the latter works just the same.

def add_one(a:int)
  puts a + 1
end

Literals

Mirah supports most of Ruby's literals. Strings are enclosed in " or ' characters, with only the " form supporting interpolation. Literal integer and double values are as in Ruby. Regular expression literals are as in Ruby, enclosed in / characters.

str = 'non-interpolated string'
str2 = "interpolated is better than #{str}"
answer = 42
pi = 3.14159265358979323846264
regex = /\d(cow)+\w\\/ # in Java, this would be "\\\\d(cow)+\\\\w\\\\\\\\"

Closures

Closures take the same form as Ruby blocks, supporting both do ... end and { ... } forms and applying the same precedence rules as in Ruby (with do ... end applying to the outermost in a chain of calls and { ... } applying to the innermost). Parameters are specified as in Ruby, and generally infer their type from use.

str = '1 2 3 4 5'
str.split.each do |x| # x is a String
  puts x + 10
end

Closures are generally defined in two ways: as macro expansions or as anonymous implementations of an interface. The each example above is a macro available on all array and java.util.Collection types, and expands inline to a simple loop. The next example, creating a new java.lang.Thread, uses the closure body as an anonymous implementation of java.lang.Runnable.

thread = Thread.new do
  # this is the public void run() body
  10.times {|i| puts "Hooray for iteration #{i}"}
end
thread.start

Note that since this is a java.lang.Thread, a call to Thread#start is required, where in Ruby the thread is started on creation.

Optional semicolons and parentheses

Semicolons are only required when separating lines of code that occur on the same line of a script. Parentheses are required only when necessary to disambiguate long strings of code.

a = 'hello goodbye'
split = str.split ' '
split.each do |str|
  str += 'foo'; puts str
end

Defining methods

Methods are defined using the same syntax as Ruby, but in most cases arguments must be explicitly typed. Type declarations take the form seen in Scala, with the variable name followed by a colon and the type.

def printN(str:String, n:int)
  n.times do
    puts str
  end
end

A simple def will define the method as static when at toplevel (as though it were a "class method" of the current script) or as an instance method when used inside a class. To define a static method inside a class, use the def self.x form as in Ruby.

class StaticUtils
  def self.add(a:int, b:int)
    a + b
  end
end

puts StaticUtils.add(3, 4) # prints "7"

Comments

Mirah supports the "here to end of line" comment form from Ruby, using the hash symbol #.

# This is the most amazing class ever
class AmazingClass
  def initialize
    @amazing = true # set amazing to 'true'
  end
end

Importing Classes

Import syntax is as in Java, except that the trailing semicolon is again optional.

import java.util.ArrayList
import javax.swing.JFrame

As in Java, the java.lang package is implicitly imported.

Fibonacci

The code for fib is largely unchanged from the Ruby version, with the only obvious difference being the type declaration. Here, a Ruby symbol :fixnum is used. In the JVM backend for Mirah, this ends up compiling to a primitive 32-bit int

def fib(a:fixnum):fixnum
  if a < 2
    a
  else
    fib(a - 1) + fib(a - 2)
  end
end

Note that the return value of this method, as all methods in Mirah, is inferred from the exit points. The type inference engine will raise an error if multiple exit points have incompatible types.

Note also that fixnum is a synonym for "int" in this case. You can use either or.

Calling Java Libraries

Here's a short script showing importing and calling the java.lang.System class from a script. Two Java methods result from compiling this script: one for foo and one for the script body itself, which is compiled as the main() for the resulting .class file.

 def foo
   home = System.getProperty "java.home"
   System.setProperty "hello.world", "something"
   hello = System.getProperty "hello.world"
 
   puts home
   puts hello
 end
 
 puts "Hello world!"
 foo

Notice also that there are no types declared anywhere in this script. The types of 'home' and 'hello' within the 'foo' method are inferred to be java.lang.String from looking at the getProperty method on java.lang.System. Also, in the current Mirah code, 'puts' is treated as a keyword that essentially does a System.out.println of the passed argument. This may or may not be part of final Mirah; or puts may be an extension method applied to java.lang.Object, so it is always present to all scripts.

Constructing Objects

Because Ruby's standard for object construction is to call a "new" method on a target class, the Java backend uses the same syntax to invoke a constructor on a target type. Here an ArrayList and a StringBufer are constructed and manipulated. Note again the similarity to Ruby code; if the imports were gone, this script is perfectly valid Ruby code.

 import java.util.ArrayList
 
 list = ArrayList.new
 sb = StringBuffer.new("Hello")
 sb.append(", world")
 list.add(sb)
 puts list

Note again that no types are actually declared here, and the types of 'list' and 'sb' are inferred from the results of constructing ArrayList and StringBuffer.

Outputting java code (Deprecated in next release(0.0.12))

By default mirah compiles to bytecode, and executes that. You can also convert straight to "java", like

 $ mirahc --java file.mirah # create folder File with .java files in it

How are Java Interfaces Defined/Implemented in Mirah?

class Foo
  implements Serializable
  ....
end

interface A do
  def something:void;end
end

interface B < A, Serializable do
  def somethingElse(x:int):int;end
end

How do you use an anonymous class?

In general if a method accepts an interface, and that interface only defines one method, then you pass it in as a block.

ref: https://github.com/mirah/mirah/blob/master/examples interfaces

How are exceptions coded in Mirah?

Ruby syntax is used (except retry and catch/throw)

begin
  ....
rescue IOException => ex
  .....
ensure
  .....
end

If you want to declare which exceptions a method throws, use the throws macro.

def foo
  throws IOException
  ....
end

This is only required if you are using the java backend as the .class backend does not check exceptions.

How to use final

There is not currently a way to declare variables final, but this will likely be added in the future. In addition, we are considering options for declaring entire classes immutable (or mutable only in constructor) and for declaring local variables as write-once (essentially equivalent to final local variables in Java.

Mirah presently opts to present mutable local variables for closures, to better imitate Ruby's mutable closure scopes. This may change or be augmented with immutable closure scopes in the future.

How to use void

If subclassing a java class or implementing an interface with a void method, Mirah should be able to infer that automatically. The only time we need to specify void is if you want to force a method to return void.

This is particularly useful for chaining methods because Mirah treats void methods as if they return self.

class A
  def foo
    puts "foo"
    self
  end

  def bar:void
    puts "bar"
  end
end

class B < A
end

y = A.new.foo  # y is an A
x = B.new.bar  # x is a B

How to set visibility (public, private etc.) in Mirah?

Visibility is set the same as in a ruby script.

How to use Java annotations in Mirah?

$Deprecated
def foobar; end

The source code compiler doesn't support annotation values, but the bytecode backend should support string, class, and annotation values, as well as arrays of those types.

Use square brackets instead of parens around the values:

$Foo["xyz"]
$Bar["a" => "A"]

How to do foreach (for loop)

I believe you do it just by using the "each" method.

import java.util.ArrayList
b = ArrayList.new [1,2,3]
b.each{|n| puts n}

Though in reality it's just syntactic sugar for a large iterator loop.

How to declare a variable's type but with value null

p = Printer(nil)

Primitive arrays

str_ary = String[5]
int_ary = int[5]

or an array of any type

classes = Class[1] 
classes[0] = Long.class

Operators

NB that currently mirah uses java's default "a == b" operator if you use "==" within mirah. this is almost always wrong if you are comparing objects, but works for integers and objects with the same object id, so plan accordingly.

Also note that you can name methods with ruby style syntax, like "solid?" and "def +(o:Object); end" and call them using that same method name, just realize these method names are not standard for java, so won't be easily callable from java, and if you uses mirahc to generate java files from them, they won't compile with javac as this is a limitation of the java parser. Not mirah though, it can use them.

casting/declaring primitive types

The default value for a number is int or long if the number is large

a = 0 # int
b = 4_000_000_000 # long

a = long(0) # long 0
b = short(0) # short 0

eventually mirah wants to also support "0L" type syntax.

Macros

See Macros for how to implement your own mirah macros them.

Importing

Imports are the same as java, without a need for a trailing colon:

import java.util.HashMap
import java.util.*

you can also rename things for convenience sake:

import java.util.HashMap as H
H.new

Profiling

As with any java program, where ProfileStev.class was created for me by mirah:

$ java -agentlib:hprof=cpu=samples ProfileStev

Re-use Blocks

You can have a method pass you block a block instance, then save that around:

interface BlockOne do
 def go3():Object
 end
end

class A

  def go(o:BlockOne)
    o # return the block
  end
  
  def go2(o:BlockOne)
    puts 'in go2'
  end
  
end

instance = A.new

block = instance.go {
  puts 'in block'
}

instance.go2 block

Your question not answered here?

More questions and answers culled from the mailing list are temporarily stored at https://gist.github.com/704274

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.