четверг, 26 ноября 2015 г.

Упрощаем работу с многоуровневыми структурами данных из внешних источников

В Perl, благодаря "самооживлению" ссылок, очень удобна работа с многоуровневыми структурами данных. Например:
 my %foo = ();
 
 my $k = "k";
 $foo{$k}{a} = "b";
 $foo{$k}{c} = "d";
Однако, если %foo - это внешняя база данных, например BerkeleyDB, то значения необходимо запаковать тем же Storable или JSON.
При этом удобство работы с многоуровневыми структурами данных снижается.

Необходимо извлечь значение, распаковать его, а если значения не было, то создать. Затем после изменения - запаковать и поместить обратно.
 my %foo = ();

 my $k = "k";

 my $f;
 if (my $_f = $foo{$k}) {
  $f = decode_json $_f;
 } else {
  $f = {};
 }

 $$f{a} = "b";
 $$f{c} = "d";

 $foo{$k} = encode_json $f;
Теперь представим, что это необходимо делать в разных местах программы.
Проще написать функцию для внесения изменений, которая будет вызываться примерно вот как:
 $foo->($k, sub {
  my ($v) = @_;
  $$v{a} = "b";
  $$v{c} = "d";
  return $v;
 } );
Немножко многословно. Но в Perl есть "магическая" переменная $_, которая позволяет сделать следующие:
 $foo->($k, sub {
  $$_{a} = "b";
  $$_{c} = "d";
 } );
Соответственно, сама функция будет выглядеть так:
 my %foo = ();
 
 my $foo = sub {
  my ($k, $sub) = @_;

  local $_;

  if (my $_f = $foo{$k}) {
   $_ = decode_json $_f;
  } else {
   $_ = {};
  }

  $sub->();

  $foo{$k} = encode_json $_;
 };
Хотя можно и так:
 sub foo(&$);

 my %foo = ();

 local *foo = sub {
  my ($sub, $k) = @_;

  local $_;

  if (my $_f = $foo{$k}) {
   $_ = decode_json $_f;
  } else {
   $_ = {};
  }

  $sub->();

  $foo{$k} = encode_json $_;
 };

 foo {
  $$_{a} = "b";
  $$_{c} = "d";
 } "k";
Даже не знаю как лучше... :-)