如何在Haskell中创建相互引用的数据结构?

问题描述

我利用了这样一个事实:当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中,调度是动态的(与类型类的静态调度相反)。那请看

Dynamic dispatch in Haskell

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?),则翻译应该简单明了。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...