创建一个在模块外部是只读的哈希,但是在里面读/写

问题描述 投票:8回答:2

我正在创建一个具有一些相当重叠的嵌套哈希的模块。哈希需要由模块半定期修改,不幸的是,使用Map排除。

通常,嵌套散列的一个分支将返回给模块[1]的用户,最简单的方法是返回嵌套的散列,例如:

return %data{$branch}{$subbranch} 
# ↪︎ %(subsubbranch1 => ... , subsubbranch2 => ... )

但是,像数组或散列这样的容器的性质是,虽然您可以将它们设置为只读,但仍可以修改键/值。由于多种原因,模块用户实际上不应该实际修改这些值。胁迫到Map无济于事,因为如果任何值也是容器,它们也可以修改。

我的第一个想法是继承Hash(或以其他方式制作一个自定义的Associative),但默认情况下自动恢复仍然是哈希。但是,通过覆盖AT-KEYASSIGN-KEY可以很容易地解决这个问题,这样如果密钥不存在,AT-KEY就会返回子类的实例:

class ProtectedHash is Hash {
    has %!hash = ();
    method EXISTS-KEY ($key)         { %!hash{$key}:exists   }
    method ASSIGN-KEY ($key, \value) { %!hash{$key} = value  }
    method AT-KEY     ($key) {
        %!hash{$key} := ProtectedHash.new unless %!hash{$key}:exists;
        %!hash{$key};
    }
}

如果从模块外部调用ASSIGN-KEY(或AT-KEY的autovivification部分),我想要做的就是失败。我想过使用类似$?MODULE的东西,但是这会在编译时设置并且始终为真。看起来我可以稍微摆脱Backtrace并检查调用的文件的名称,但是我可以假设对这两个函数的调用跟踪有多一致?

例如,对于ASSIGN-KEY我有:

method ASSIGN-KEY ($key, \value) { 
    my @trace = Backtrace.new.list[3..*];
        # The first three can be ignored:
        # 0: code          at ...Backtrace.pm6 
        # 1: method new    at ...Backtrace.pm6
        # 2: method AT-KEY at ...ThisFile.pm6
    if/unless ??? {
        %!hash{$key} = value
    }
}

AT-KEY通常由sub postcircumfix<{ }>调用(在这种情况下@trace[0]可以忽略,trace[1]将是感兴趣的)但也可能,虽然很少,直接调用,在这种情况下trace[0]是我想要验证的地方文件名。

有没有其他常见的方式可以调用AT-KEYASSIGN-KEY?或者应该检查这两个步骤占99.9%的那些方法的调用? [2]


[1]用户可能只想操作几个subx4分支,因此我认为最好为他们提供必要更慢的.Hash方法,以便他们真正需要时,而不是假设他们总是需要一个可操作的容器。有时这些可能被称为足够(特别是通过get-branch($foo){$subbranch}{$subsubbranch}模式),创建哈希深层克隆的额外开销变得相当重要。 [2]我并不太关心防止任何访问(虽然我很好奇,如果纯粹通过子类化可能会这样做),因为我确信一个相当勤奋的编码器总是可以解决问题,但我想抓住最常见的一种方式说“不能碰这个!” (提示90年代的音乐......)并提供Awesome错误消息。

hash immutability perl6
2个回答
8
投票

通过返回包装原始ArrayHash的东西,或者使用but做一个浅拷贝并混合到它(这意味着你保留原始类型)来实现这一点可能更容易。

我们可以声明这样的角色:

role Can'tTouchThis {
    method AT-KEY(|) {
        untouchable callsame
    }

    method ASSIGN-KEY(|) {
        die "Cannot assign to this";
    }

    method AT-POS(|) {
        untouchable callsame
    }

    method ASSIGN-POS(|) {
        die "Cannot assign to this";
    }
}

sub untouchable定义为:

multi untouchable(Positional \p) {
    p but Can'tTouchThis
}
multi untouchable(Associative \a) {
    a but Can'tTouchThis
}
multi untouchable(\o) {
    o
}

因此,在访问时处理嵌套数据结构 - 为那些创建只读外观。

这是一个示例和一些测试用例来说明效果:

class Example {
    has %!foo = a => [ 1, 2, [ 3, 4] ], b => { c => { d => 42, e => 19 }, f => 100 };

    method get($sym) {
        untouchable %!foo{$sym}
    }
}

given Example.new {
    use Test;

    # Positional cases
    is .get('a')[0], 1;
    is .get('a')[2][1], 4;
    dies-ok { .get('a')[1] = 42 };
    is .get('a')[1], 2;

    # Associative cases
    is .get('b')<c><d>, 42;
    dies-ok { .get('b')<f> = 99 };
    dies-ok { .get('b')<c><d> = 99 };
    is .get('b')<f>, 100;
    is .get('b')<c><d>, 42;

    # Auto-viv also doesn't work
    dies-ok { .get('a')[4]<a> = 99 };
    dies-ok { .get('a')[4][0] = 99 };
}

删除untouchable方法中的get调用,以查看此处的大多数测试由于缺乏保护而失败。


4
投票

我最终使用的解决方案满足了我的需求,我在这里发布它可能会遇到类似情况的人。 (不幸的是,角色混合的答案无法生存下来)

我的最终方法是最担心无意识的编辑。为了防止这种情况,我创建了一个名为DB-Item的Associative类,它在内部有一个哈希值。 AT-KEY方法返回哈希中的项目(如果存在),但ASSIGN-KEYBIND-KEY只是立即失败并显示相应的错误消息。唯一的另一种方法是ADD-TO-DATABASE。该方法根据传递的内容处理添加叶子/分支(通常最终用户应该警惕直接使用所有大写方法)。由于分支可以具有不同的长度,这也极大地简化了初始数据库创建:

class DB-Item does Associative {

  has %!hash   = ();
  my  $epitaph = "Modification of the database is not a good idea:\n" ~
                 "  - Use .clone if you want to get a editable branch.\n" ~ 
                 "  - If you really know what you're doing, use .ADD-TO-DATABASE";

  method ADD-TO-DATABASE (*@branch) {
    if @branch == 2 {
      %!hash{@branch.head} = @branch.tail
    }else{
      %!hash{@branch.head} = DB-Item.new;
      %!hash{@branch.head}.ADD-TO-DATABASE(@branch[1..*]);
    }
  }

  method ASSIGN-KEY(|) is hidden-from-backtrace { die $epitaph }
  method BIND-KEY(|)   is hidden-from-backtrace { die $epitaph }

  method EXISTS-KEY($key) { %!hash{$key}:exists                        }
  method AT-KEY($key)     { %!hash{$key}:exists ?? %!hash{$key} !! Nil }

  method clone { ... }
}
© www.soinside.com 2019 - 2024. All rights reserved.