Category: Welcome

TODO: Implement

Somehow I seem to have reached a new level in my year-old C++ code. Apart form fixing some minor bugs in rare use-cases, the only parts I need to fix are NYIs (Not-Yet-Implemented Errors). Whenever I wonder why some function is not working, a few clicks into the code lead me to a method with the typical NYI body:

{
    // TODO: Implement
}

Ah, well. I might as well get to it ;)

by axel
2008-07-17. 13:13:23. 70 words, 632 views. Categories: Welcome , Leave a comment »Send a trackback »

Signals & Slots in Ruby

Motivation

Well, ruby is actually so flexible that there is no real need to implement the goode olde signals & slots metaphor. But it serves nicely as demonstration of ruby properties, so here we go. A typical signal & slots example looks like this:

 
# constants
OFF = false; ON = true
# ok, I'm laaaazy, so I type this just once
module Named
  attr_accessor :name   # getter + setter
  def initialize(name)
    @name=name
  end
end

# gosh, a switch! this one emits a signal labeld :switched
class SignalingSwitch 
  include Named 
  include Signaling
  def switch 
    print @name," switched\n"
    emit :switched
  end
end

# something to switch.. a light!
class Light 
  include Named  
  @state=OFF # initial value 
  attr_accessor :state # getter + setter 

  def turn(x=!@state)  # default: toggle
    @state=x
    print ' ',@name,' turned ',if @state then "on" else "off" end,"\n"
  end
end

# ok, let's roll. two lights
l1 = Light.new("Light1")
l2 = Light.new("Light2")

# each light gets its own switch
s1 = SignalingSwitch.new("Switch1")
connect(s1,:switched,l1,:turn)
s2 = SignalingSwitch.new("Switch2")
connect(s2,:switched,l2,:turn)
So if we call
s1.switch
s1.switch
s2.switch
We get the ouput
 
Switch1 switched
  Light1 turned on
Switch1 switched
  Light1 turned off
Switch2 switched
  Light2 turned on
Ok, signals and slots are a bit more powerfull. We may add
# add an all of all on switch as well
sOff = SignalingSwitch.new("Switch all off")
connect(sOff,:switched,l1,:turn,OFF)
connect(sOff,:switched,l2,:turn,OFF)
sOn = SignalingSwitch.new("Switch all on")
connect(sOn,:switched,l1,:turn,ON)
connect(sOn,:switched,l2,:turn,ON)
So that via
sOn.switch
sOff.switch
We get the ouput
 
Switch all on switched
  Light1 turned on
  Light2 turned on
Switch all off switched
  Light1 turned off
  Light2 turned off

How it is made

Ok, I wrote the module Signaling and a global connect function to make it work

 
# Another Signals + Slots Implementation for Ruby (c) Axel Plinge 2006

# in order to avoid eval(...) cascades, all signaling Objects
# have to 'include Signaling' in order to be able to 'emit'
module Signaling   
  # connect one of our signals to one someones slot i.e. method
  def connect(signal,recipient,slot,*args)
    @connections = Hash.new unless @connections
    @connections[signal] = [] unless @connections[signal]
    @connections[signal].push [recipient.method(slot),args]
  end
  
  # emit :signal name => call associated method with args or default value
  def emit(name,*args)
    return if !@connections
    connected_slots =@connections[name]
    return if !connected_slots
    connected_slots.each do |slot|
      slot[0].call(*(slot[1]+args)) # concatenate *args lists
    end
  end
end
  
# connect sender's signal to one recipient's slot i.e. method
# called by sender.emit signal,*emit_args
#
# if *args are given, recipient.slot(*args,*emit_args) will be invoked,
# otherwise the just the args from after the emit statement are used
# recipient.slot(*emit_args)
def connect(sender,signal,recipient,slot,*args)
  sender.connect(signal,recipient,slot,*args)
end

Modules in Ruby can be used as namespaces in C++ or Java or, as I have done here, for multiple ineritance, just as interfaces are used in Java. Object.method(mehtod_name) gives and Method object with a method call to involke it. Since variable argument lists are arrays, we can concatenate them with + like any other array before unfolding them with *.

An Observer Pattern

Well, the Observer pattern looks like a subset of signals and slots. So I just implemented it as such. Note that there is a big Observer implementation by Joel VanderWerf on RAA and, of course, some implementations in the Design Patterns collection on RubyGarden (which I, of course, read after implementing it myself... )

# Observer Pattern, (c) Axel Plinge 2006

module Observable
  include Signaling
  # connct oberver to our changed event 
  def addObserver(o)
    connect(:changed,o,:update,self)
  end
  # notify all observers of our change
  def notifyObservers(*args)
    emit :changed,self,*args
  end
end

module Observer
  # called if observable changes
  def update(o,*args) 
  end
end
In order to test that, here a simple example
 
# a list of inclomming trains
class TrainList
  include Observable
  
  def initialize()    
    @list = []
  end
  
  def add(train)    
    @list.push(train)
    @list.sort!    
    notifyObservers
  end
  
  def del(train)
    @list.delete(train)
    notifyObservers
  end
  
  def to_s
    s=''    
    @list.each do |t|
      s=s+t.to_s+"\n"
    end
    s
  end  
end

# a display of incomming trains
class TrainDisplay
  include Observer # actually we just have to define update(o,args)
  
  attr_accessor :name # getter + setter
  def initialize(name)
    @name=name
  end
  
  def update(o,*args)
    print "---"+@name+"---"+"\n"
    print o.to_s
  end  
end
So if we call:
 
d1 = TrainDisplay.new("am Eingang")
d2 = TrainDisplay.new("am Gleis")
l  = TrainList.new
l.addObserver(d1)
l.addObserver(d2)
t1 = Train.new("16:00", "H-Bahn SWK Abgabe")
t2 = Train.new("11:11", "S1 Kölle")
l.add t1
l.add t2
l.del t2
We get the ouput
---am Eingang---
16:00   H-Bahn SWK Abgabe
---am Gleis---
16:00   H-Bahn SWK Abgabe
---am Eingang---
16:00   H-Bahn SWK Abgabe
11:11   S1 Kölle
---am Gleis---
16:00   H-Bahn SWK Abgabe
11:11   S1 Kölle
---am Eingang---
16:00   H-Bahn SWK Abgabe
---am Gleis---
16:00   H-Bahn SWK Abgabe
Ok, just in case you are wondering, here the Train class
# schoo schooo
class Train
  attr_accessor :time, :name
  
  def initialize(time,name)
    @time=time;@name=name
  end
  
  def <=>(o)
    if @time==o.time then @name<=>o.name else @time<=>o.time end
  end
  
  def to_s
    @time.to_s+"\t"+@name
  end
end
Anyways, I hope this was a bit insightfull. The whole code is now part of APRL, you can download it here or view the rdocs, if you like.
by axel
01/17/06. 11:33:05 am. 918 words, 13517 views. Categories: Welcome , Leave a comment »Send a trackback »