首页 > 解决方案 > 如何在 Crystal 中进行通用记忆?

问题描述

我想在 Crystal 中定义一个通用的记忆包装器。我有以下水晶代码:

  module Scalar(T)
    abstract def value: T
  end

  class ScSticky(T)
    include Scalar(T)

    def initialize(sc : Scalar(T))
      @sc = sc
      @val = uninitialized T
    end

    def value: T
      @val ||= @sc.value
    end
  end

换句话说,我只想ScSticky调用底层Scalar(T)并返回所有后续调用的缓存输出。T但是,如果是,上述方法不起作用Int32

例如,包装此类时


class ScCounter
  include Scalar(Int32)

  def initialize
      @val = 100
  end

  def value: Int32
    @val += 1
    @val
  end
end

ScSticky(ScCounter.new).value将始终等于0(据我了解,因为unitialized Int32实际上是用 0 值初始化的)

我非常感谢您对这个问题的帮助

Upd:似乎实现这一点的正确方法是使用nil,但是我在理解这种实现的外观方面存在问题。我也希望能够记住.value方法,即使它返回nil(换句话说,如果它T是可空类型)

标签: crystal-lang

解决方案


您正在使用一个不安全的功能“ uninitialized”,这意味着,“保留以前内存中的任何内容”(理论上该值是随机的并且可能无效,实际上您通常最终还是 0 - 但它仍然不能保证)。
关于该uninitialized功能的简短故事是请不要使用它

如果你写了这种行为,你不会感到惊讶@val = 0——这就是你写的。

您必须定义@val : T? = nil-- 以使其可空(具有这个单独的可能值nil,这是它自己的类型 - Nil)。
你可能认为这unitialized会带来nil画面,但它绝对不会。


作为对您关于还包括nil可能值的评论的回应,这里有一个完整的解决方案,它使用用户永远无法创建的唯一“哨兵”结构,而不是 Nil。

module Scalar(T)
  abstract def value: T
end
 
private struct Sentinel
end
 
class ScSticky(T)
  include Scalar(T)
 
  @val : T | Sentinel = Sentinel.new
  
  def initialize(@sc : Scalar(T))
  end
 
  def value: T
    val = @val
    if val.is_a?(Sentinel)
      @val = @sc.value
    else
      val
    end
  end
end
 
class ScCounter
  include Scalar(Int32)
 
  def initialize
    @val = 100
  end
 
  def value: Int32
    @val += 1
  end
end
 
sc = ScSticky.new(ScCounter.new)
 
p! sc.value #=> 101
p! sc.value #=> 101

推荐阅读