如何在 Crystal 中进行通用记忆?

mon*_*ula 3 crystal-lang

我想在 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
Run Code Online (Sandbox Code Playgroud)

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

例如,当包装这个类时


class ScCounter
  include Scalar(Int32)

  def initialize
      @val = 100
  end

  def value: Int32
    @val += 1
    @val
  end
end
Run Code Online (Sandbox Code Playgroud)

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

我非常感谢有关此问题的帮助

更新:似乎实现这一点的正确方法是使用nil,但是我在理解这种实现到底应该是什么样子方面遇到了问题。我也希望能够记住.value方法,即使它返回nil(换句话说,如果它T是一个 nilable 类型)

Ole*_*pin 5

您正在使用不安全的功能“ 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
Run Code Online (Sandbox Code Playgroud)