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

How do I make generic memoization in Crystal?

我想在 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) 一次,return 缓存输出用于所有后续调用。 但是,如果 TInt32

,则上述方法不起作用

例如,包装这个 class


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 方法,即使它 returns nil (换句话说,如果 T 是可空类型)

您正在使用不安全的功能“uninitialized”,这意味着“保留之前内存中的任何内容”(理论上该值是随机的并且可能无效,实际上您通常以 0 结尾无论如何——但仍然不能保证。
关于 uninitialized 功能的小故事是 请永远不要使用它

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

您必须定义 @val : T? = nil —— 使其变为 nilable(具有 nil 的单独可能值,这是它自己的类型 - Nil)。
您可能认为 unitialized 会带来 nil,但事实并非如此。


为了回应您关于也将 nil 包含在可能值中的评论,这里有一个完整的解决方案,它使用用户永远无法创建的独特“sentinel”结构代替 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