« January 2006 | Main | March 2006 »

February 2006

February 28, 2006

Generics stabilizing

RubyCLR now supports multi-type generic types such as Dictionary<T, U>. I also changed the nasty cons method to the infinitely cleaner of method. Here’s some tests to show the progress:


def test_create_two_parameter_generic_type
  dict = Dictionary.of(Int32, Int32).new
  dict.add(1, 1)
  dict.add(2, 2)
  assert_equal 1, dict[1]
  assert_equal 2, dict[2]
end

def test_dictionary_with_string_key
  dict = Dictionary.of(System::String, Int32).new
  dict['John'] = 42
  dict['Ruby'] = 2
  assert_equal 42, dict['John']
  assert_equal 2, dict['Ruby']
end

The one thing that I’m not happy with right now is being forced to fully qualify the System::String type. I might add some code to special-case that type name in the of method and alias to the CLR String type from the Ruby String type.

You might be wondering how I implement the generic types. I generate a Ruby shadow class that has a mangled name. For example, Dictionary.of(Int32,Int32), maps to a Ruby class called Dictionary_generic_Int32_Int32, which is generated the first time of is called. On all subsequent calls, I just look up the Ruby shadow class name. This is also how I handle arrays (you can really think of arrays as a special case of generics).

Generics in RubyCLR

I got a crude implementation of generics up and running in RubyCLR today. It does what these tests say it does, and nothing more :)


class GenericsTests < TestCase
  include System::Collections::Generic

  def test_make_generic_type
    c = List.cons(Int32)
    assert_equal 'System::Collections::Generic::List_generic_Int32', c.name
    assert_equal 'System.Collections.Generic.List`1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]', c.clr_type.full_name
  end

  def test_create_generic_type
    list = List.cons(Int32).new
    assert_equal 'List`1', list.clr_type.name
  end

  def test_call_generic_type
    list = List.cons(Int32).new
    list.add(42)
    assert_equal 42, list[0]
  end

  def test_type_conversion_error
    assert_raises TypeError do
      list = List.cons(Int32).new
      list.add('42')
    end
  end
end

As for feedback, I’m not all that happy with the name cons for the method that creates a generic type. Suggestions would be welcome (but they have to be easy to type, since I’m competing with List<Int32> :)

I also ran some crude performance benchmarks for adding 1M integers to each data structure:

  • ArrayList = 1.15s
  • List<Int32> = 1.00s
  • Ruby Array = 0.66s

I just wanted to get some order of magnitude estimates of performance. So far I’m quite happy with the performance of the bridge. 1M method calls / second is nothing to sneeze at.

IDEs and complexity

Worth repeating:

Intellisense and code generation is an isolated developer trick for hiding real complexity and risk.

Patrick Logan does a great job of capturing why I don’t like the complexity of today’s IDEs.

February 27, 2006

RubyCLR and auto_dispose

In the second drop of RubyCLR, I had a broken implementation of auto_dispose, which automatically calls an object’s Dispose method for you. Fortunately, Gunter Szolderits noticed this problem, and sent me a really lovely implementation that allows for parallel assignment. That means that instead of writing this:


auto_dispose(D.new) do |dc|
  auto_dispose(AD.new) do |adc|
    adc.good_method
  end
  dc.good_method
end

you can now write this:


auto_dispose(D.new, AD.new) do |dc, adc|
  adc.good_method
  dc.good_method
end

Using the new interfaces implementation, the implementation of auto_dispose now looks like this:


def auto_dispose(*objs)
  yield(*objs) if block_given?
ensure
  objs.each do |obj|
    if obj != nil 
      disposable = obj.as IDisposable
      disposable.dispose if disposable != nil
    end
  end
end

Now, is that pretty or what? And I mean both the usage and the implementation!

Interfaces and RubyCLR

My earlier implementation of interfaces for RubyCLR was incomplete. I didn’t have code to let you test whether an object implemented the interface requested. There are a couple of approaches that you can use to solve this problem: via reflection at runtime and via a shim method that “knows” about all of the interfaces that an object implements.

I chose the second option: implementing two new methods is and as. The goal is to eventually provide the same semantics as the C# is and as operators. You can use the is method to test whether an object implements an interface, and you can use the as operator to return a reference to that interface.

I use Reflection to enumerate all of the interfaces of a class and bake in all of those interface references by recording the RuntimeTypeHandle for each one of the interfaces. This approach gives me superior performance since I avoid using Reflection on all but the first call to the is or as methods of a type.

The ultimate goal is that is and as will match the semantics of is and as in C#. Currently I’m missing support for the boxing and unboxing scenarios as well as the super-type scenario.

Here’s the unit test for the is method:


def test_is
  m = System::Data::DataSet.new
  assert m.is?(System::Data::DataSet)
  assert m.is?(IComponent)
  assert m.is?(IDisposable)
  assert m.is?(IServiceProvider)
  assert m.is?(IListSource)
  assert m.is?(System::Xml::Serialization::IXmlSerializable)
  assert m.is?(ISupportInitializeNotification)
  assert m.is?(ISupportInitialize)
  assert m.is?(System::Runtime::Serialization::ISerializable)
  assert !m.is?(System::Data::IColumnMapping)
end

and a simpler test for the as method:


def test_as
  a = ArrayList.new
  a.add(1)
  e = a.as(IEnumerable).get_enumerator.as(IEnumerator)
  assert e.move_next
  assert 1, e.current
  assert !e.move_next
  assert_equal nil, a.as(IComponent)
end

February 23, 2006

Native Ruby array data binding

I spent some time today hacking out an experimental implementation of native Ruby array data binding. It works by using a CLR helper class called DataBinder that implements IList, and calls back to Ruby using delegates to retrieve the data. This gives the following usage model:


names = ['John', 'Paul', 'George', Ringo']
list_box.data_source = names.make_bindable

To make this work, I inject a make_bindable method into the Array class:


def make_bindable
  binder = DataBinder.new
  binder.get_data do |sender, args|
    args.current_object = self[args.index]
  end
  binder.get_count do |sender, args|
    args.count = self.length
  end
  binder
end

Thoughts? Does this feel good?

RubyCLR is all about choice

Look at what I got working this morning:


def test_enumerator
  a = ArrayList.new
  a.Add(1)
  a.Add(2)
  a.Add(3)
  e = a.get_interface(IEnumerable).GetEnumerator.get_interface(IEnumerator)
  assert e.MoveNext
  assert 1, e.Current
  assert e.MoveNext
  assert 2, e.Current
  assert e.MoveNext
  assert 3, e.Current
  assert !e.MoveNext
end

vs.


def test_enumerator
  a = ArrayList.new
  a.add(1)
  a.add(2)
  a.add(3)
  e = a.get_interface(IEnumerable).get_enumerator.get_interface(IEnumerator)
  assert e.move_next
  assert 1, e.current
  assert e.move_next
  assert 2, e.current
  assert e.move_next
  assert 3, e.current
  assert !e.move_next
end

By the way, both work in the same app.

RubyCLR Interfaces

A notable feature that was missing from the second drop of RubyCLR was support for interfaces. Also, I was missing support for integrating .NET Reflection with Ruby Reflection.

I spent some time yesterday adding these features to RubyCLR. This is what I wound up with:


  def test_enumerator
    a = ArrayList.new
    a.Add(1)
    a.Add(2)
    a.Add(3)
    enum = a.get_interface('System.Collections.IEnumerable')
    e    = enum.GetEnumerator.get_interface('System.Collections.IEnumerator')
    assert e.MoveNext
    assert 1, e.Current
    assert e.MoveNext
    assert 2, e.Current
    assert e.MoveNext
    assert 3, e.Current
    assert !e.MoveNext
  end

As you could imagine, I’m going to add explicit support for an each method on enumerable objects.

Implementing get_interface turned out to be easier than expected:


  def get_interface(name)
    klass = self.class
    create_safe_ruby_instance_method(klass, 'get_interface') do
      ldstr        name.to_s
      ld_this      klass
      call         'static Marshal::ToRubyObjectAsInterface(String, Object)'
      ret
    end
    get_interface(name)
  end

The C++ ToRubyObjectAsInterface simply thunks to some existing code in the C++ part of the RubyCLR runtime.

February 21, 2006

Google Calc

Justin just posted an awesome sample that uses RubyCLR to the ruby-talk list:

I had no idea that Google’s calculator could work with fortnights as a unit!

Here’s a link to the complete source to his app.

The more people I can get to use it, the sooner we’ll all have a production-quality release to work with!

Keep those samples coming!

February 20, 2006

Second Drop of RubyCLR

It’s alive and it’s real:

This second release of my RubyCLR bridge contains a non-trivial Windows Forms 2.0 application written entirely in Ruby. If you don’t want to download the entire drop, you can click here to look at the source.

If you thought you’ve seen this application before, you’re right – it’s a partial port of Joe Stegman’s RSS Reader application that was designed to show off some of the more interesting features of Windows Forms 2.0.

I’m pretty sure I could have finished porting the rest of the app, but I think I ported enough of it to demonstrate that you can actually use this drop to build a real app.

There are now 106 tests and 165 assertions in the unit test suite. They all pass.

This build now contains release builds of RbDynamicMethod.dll and RubyClrTests.dll, so it should run on any computer that has Ruby 1.8.2 and .NET Frameworks 2.0 RTM installed.

Get the bits here.

Enjoy.

Photos

  • www.flickr.com
    This is a Flickr badge showing public photos from John Lam. Make your own badge here.

Recent Comments

Recent Posts

May 2008

Sun Mon Tue Wed Thu Fri Sat
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Blog powered by TypePad