Wojciech Danilo
Wojciech Danilo

Reputation: 11803

Different setter and getter types in Haskell's lenses

I've got a data type G, which have got field _repr :: Data.Graph.Inductive.Gr String String. The normal way, when adding new node into Gr graph, we have to provide an LNode a object, which basically is defined as a tuple of (Int, a), where Int is the nodes index in Graph - see the example function add below.

I want to implement a function addx, which will compute the index automatically (for example by using Data.Graph.Inductive.newNodes function). I want the addx to have signature of addx :: String -> G -> Int and this function will compute new free index, modify the graph G and return this computed index. Is it possible in Haskell to create such function (which will modify an existing object - G in this case) - by using lenses or something like that?

I have seen, that Haskell lens is defined like lens :: (a -> c) -> (a -> d -> b) -> Lens a b c d and lens is basically a "getter" and "setter", so its signature allows for different types of getter output (c), setter value (d) and setter output (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

Upvotes: 1

Views: 485

Answers (1)

J. Abrahamson
J. Abrahamson

Reputation: 74334

Your type for addx is broken since you can't modify G in a pure function without returning the modified form like addx1 :: String -> G -> (Int, G). If you have a clever eye for Haskell monads, you might notice that this has an isomorphic type, addx2 :: String -> State G Int.

We can align everything to this "stateful" orientation

add' node = do g <- get
               put $ g { _repr = DB.insNode node $ _repr g }

and make it more succinct with lenses

add'' node = repr %= DB.insNode node

The real challenge here is, at the end of the day, tracking the node identity. One way would be to carry it alongside the repr in your type

data G = G { _repr :: DG.Gr String String, _name :: String, _index :: Int } 
empty = G DG.empty "" 0

then use that when building nodes (using lenses again!)

addx' name = do i <- use index
                repr %= DB.insNode (i, node)
                i += 1

Upvotes: 2

Related Questions