我有data type
G
,其中包含字段_repr :: Data.Graph.Inductive.Gr String String
。通常的方式是,在将新节点添加到Gr
图中时,我们必须提供一个LNode a
对象,它基本上被定义为(Int, a)
的元组,其中Int是Graph中的节点索引 - 请参阅下面的示例函数add
。
我想实现一个函数addx
,它会自动计算索引(例如使用Data.Graph.Inductive.newNodes
函数)。我希望addx
具有addx :: String -> G -> Int
的签名,此函数将计算新的自由索引,修改图G并返回此计算的索引。在Haskell中是否可以创建这样的函数(在这种情况下将修改现有对象 - G
) - 使用镜头或类似的东西?
我看过,Haskell镜头定义为lens :: (a -> c) -> (a -> d -> b) -> Lens a b c d
,镜头基本上是“getter”和“setter”,因此它的签名允许不同类型的getter输出(c
),setter值(d
)和setter输出(b
)。
import qualified Data.Graph.Inductive as DG
data G = G { _repr :: DG.Gr String String, _name::String} deriving ( Show )
empty :: G
empty = G DG.empty ""
add :: DG.LNode String -> G -> G
add node g = g{_repr = DG.insNode node $ _repr g}
-- is it possible to define it?
addx :: String -> G -> Int
addx name g = undefined
main :: IO ()
main = do
let g = add (1, "test2")
$ add (0, "test1")
$ empty
n1 = addx "test2" g
g2 = DG.insEdge(n1,0)
$ DG.insEdge(0,1)
print $ g
答案 0 :(得分:2)
addx
的类型已被破坏,因为您无法在不返回修改后的表单G
的情况下修改纯函数中的addx1 :: String -> G -> (Int, G)
。如果你对Haskell monad有一个聪明的眼睛,你可能会注意到它有一个同构类型,addx2 :: String -> State G Int
。
我们可以将所有内容与这种“有状态”的方向对齐
add' node = do g <- get
put $ g { _repr = DB.insNode node $ _repr g }
使镜片更加简洁
add'' node = repr %= DB.insNode node
这里真正的挑战是,在一天结束时,跟踪节点身份。一种方法是将其与您的类型中的repr
一起携带
data G = G { _repr :: DG.Gr String String, _name :: String, _index :: Int }
empty = G DG.empty "" 0
然后在构建节点时使用它(再次使用镜头!)
addx' name = do i <- use index
repr %= DB.insNode (i, node)
i += 1