问题描述
我利用了这样一个事实:当JVM创建一个对象(无论是否不可变)时,其指针是在初始化其字段之前创建的。
这使我可以创建如下内容:
(py3+) [nuwjtv5@rh6-rd-ho-cloudera-07 ~]$ bat packages.jl
───────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ File: packages.jl
───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ using Pkg
2 │ Pkg.update()
3 │ Pkg.add("Pluto")
───────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
(py3+) [nuwjtv5@rh6-rd-ho-cloudera-07 ~]$ julia --version
julia version 1.5.2
(py3+) [nuwjtv5@rh6-rd-ho-cloudera-07 ~]$ julia packages.jl
Installing kNown registries into `~/.julia`
######################################################################## 100.0%
Added registry `General` to `~/.julia/registries/General`
Updating registry at `~/.julia/registries/General`
No Changes to `~/.julia/environments/v1.5/Project.toml`
No Changes to `~/.julia/environments/v1.5/Manifest.toml`
Resolving package versions...
julia: /buildworker/worker/package_linux64/build/src/cgmemmgr.cpp:831: virtual uint8_t* {anonymous}::RTDyldMemoryManagerJL::allocateCodeSection(uintptr_t,unsigned int,llvm::StringRef): Assertion `!code_allocated' Failed.
signal (11): Segmentation fault
in expression starting at /export/home/nuwjtv5/packages.jl:3
Segmentation fault
(据我所知)Haskell并非如此,如果确实如此,Haskell不会给我提供利用该工具的工具(不会引用“ this”)。
所以我想知道,如何在Haskell中实现这一目标?
我认为,class BackRefdNode(val parent:Option[BackRefdNode],node:ClassicNode){
val children=node.children.map{c=>
new BackRefdNode(Some(this),c)
}
函数也许可以解决问题,但这实际上并没有给我“ this”引用,而是引用了经过计算的thunk,理论上它具有相同的结构作为创建的BackRefdNode
解决方法
实际上,Haskell在这里又走了一步。它是惰性计算的,这意味着您可以在初始化之前获取对任何东西的引用,而不仅仅是具有字段的对象。使用数据类型
data ClassicNode = ClassicNode { children :: [ClassicNode] }
data BackRefdNode = BackRefdNode { parent :: Maybe BackRefdNode,children :: [BackRefdNode] }
您可以创建一个函数
backRefdNode :: Maybe BackRefdNode -> ClassicNode -> BackRefdNode
backRefdNode parent node = let result = BackRefdNode parent (backRefdNode result <$> children node)
in result
请注意在初始化result
本身的表达式中如何引用result
。这样可以很好地工作,并且可以有效地共享其中包含循环引用的树对象。
要弄清这种数据结构,要比Scala难得多,因为在Haskell中没有引用eq
实体。 BackRefdNode
的每个子代都将其作为父代的不变性无法测试,必须从构造中对其进行证明。
不是Scala代码
trait ClassicNode {
def children: List[ClassicNode]
}
class BackRefdNode(val parent: Option[BackRefdNode],node: ClassicNode) {
val children = node.children.map { c =>
new BackRefdNode(Some(this),c)
}
}
类似于Haskell代码
data ClassicNode
data BackRefdNode = BRN { parent :: Maybe BackRefdNode,node :: ClassicNode }
children1 :: ClassicNode -> [ClassicNode]
children1 _ = undefined
children :: BackRefdNode -> [BackRefdNode]
children this = map (\c -> BRN (Just this) c) (children1 (node this))
?
或者在Haskell中使用类型类
class GetChildren a where
children :: a -> [a]
data ClassicNode
data BackRefdNode = BRN { parent :: Maybe BackRefdNode,node :: ClassicNode }
instance GetChildren ClassicNode where
children _ = undefined
instance GetChildren BackRefdNode where
children this = map (\c -> BRN (Just this) c) (children (node this))
即双重翻译成Scala
trait ClassicNode
class BackRefdNode(val parent: Option[BackRefdNode],val node: ClassicNode)
trait GetChildren[A] {
def children(a: A): List[A]
}
object GetChildren {
implicit val classicNodeGetChildren: GetChildren[ClassicNode] = _ => ???
implicit val backRefdNodeGetChildren: GetChildren[BackRefdNode] = a =>
a.node.children.map { c =>
new BackRefdNode(Some(a),c)
}
}
implicit class GetChildrenOps[A](val a: A) extends AnyVal {
def children(implicit getChildren: GetChildren[A]): List[A] =
getChildren.children(a)
}
也许您的意思是在Java this
中,调度是动态的(与类型类的静态调度相反)。那请看
Is the dispatch of a Haskell TypeClass dynamic?
Does GHC use dynamic dispatch with existential types?
,更一般地,您可以创建这种周期性,而无需使用Future
/ Promise
组合(或就此而言,则来自Cats Effect的Deferred
)来利用JVM行为:
class BackRefdNode(val parent: Option[BackRefdNode]) {
private[this] val leftPromise: Promise[Option[BackRefdNode]]()
private[this] val rightPromise: Promise[Option[BackRefdNode]]()
// leftChild.value will be:
// None if we haven't completed yet
// Some(Success(None)) if there will never be a left child
// Some(Success(Some(node))) if node is the left child
// (technically this Future never fails,but that's an implementation detail
def leftChild: Future[Option[BackRefdNode]] = leftPromise.future
def rightChild: Future[Option[BackRefdNode]] = rightPromise.future
def leftChildIs(nodeOpt: Option[BackRefdNode]): Try[Unit] =
Try { leftPromise.success(nodeOpt) }
def rightChildIs(node: Option[BackRefdNode]): Try[Unit] =
Try { rightPromise.success(nodeOpt) }
}
您通过使循环的一个方向为monadic(-ish)来支付价格,但请注意,您根本不依赖this
或其他各种JVM实现。
因此,如果有与Scala的Promise
/ Future
等效的Haskell(perhaps Data.Promise?),则翻译应该简单明了。