在榆木中管理不可能的状态有什么好的模式?

问题描述 投票:2回答:2

也许您可以提供帮助。我是Elm的初学者,并且遇到了相当平凡的问题。我对Elm感到非常兴奋,并且在较小的事情上取得了相当的成功,所以现在我尝试了一些更复杂的事情,但似乎还是无法解决。

我正在尝试在Elm中构建一些使用类似图的底层数据结构的东西。我使用这样的流畅/工厂模式创建图形:

sample : Result String MyThing
sample =
  MyThing.empty
    |> addNode 1 "bobble"
    |> addNode 2 "why not"
    |> addEdge 1 2 "some data here too"

[当此代码返回Ok MyThing时,将以一致的方式设置整个图,并确保所有节点和边都具有所需的数据,并且所有节点的边实际上都存在。

实际代码具有与节点和边关联的更复杂的数据,但这与问题无关紧要。在内部,节点和边存储在Dict Int element中。

type alias MyThing =
  { nodes : Dict Int String
  , edges : Dict Int { from : Int, to : Int, label : String } 
  }

现在,在模块的用户中,我想访问图形的各个元素。但是,每当我使用Dict.get访问节点或边之一时,都会得到Maybe。这很不方便,因为借助我的构造函数代码,我know索引存在,等等。当我know一个索引时,我不想用MaybeResult弄乱上游代码。边缘存在。举个例子:

getNodeTexts : Edge -> MyThing -> Maybe (String, String)
getNodeTexts edge thing =
  case Dict.get edge.from thing.nodes of
    Nothing ->
      --Yeah, actually this can never happen...
      Nothing
    Just fromNode -> case Dict.get edge.to thing.nodes of
      Nothing -> 
        --Again, this can never actually happen because the builder code prevents it.
        Nothing
      Just toNode ->
        Just ( fromNode.label, toNode.label )

这只是很多样板代码,用于处理我在工厂代码中特别禁止的内容。但是更糟的是:现在,消费者需要额外的样板代码来处理Maybe-可能不知道Maybe实际上会是[[never是Nothing。该API对消费者而言是一种谎言。这不是Elm想要避免的事情吗?与假设但不正确的比较:

getNodeTexts : Edge -> MyThing -> (String, String) getNodeTexts edge thing = ( Dict.get edge.from thing.nodes |> .label , Dict.get edge.to thing.nodes |> .label )
一种替代方案是不使用Int ID,而是使用实际数据-但由于连接器可能有许多边缘,因此更新事情变得非常繁琐。没有通过Int解耦来管理状态似乎不是一个好主意。

我觉得必须使用不透明的ID类型解决这个难题,但我只是看不到。我将不胜感激任何指针。

注意:我也尝试同时使用drathierelm-community榆木图库,但是它们没有解决特定的问题。它们也依赖于下面的Dict,所以我最终得到相同的Maybe

graph functional-programming elm
2个回答
1
投票
您的问题没有简单的答案。我可以提供一个评论和编码建议。

您使用了魔术词“不可能的状态”,但是正如OOBalance指出的那样,您可以在建模中创建不可能的状态。 Elm中“不可能的状态”的正常含义恰好与建模有关,例如当您使用两个Bool表示3种可能的状态时。在Elm中,您可以为此使用自定义类型,而不必在代码中保留布尔值的一种组合。

至于您的代码,您可以通过以下方式减少其长度(甚至可以减少复杂性)

getNodeTexts : Edge -> MyThing -> Maybe ( String, String ) getNodeTexts edge thing = Maybe.map2 (\ n1 n2 -> ( n1.label, n2.label )) (Dict.get edge.from thing.nodes) (Dict.get edge.to thing.nodes)


1
投票
根据您的描述,在我看来这些状态实际上并非不可能。

让我们开始定义MyThing

type alias MyThing = { nodes : Dict Int String , edges : Dict Int { from : Int, to : Int, label : String } }

这是类型别名,而不是类型–意味着编译器将接受MyThing代替{nodes : Dict Int String, edges : Dict Int {from : Int, to : Int, label : String}},反之亦然。

因此,与其使用您的工厂函数安全地构造MyThing值,我不可以写:

import Dict myThing = { nodes = Dict.empty, edges = Dict.fromList [(0, {from = 0, to = 1, label = "Edge 0"})] }

…,然后将myThing传递给期望使用MyThing的任何函数,即使通过[[Edge 0连接的节点不包含在myThing.nodes中。

您可以通过将MyThing更改为自定义类型来解决此问题:

type MyThing = MyThing { nodes : Dict Int String , edges : Dict Int { from : Int, to : Int, label : String } }

…,并使用exposing (MyThing)而不是exposing (MyThing(..))进行曝光。这样,就不会暴露MyThing的构造函数,并且模块外部的代码必须使用工厂函数来获取值。

Edge同样适用,我假设定义为:

type alias Edge = { from : Int, to : Int, label : String }

除非将其更改为自定义类型,否则构造任意Edge值很简单:

type Edge
    = Edge { from : Int, to : Int, label : String }

但是,您将需要公开一些函数以获得Edge值,以传递给类似getNodeTexts的函数。假设我已经获得了MyThing及其边缘之一:

myThing : MyThing
-- created using factory functions

edge : Edge
-- an edge of myThing

现在我创建另一个MyThing值,并将其与getNodeTexts一起传递给edge

myOtherThing : MyThing
-- a different value of type MyThing

nodeTexts = getNodeTexts edge myOtherThing

这应该返回Maybe.NothingResult.Err String,但肯定不返回(String, String) –边不属于myOtherThing,因此不能保证其节点包含在其中。

© www.soinside.com 2019 - 2024. All rights reserved.