如何在 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 缓存输出用于所有后续调用。
但是,如果 T
是 Int32
,则上述方法不起作用
例如,包装这个 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
我想在 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 缓存输出用于所有后续调用。
但是,如果 T
是 Int32
例如,包装这个 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