【问题标题】:Modeling heterogenous graphs in Haskell在 Haskell 中建模异构图
【发布时间】:2018-04-28 02:15:25
【问题描述】:

如何在 haskell 中对我称之为“异构图”的模型进行建模,以便在编译时验证图的类型正确性?

为此,异构图是一组节点,每个节点都有一个特定的类型标签,以及一组边,每个边都有一个源类型标签和一个目标类型标签。

我们希望静态地确保在将边添加到图中时,该边的源类型标签与源节点的类型标签以及该边的目标类型标签匹配匹配目标节点的类型标签。但我们不希望以微不足道的方式做到这一点(通过强制整个图只包含具有一个特定类型标签的节点)。

【问题讨论】:

  • “类型”是指编译时类型吗?如果是这样,他们将需要某种运行时残留物。如果类型集易于枚举,则可能是 GADT,如果类型集不易枚举,则可能使用Typeable
  • 您对在内部使用不安全功能的安全界面感兴趣吗?你的图应该支持哪些操作?并且对节点的类型有什么限制吗?
  • @JoachimBreitner 是的,在内部使用不安全的功能会很好。可键入或可擦除的幻象节点/边缘类型将优于 unsafeCoerce 之类的类型,但如果它是必要且外部安全的,则任何事情都会发生。既然您问了,一旦知道图形的类型正确,将其转换为同质表示可能就足够了。也许在这个意义上检查同质表示的正确性的 TH 拼接可能满足我的所有需求,我会稍微探索一下这条路径。
  • 我认为可以有一个实现来存储Any 类型的节点,使用标记类型公开接口,在内部使用unsafeCoerce,也许还有更高级别的类型(如runST)确保不同图形的节点不会混淆。但是先看看你的“梦想界面”会很有帮助。

标签: haskell graph-theory dsl typechecking


【解决方案1】:

我不确定如何在编译时强制执行此操作——我认为它要求你的图表是完全静态的?——但在运行时使用 Typeable 强制执行相对简单。这是它的样子的草图。首先,我将从输入NodeEdge 类型开始:

data Node a = Node a
data Edge a b = Edge !Int !Int

用existentials包装它们:

{-# LANGUAGE ExistentialQuantification #-}

import Data.Typeable

data SomeNode
  = forall a. (Typeable a)
  => SomeNode (Node a)

data SomeEdge
  = forall a b. (Typeable a, Typeable b)
  => SomeEdge (Edge a b)

拥有使用存在量化类型的异构图数据类型:

import Data.IntMap (IntMap)

-- Not a great representation, but simple for illustration.
data Graph = Graph !(IntMap SomeNode) [SomeEdge]

然后是执行动态类型检查的操作:

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

import qualified Data.IntMap as IntMap

addNode
  :: forall a. (Typeable a)
  => Int -> a -> Graph -> Maybe Graph
addNode i x (Graph ns es) = case IntMap.lookup i ns of

  -- If a node already exists at a given index:
  Just (SomeNode (existing :: Node e)) -> case eqT @e @a of

    -- Type-preserving replacement is allowed, but…
    Just Refl -> Just $ Graph ns' es

    -- …*type-changing* replacement is *not* allowed,
    -- since it could invalidate existing edges.
    Nothing -> Nothing

  -- Insertion is of course allowed.
  Nothing -> Just $ Graph ns' es

  where
    ns' = IntMap.insert i (SomeNode (Node x)) ns

-- To add an edge:
addEdge
  :: forall a b. (Typeable a, Typeable b)
  => Edge a b -> Graph -> Maybe Graph
addEdge e@(Edge f t) (Graph ns es) = do

  -- The ‘from’ node must exist…
  SomeNode (fn :: Node tfn) <- IntMap.lookup f ns
  -- …and have the correct type; and
  Refl <- eqT @a @tfn

  -- The ‘to’ node must exist…
  SomeNode (tn :: Node ttn) <- IntMap.lookup t ns
  -- …and have the correct type.
  Refl <- eqT @b @ttn

  pure $ Graph ns $ SomeEdge e : es

现在成功了:

pure (Graph mempty mempty)
  >>= addNode 0 (1 :: Int)
  >>= addNode 1 ('x' :: Char)
  >>= addEdge (Edge 0 1 :: Edge Int Char)

但是将Edge Int Char 中的Int/Char 更改为无效类型,或将0/1 更改为无效索引,将失败并返回Nothing

【讨论】:

  • 这是一个很好的动态解决方案。接受图是静态的条件(例如,在用于描述机电系统或键合图的 EDSL 中),您是否看到任何通往静态检查解决方案的途径?
  • @DougMcClean:您能否详细解释一下您的用例以及您需要哪些操作?我正在使用像data Graph (ns :: [(Nat, *)]) (es :: [(Nat, Nat)]) = Graph (Nodes ns) (Edges es) 这样的解决方案,其中整个图形结构都以类型编码,NodesEdges 是为您提供运行时值字段的类型族。 API 可以是emptyGraph &amp; addNode @0 (1 :: Int) &amp; addNode @1 ('x' :: Char) &amp; addEdge @0 @1 (Edge 0.5 :: Edge Int Char) :: Graph '[(1, Char), (0, Int)] '[(0, 1)],使用TypeApplications 来指定标签——也可以使用Symbol 而不是Nat
  • 我的用例是为自动化系统建模。 Node Device 将代表物理设备(如电机或传感器)的内部逻辑/行为,并通过 Edge Device ElectricalPort 连接到电气端口。静态检查将确保 wire :: Edge ElectricalPort ElectricalPort 不能用于 tubing :: Edge PneumaticPort PneumaticPort 的预期用途。类型标签是幻像,一旦检查就可以删除,或者转换为一些简单的术语级别表示,因此查询界面可以检查标签并在Maybe内返回。
  • 理想情况下,可以通过某种方式添加整个子图,就好像它是一条边一样,例如pressureSensor :: ActsLikeAnEdge PneumaticPort ElectricalPort,实际上压力传感器是用一些内部细节建模的。
  • @DougMcClean:嗯,我想知道你是否想要Arrows?例如,使用type Edge i o = Kleisli IO i o; type Source o = Edge () o; type Sink i = Edge i (),您可以编写诸如proc (power, pneumatic) -&gt; do { power &lt;- powerCable &lt;&lt;&lt; battery -&lt; (); sensor &lt;- dataCable &lt;&lt;&lt; pressureSensor -&lt; pneumatic; result &lt;- dataCable &lt;&lt;&lt; device -&lt; (power, sensor); display -&lt; result } 之类的内容,并使用诸如battery :: Source PowerOutpowerCable :: Edge PowerOut PowerIndevice :: Edge (PowerIn, DataIn Double) (DataOut Double)display :: Sink (DataOut Double) 之类的内容的适当定义...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多