2013-05-25

The Paper Crane and the Nested Algorithms

"It is so wrong in all the right ways"
-- someone

One of my favourite Vala things

So, I have been trying out different stuff in Vala lately, one of which is the GObject to JSON stuff (more on that in an other post). Today I thought that I would show you some dirty, dirty, dirty things to do with Vala.

Nested lambda functions

So the topic of today is -- as the heading suggests -- nested lambda functions (or closures, if you are so inclined).

using GLib;
using Gtk;


public class MyApp : Gtk.Application {
  static int main (string args[]) {
    MyApp app = new MyApp ();
    return app.run ();
  }
  
  Gtk.Label label;
  Gtk.Window window;
  
  MyApp () {
    
    Object(application_id: "testing.my.application", flags: ApplicationFlags.FLAGS_NONE);
  }
  
  
  protected override void activate () {
    /* Creating a window */
    window = new Gtk.ApplicationWindow (this);
    
    this.build_menues ();
    
    window.set_default_size (400,400);
    this.label = new Gtk.Label ("Hello GTK!");
    window.add (label);
    window.title = "Hello GTK!";
    window.show_all ();
  }
  
  private void build_menues () {
    /* Creating the application menu */
    GLib.Menu app_menu = new GLib.Menu ();
    app_menu.append ("Message", "app.message");
    app_menu.append ("About", "app.about");
    app_menu.append ("Quit", "app.quit");
    
    this.set_app_menu (app_menu);
    
    GLib.SimpleAction quit_action = new GLib.SimpleAction ("quit", null);
    quit_action.activate.connect (() => {
      this.quit();
    });
    this.add_action (quit_action);
    
    GLib.SimpleAction about_action = new GLib.SimpleAction ("about", null);
    about_action.activate.connect (() => {
      Gtk.AboutDialog dialog = new Gtk.AboutDialog();
      dialog.set_destroy_with_parent (true);
      dialog.set_transient_for(this.window);
      dialog.set_modal (true);
      
      dialog.response.connect((response_id) => {
        if (response_id == Gtk.ResponseType.CANCEL ||
            response_id == Gtk.ResponseType.DELETE_EVENT) {
          //dialog.hide_on_delete ();
          dialog.destroy ();
        }
      });
      
      dialog.authors = {"Gustav Hartvigsson", "Some One Else"};
      dialog.program_name = "Hello GTK!";
      dialog.present();
    });
    this.add_action (about_action);
    
    GLib.SimpleAction message_action = new GLib.SimpleAction ("message", null);
    message_action.activate.connect (() => {
      label.set_text ("I have been clicked!");
    });
    this.add_action (message_action);
    
    
  }
  
}

If you look at the highlighted code (line 49-66) you will see nested lambda functions. This is awesome, and something you would expect from a modern, high abstraction level, object oriented language.

Dissection the C code

The C code that this produces (using valac-0.20 main.vala --pkg gtk+-3.0 --pkg gio-2.0 -C ) looks like this:

/* main.c generated by valac 0.20.1, the Vala compiler
 * generated from main.vala, do not modify */


#include <glib.h>
#include <glib-object.h>
#include <gtk/gtk.h>
#include <stdlib.h>
#include <string.h>
#include <gio/gio.h>


#define TYPE_MY_APP (my_app_get_type ())
#define MY_APP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_MY_APP, MyApp))
#define MY_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_MY_APP, MyAppClass))
#define IS_MY_APP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_MY_APP))
#define IS_MY_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_MY_APP))
#define MY_APP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_MY_APP, MyAppClass))

typedef struct _MyApp MyApp;
typedef struct _MyAppClass MyAppClass;
typedef struct _MyAppPrivate MyAppPrivate;
#define _g_object_unref0(var) ((var == NULL) ? NULL : (var = (g_object_unref (var), NULL)))
typedef struct _Block1Data Block1Data;

struct _MyApp {
 GtkApplication parent_instance;
 MyAppPrivate * priv;
};

struct _MyAppClass {
 GtkApplicationClass parent_class;
};

struct _MyAppPrivate {
 GtkLabel* label;
 GtkWindow* window;
};

struct _Block1Data {
 int _ref_count_;
 MyApp * self;
 GtkAboutDialog* dialog;
};


static gpointer my_app_parent_class = NULL;

GType my_app_get_type (void) G_GNUC_CONST;
#define MY_APP_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_MY_APP, MyAppPrivate))
enum  {
 MY_APP_DUMMY_PROPERTY
};
static gint my_app_main (const gchar* args, int args_length1);
static MyApp* my_app_new (void);
static MyApp* my_app_construct (GType object_type);
static void my_app_real_activate (GApplication* base);
static void my_app_build_menues (MyApp* self);
static void __lambda2_ (MyApp* self);
static void ___lambda2__g_simple_action_activate (GSimpleAction* _sender, GVariant* parameter, gpointer self);
static void __lambda3_ (MyApp* self);
static Block1Data* block1_data_ref (Block1Data* _data1_);
static void block1_data_unref (void * _userdata_);
static void __lambda4_ (Block1Data* _data1_, gint response_id);
static void ___lambda4__gtk_dialog_response (GtkDialog* _sender, gint response_id, gpointer self);
static void ___lambda3__g_simple_action_activate (GSimpleAction* _sender, GVariant* parameter, gpointer self);
static void __lambda5_ (MyApp* self);
static void ___lambda5__g_simple_action_activate (GSimpleAction* _sender, GVariant* parameter, gpointer self);
static void my_app_finalize (GObject* obj);
static void _vala_array_destroy (gpointer array, gint array_length, GDestroyNotify destroy_func);
static void _vala_array_free (gpointer array, gint array_length, GDestroyNotify destroy_func);


static gint my_app_main (const gchar* args, int args_length1) {
 gint result = 0;
 MyApp* _tmp0_;
 MyApp* app;
 gint _tmp1_ = 0;
 _tmp0_ = my_app_new ();
 app = _tmp0_;
 _tmp1_ = g_application_run ((GApplication*) app, 0, NULL);
 result = _tmp1_;
 _g_object_unref0 (app);
 return result;
}


int main (int argc, char ** argv) {
 g_type_init ();
 return my_app_main (argv, argc);
}


static MyApp* my_app_construct (GType object_type) {
 MyApp * self = NULL;
 self = (MyApp*) g_object_new (object_type, "application-id", "testing.my.application", "flags", G_APPLICATION_FLAGS_NONE, NULL);
 return self;
}


static MyApp* my_app_new (void) {
 return my_app_construct (TYPE_MY_APP);
}


static void my_app_real_activate (GApplication* base) {
 MyApp * self;
 GtkApplicationWindow* _tmp0_;
 GtkWindow* _tmp1_;
 GtkLabel* _tmp2_;
 GtkWindow* _tmp3_;
 GtkLabel* _tmp4_;
 GtkWindow* _tmp5_;
 GtkWindow* _tmp6_;
 self = (MyApp*) base;
 _tmp0_ = (GtkApplicationWindow*) gtk_application_window_new ((GtkApplication*) self);
 g_object_ref_sink (_tmp0_);
 _g_object_unref0 (self->priv->window);
 self->priv->window = (GtkWindow*) _tmp0_;
 my_app_build_menues (self);
 _tmp1_ = self->priv->window;
 gtk_window_set_default_size (_tmp1_, 400, 400);
 _tmp2_ = (GtkLabel*) gtk_label_new ("Hello GTK!");
 g_object_ref_sink (_tmp2_);
 _g_object_unref0 (self->priv->label);
 self->priv->label = _tmp2_;
 _tmp3_ = self->priv->window;
 _tmp4_ = self->priv->label;
 gtk_container_add ((GtkContainer*) _tmp3_, (GtkWidget*) _tmp4_);
 _tmp5_ = self->priv->window;
 gtk_window_set_title (_tmp5_, "Hello GTK!");
 _tmp6_ = self->priv->window;
 gtk_widget_show_all ((GtkWidget*) _tmp6_);
}


static void __lambda2_ (MyApp* self) {
 g_application_quit ((GApplication*) self);
}


static void ___lambda2__g_simple_action_activate (GSimpleAction* _sender, GVariant* parameter, gpointer self) {
 __lambda2_ (self);
}


static Block1Data* block1_data_ref (Block1Data* _data1_) {
 g_atomic_int_inc (&_data1_->_ref_count_);
 return _data1_;
}


static void block1_data_unref (void * _userdata_) {
 Block1Data* _data1_;
 _data1_ = (Block1Data*) _userdata_;
 if (g_atomic_int_dec_and_test (&_data1_->_ref_count_)) {
  MyApp * self;
  self = _data1_->self;
  _g_object_unref0 (_data1_->dialog);
  _g_object_unref0 (self);
  g_slice_free (Block1Data, _data1_);
 }
}


static void __lambda4_ (Block1Data* _data1_, gint response_id) {
 MyApp * self;
 gboolean _tmp0_ = FALSE;
 gint _tmp1_;
 gboolean _tmp3_;
 self = _data1_->self;
 _tmp1_ = response_id;
 if (_tmp1_ == ((gint) GTK_RESPONSE_CANCEL)) {
  _tmp0_ = TRUE;
 } else {
  gint _tmp2_;
  _tmp2_ = response_id;
  _tmp0_ = _tmp2_ == ((gint) GTK_RESPONSE_DELETE_EVENT);
 }
 _tmp3_ = _tmp0_;
 if (_tmp3_) {
  gtk_widget_destroy ((GtkWidget*) _data1_->dialog);
 }
}


static void ___lambda4__gtk_dialog_response (GtkDialog* _sender, gint response_id, gpointer self) {
 __lambda4_ (self, response_id);
}


static void __lambda3_ (MyApp* self) {
 Block1Data* _data1_;
 GtkAboutDialog* _tmp0_;
 GtkWindow* _tmp1_;
 gchar* _tmp2_;
 gchar* _tmp3_;
 gchar** _tmp4_ = NULL;
 gchar** _tmp5_;
 gint _tmp5__length1;
 _data1_ = g_slice_new0 (Block1Data);
 _data1_->_ref_count_ = 1;
 _data1_->self = g_object_ref (self);
 _tmp0_ = (GtkAboutDialog*) gtk_about_dialog_new ();
 g_object_ref_sink (_tmp0_);
 _data1_->dialog = _tmp0_;
 gtk_window_set_destroy_with_parent ((GtkWindow*) _data1_->dialog, TRUE);
 _tmp1_ = self->priv->window;
 gtk_window_set_transient_for ((GtkWindow*) _data1_->dialog, _tmp1_);
 gtk_window_set_modal ((GtkWindow*) _data1_->dialog, TRUE);
 g_signal_connect_data ((GtkDialog*) _data1_->dialog, "response", (GCallback) ___lambda4__gtk_dialog_response, block1_data_ref (_data1_), (GClosureNotify) block1_data_unref, 0);
 _tmp2_ = g_strdup ("Gustav Hartvigsson");
 _tmp3_ = g_strdup ("Some One Else");
 _tmp4_ = g_new0 (gchar*, 2 + 1);
 _tmp4_[0] = _tmp2_;
 _tmp4_[1] = _tmp3_;
 _tmp5_ = _tmp4_;
 _tmp5__length1 = 2;
 gtk_about_dialog_set_authors (_data1_->dialog, _tmp5_);
 _tmp5_ = (_vala_array_free (_tmp5_, _tmp5__length1, (GDestroyNotify) g_free), NULL);
 gtk_about_dialog_set_program_name (_data1_->dialog, "Hello GTK!");
 gtk_window_present ((GtkWindow*) _data1_->dialog);
 block1_data_unref (_data1_);
 _data1_ = NULL;
}


static void ___lambda3__g_simple_action_activate (GSimpleAction* _sender, GVariant* parameter, gpointer self) {
 __lambda3_ (self);
}


static void __lambda5_ (MyApp* self) {
 GtkLabel* _tmp0_;
 _tmp0_ = self->priv->label;
 gtk_label_set_text (_tmp0_, "I have been clicked!");
}


static void ___lambda5__g_simple_action_activate (GSimpleAction* _sender, GVariant* parameter, gpointer self) {
 __lambda5_ (self);
}


static void my_app_build_menues (MyApp* self) {
 GMenu* _tmp0_;
 GMenu* app_menu;
 GSimpleAction* _tmp1_;
 GSimpleAction* quit_action;
 GSimpleAction* _tmp2_;
 GSimpleAction* about_action;
 GSimpleAction* _tmp3_;
 GSimpleAction* message_action;
 g_return_if_fail (self != NULL);
 _tmp0_ = g_menu_new ();
 app_menu = _tmp0_;
 g_menu_append (app_menu, "Message", "app.message");
 g_menu_append (app_menu, "About", "app.about");
 g_menu_append (app_menu, "Quit", "app.quit");
 gtk_application_set_app_menu ((GtkApplication*) self, (GMenuModel*) app_menu);
 _tmp1_ = g_simple_action_new ("quit", NULL);
 quit_action = _tmp1_;
 g_signal_connect_object (quit_action, "activate", (GCallback) ___lambda2__g_simple_action_activate, self, 0);
 g_action_map_add_action ((GActionMap*) self, (GAction*) quit_action);
 _tmp2_ = g_simple_action_new ("about", NULL);
 about_action = _tmp2_;
 g_signal_connect_object (about_action, "activate", (GCallback) ___lambda3__g_simple_action_activate, self, 0);
 g_action_map_add_action ((GActionMap*) self, (GAction*) about_action);
 _tmp3_ = g_simple_action_new ("message", NULL);
 message_action = _tmp3_;
 g_signal_connect_object (message_action, "activate", (GCallback) ___lambda5__g_simple_action_activate, self, 0);
 g_action_map_add_action ((GActionMap*) self, (GAction*) message_action);
 _g_object_unref0 (message_action);
 _g_object_unref0 (about_action);
 _g_object_unref0 (quit_action);
 _g_object_unref0 (app_menu);
}


static void my_app_class_init (MyAppClass * klass) {
 my_app_parent_class = g_type_class_peek_parent (klass);
 g_type_class_add_private (klass, sizeof (MyAppPrivate));
 G_APPLICATION_CLASS (klass)->activate = my_app_real_activate;
 G_OBJECT_CLASS (klass)->finalize = my_app_finalize;
}


static void my_app_instance_init (MyApp * self) {
 self->priv = MY_APP_GET_PRIVATE (self);
}


static void my_app_finalize (GObject* obj) {
 MyApp * self;
 self = G_TYPE_CHECK_INSTANCE_CAST (obj, TYPE_MY_APP, MyApp);
 _g_object_unref0 (self->priv->label);
 _g_object_unref0 (self->priv->window);
 G_OBJECT_CLASS (my_app_parent_class)->finalize (obj);
}


GType my_app_get_type (void) {
 static volatile gsize my_app_type_id__volatile = 0;
 if (g_once_init_enter (&my_app_type_id__volatile)) {
  static const GTypeInfo g_define_type_info = { sizeof (MyAppClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) my_app_class_init, (GClassFinalizeFunc) NULL, NULL, sizeof (MyApp), 0, (GInstanceInitFunc) my_app_instance_init, NULL };
  GType my_app_type_id;
  my_app_type_id = g_type_register_static (GTK_TYPE_APPLICATION, "MyApp", &g_define_type_info, 0);
  g_once_init_leave (&my_app_type_id__volatile, my_app_type_id);
 }
 return my_app_type_id__volatile;
}


static void _vala_array_destroy (gpointer array, gint array_length, GDestroyNotify destroy_func) {
 if ((array != NULL) && (destroy_func != NULL)) {
  int i;
  for (i = 0; i < array_length; i = i + 1) {
   if (((gpointer*) array)[i] != NULL) {
    destroy_func (((gpointer*) array)[i]);
   }
  }
 }
}


static void _vala_array_free (gpointer array, gint array_length, GDestroyNotify destroy_func) {
 _vala_array_destroy (array, array_length, destroy_func);
 g_free (array);
}

If we look at the function my_app_build_menues (line 245) you will see that on line 264 the action is added to the self object (equivilant to this in the vala code). The action adding just passes a function pointer to the function ___lambda3__g_simple_action_activate, and if we follow the path we will see that that function on line 228 calls an other function on line 193.

In the function prior mentioned ( __lambda3 () ) we can see that on line 211 there is another string-up to the function ___lambda4__gtk_dialog_response () on line 187 that runs the function on line 166.

This is what nested lambdas in Vala look like. it is scary and beautiful at the same time. I have no idea if lambdas are slower than just creating a function and using them as parameters to the action/callback/delegates, I just wanted to show of some of the sexy and dirty internals of Vala.