Skip to content

Instantly share code, notes, and snippets.

@adithyaov
Last active May 4, 2019 10:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adithyaov/f87b5b496fd88ef91cfe438dfaf3a955 to your computer and use it in GitHub Desktop.
Save adithyaov/f87b5b496fd88ef91cfe438dfaf3a955 to your computer and use it in GitHub Desktop.
Acyclic Examples
module AcyclicMonad (dag, singleton, edgeTo) where
-- Only export dag, singleton and edgeTo.
import Control.Monad.Trans.State.Strict
type Vertex = Int
newtype DAG = DAG DAG' deriving Show
-- eg. Edges 4 [2, 3] (Edges 3 [1] (Edges 2 [1] (Edges 1 [] Nil)))
-- represents 4 * 2 + 4 * 3 + 3 * 1 + 2 * 1 + 1
data DAG'
= Cons Vertex
[DAG']
DAG'
| Nil
deriving (Show)
-- A simple helper function
vertex (Cons i _ _) = i
vertex Nil = 0
-- A State monad creating a singleton
singleton = modify (\s -> Cons (1 + vertex s) [] s) >> get
-- A State monad resulting in proper edges
edgeTo es = modify (\s -> Cons (1 + vertex s) es s) >> get
-- A simple function to run the state to get DAG in return
dag = DAG . snd . flip runState Nil
-- The result : DAG 3 [1..DAG,2..DAG] (DAG 2 [] (DAG 1 [] Nil))
dagTest =
dag $ do
v1 <- singleton
v2 <- singleton
edgeTo [v1, v2]
@anfelor
Copy link

anfelor commented May 2, 2019

Doesn't the current implementation need quadratic space? For example

    v1 <- singleton
    v2 <- edgeTo [v1]
    v3 <- edgeTo [v2, v1]
    v4 <- edgeTo [v3]
    v5 <- edgeTo [v4, v3, v1]
    edgeTo [v5, v1]

results in

DAG (Cons 6 [Cons 5 [Cons 4 [Cons 3 [Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil),Cons 1 [] Nil] (Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil))] (Cons 3 [Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil),Cons 1 [] Nil] (Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil))),Cons 3 [Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil),Cons 1 [] Nil] (Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil)),Cons 1 [] Nil] (Cons 4 [Cons 3 [Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil),Cons 1 [] Nil] (Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil))] (Cons 3 [Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil),Cons 1 [] Nil] (Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil)))),Cons 1 [] Nil] (Cons 5 [Cons 4 [Cons 3 [Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil),Cons 1 [] Nil] (Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil))] (Cons 3 [Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil),Cons 1 [] Nil] (Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil))),Cons 3 [Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil),Cons 1 [] Nil] (Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil)),Cons 1 [] Nil] (Cons 4 [Cons 3 [Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil),Cons 1 [] Nil] (Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil))] (Cons 3 [Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil),Cons 1 [] Nil] (Cons 2 [Cons 1 [] Nil] (Cons 1 [] Nil))))))

which seems to be rather big..

@adithyaov
Copy link
Author

adithyaov commented May 2, 2019

@anfelor
I believe underneath the representation is not huge.
If my understanding of Haskell is correct, then, Memory is allocated once and information is stored there.
When it is referenced further, underneath, Haskell would just use a pointer (of some kind) and does not allocate space again.
I might be wrong, please let me know!

If not, I think a simple way to solve this problem is something like the follows,

data DAG'
  = Cons Vertex
         [Vertex]
         DAG'
  | Nil
deriving (Show)

edgeTo es = modify (\s -> Cons (1 + vertex s) (map vertex es) s) >> get

Then something like,

    v1 <- singleton
    v2 <- edgeTo [v1]
    v3 <- edgeTo [v2, v1]
    v4 <- edgeTo [v3]
    v5 <- edgeTo [v4, v3, v1]
    edgeTo [v5, v1]

would result in,

DAG (Cons 6 [1, 5] (Cons 5 [1, 3, 4] (Cons 4 [3] (Cons 3 [2, 1] (Cons 2 [1] (Cons 1 [] Nil))))))

A few more changes are required (making Vertex a new type instead of type alias) to make it safe but I guess this should work!
Initially, this is what I made but traversing the graph is comparatively harder with this representation.

@anfelor
Copy link

anfelor commented May 3, 2019

When it is referenced further, underneath, Haskell would just use a pointer (of some kind) and does not allocate space again.

You are right! In practice this would probably run in linear space.

This looks a bit like type-safe indexing into sized vectors as in Data.Vector.Sized. Maybe one could also define

data Acyclic n a = Acyclic (AdjacencyMap a) (Vector n a)
append :: a -> [Ordinal n] -> Acyclic n a -> Acyclic (S n) a

it would have the benefit of allowing the addition of new vertices outside of the "dag monad".

@adithyaov
Copy link
Author

adithyaov commented May 4, 2019

@anfelor

data Acyclic n a = Acyclic (AdjacencyMap a) (Vector n a)

Could you please give me a simple example?

I think one can go a step further and eliminate the "dag monad" completely? (But I don't think it's worth it.)
I try a few experiments and get back to you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment