

Perl で文字列を生成するとき、よく `Use of uninitialized value concatenation`
を出してしまうので、どうにか解消したいと考えています。

これは初期化されていない(というか、undef
が入っている)変数を文字列として結合したときに出ますが、毎回必ず初期値を入れるのはコード量が多くなるので嬉しくありません。

undef の場合に、空白になるようなイディオムはないのでしょうか?

例えば Ruby だと、変数が nil のとき、`+` で文字列結合しようとするとエラーになりますが、  
式展開するとエラーになりません。  
( `nil.to_s => ''` になるので )

[code]

    irb(main):005:0> a = nil
    => nil
    irb(main):006:0> a + "bbb"
    NoMethodError: undefined method `+' for nil:NilClass
            from (irb):6
            from /usr/bin/irb:11:in `<main>'
    irb(main):007:0> "#{a}bbb"
    => "bbb"
    
[/code]

よって、Ruby では「文字列を生成するときは、必ず式展開を使う」というルールにしてしまえば、この系統のエラーは防げます。

一方、Perl では、文字列結合でも、変数展開でも、エラーはでませんが、どちらでも warning が出ます。

[code]

    24> $b = undef
    $res[18] = undef
    
    25> $b . "222"
    Use of uninitialized value $b in concatenation (.) or string at reply input line 1.
    $res[19] = '222'
    
    31> "${b}222"
    Use of uninitialized value $b in concatenation (.) or string at reply input line 1.
    $res[22] = '222'
    
[/code]

この warning を出さないようにするには、どう書くのが良いでしょうか。

`追記 2016-10-18`

提示した例が、意図を説明できない例だったので、追記します。  
(変数を宣言するときに初期値を入れるのは、当然ですよね……)

(id と 名前) のハッシュと、(id と 年齢) のハッシュ、があるとき、それを合成して表示することを考えます。  
但し、年齢があるとは限りません。

[code]

    21> my $ids_names = { 10 => '佐藤一郎',  20 => '鈴木二郎',  30 => '田中三郎' }
    $res[15] = {
      '10' => '佐藤一郎',
      '20' => '鈴木二郎',
      '30' => '田中三郎'
    }
    
    22> my $ids_ages = { 10 => 40, 30 => 20 };
    $res[16] = {
      '10' => 40,
      '30' => 20
    }
    
    32> map { $ids_names->{$_} . " (" . $ids_ages->{$_} . ") さん" } keys %$ids_names;
    Use of uninitialized value in concatenation (.) or string at reply input line 1.
    $res[21] = [
      '鈴木二郎 () さん',
      '田中三郎 (20) さん',
      '佐藤一郎 (40) さん'
    ]
    
[/code]

このとき、`Use of uninitialized value in concatenation` を避けるイディオムは、ないでしょうか?

ruby は、エラーになりませんし、

[code]

    irb(main):001:0> ids_names = { 10 => '佐藤一郎',  20 => '鈴木二郎',  30 => '田中三郎' }
    => {10=>"佐藤一郎", 20=>"鈴木二郎", 30=>"田中三郎"}
    irb(main):002:0> ids_ages = { 10 => 40, 30 => 20 }
    => {10=>40, 30=>20}
    irb(main):008:0> ids_names.map { |id, name| "#{name} (#{ids_ages[id]}) さん" }
    => ["佐藤一郎 (40) さん", "鈴木二郎 () さん", "田中三郎 (20) さん"]
    
[/code]

python は get で初期値が使えます。

[code]

    In [1]: ids_names = {10: '佐藤一郎',  20: '鈴木二郎',  30: '田中三郎' }
    In [2]: ids_ages = { 10: 40, 30: 20 }
    In [7]: ["{} ({}) さん".format(name, ids_ages.get(id, '')) for (id, name) in ids_names.items()]
    Out[7]: ['佐藤一郎 (40) さん', '鈴木二郎 () さん', '田中三郎 (20) さん']
    
[/code]

perl も python に習って、

[code]

    32> map { $ids_names->{$_} . " (" . ($ids_ages->{$_} // '') . ") さん" } keys %$ids_names;
    
[/code]

のように書くのか、もっと良いイディオムがあるのか、知りたいと考えています。  
(それなりに頻出だと思うのですが……)


CC BY-SA 3.0
2016-10-17T08:01:51.200
29627
2016-10-18T16:24:47.207
2016-10-18T09:54:01.660
7471
7471
question
perl
Perl で `Use of uninitialized value concatenation` を出さないイディオムはありませんか?


特定のスコープだけ対象の警告を抑制すればよいのではないですか？

[code]

    #!/usr/bin/perl
    use strict;
    use warnings;
    use Data::Dumper;
    
    my $ids_names = +{
        10 => '佐藤一郎',
        20 => '鈴木二郎',
        30 => '田中三郎',
    };
    
    my $ids_ages = +{
        10 => 40,
        30 => 20,
    };
    
    print Dumper map {
        no warnings 'uninitialized';
        sprintf '%s (%s) さん', $ids_names->{$_}, $ids_ages->{$_};
    } sort keys %{$ids_names};
    
[/code]

もしくはno
warningsを使わずundefを空文字列で扱いたい意図を明確にするなら、`sprintf`の第二引数以降をmapに食わせればいいと思います。

[code]

    print Dumper map {
        sprintf '%s (%s) さん',
            map { $_ // q{} } $ids_names->{$_}, $ids_ages->{$_};
    } sort keys %{$ids_names};
    
[/code]


CC BY-SA 3.0
2016-10-18T13:42:56.927
29672
2016-10-18T13:42:56.927
62
29627
answer


イディオムではないのですが、`Tie::Hash` を使う方法などを。

`FETCH` メソッドをオーバーライドしてデフォルト値(ここでは空文字列)を返すようにしています。

[code]

    use strict;
    use warnings;
    
    package DefaultHash;
    
    use Tie::Hash;
    use base qw(Tie::StdHash);
    
    sub FETCH {
      my ($self, $key) = @_;
      return exists $self->{$key} ? $self->{$key} : ''
    }
    
    package main;
    
    use Data::Dumper;
    
    my $ids_names = { 10 => '佐藤一郎',  20 => '鈴木二郎',  30 => '田中三郎' };
    
    tie my %ids_ages_hash, 'DefaultHash';
    %ids_ages_hash = ( 10 => 40, 30 => 20 );
    my $ids_ages = \%ids_ages_hash;
    
    print Dumper map { $ids_names->{$_} . " (" . $ids_ages->{$_} . ") さん" } keys %$ids_names;
    
[/code]


CC BY-SA 3.0
2016-10-18T16:24:47.207
29676
2016-10-18T16:24:47.207
29627
answer