-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Using algebraic-graphs #11
Comments
Furthermore I also think the testing code should be converted to alga use alga construction primitives, from the paper found in its library it is not possible to create malformed graphs - which will allow the parser to be tested more rigorously as it reduces the chance of human error in the testing. The current approach relies on humans parsing the graphs and embedding the graphs with the constructors which are then compared to the parsed result for equality. |
It may still be useful to represent graphs abstractly in |
One possible issue when using Alga for graph representation is edge properties. At the moment, Alga does not provide any support for this, so one may need to do something like: data Pangraph = Pangraph
{ graph :: Graph ByteString
, vertexProperties :: Map ByteString [ByteString]
, edgeProperties :: Map (ByteString, ByteString) [ByteString] } |
My theoretical solution to combining alga and extra properties relies on abstracting a part of the alga interface into one which encompasses extra properties. But this to me feels out of the scope of pangraph (as a parser and serialiser) and also alga (as an algebra of graphs). I think I will give it a try despite my doubts, I will take the methods from |
@thisiswhereitype I think I agree with your doubts. I am starting to think that perhaps we should do the following. Pangraph needs to store lists of vertices and lists of edges, along with their properties, as they come from the input file. So, the internal representation should match this, and does not need to rely on Alga. For example: data Pangraph = Pangraph
{ vertexProperties :: Map ByteString VertexProperties
, edgeProperties :: Map (ByteString, ByteString) EdgeProperties } So, it's just a map from vertex names to vertex properties, and the same for edges (pairs of vertex names). Then, in order to provide a clean interface to the Alga library, we also need a couple functions like: -- Extract the underlying graph by parsing each vertex and taking into account its properties
-- Note that the function may fail for a number of reasons, e.g. if Pangraph datatype contains
-- an edge referring to a non-existent vertex or if the parsing function has failed
toGraph :: (ByteString -> VertexProperties -> Maybe a) -> Pangraph -> Maybe (Graph a)
-- Turn an algebraic graph into a Pangraph representation, filling out the vertex properties
-- using the provided function
fromGraph :: (a -> (ByteString, VertexProperties)) -> Graph a -> Pangraph This is just a draft idea, so we can keep discussing this further in case of any doubts. |
I think this will work best to parse as many formats. Flexibility and performance should be the biggest concerns. Currently I am working with this implementation: data Pangraph = Pangraph { unNodes :: Map Key Node
, unEdges :: Map Key Edge
} deriving (Eq)
newtype Node = Node { unNodeAttributes :: [Attribute]
} deriving (Eq)
newtype Edge = Edge { unEdgeAttributes :: [Attribute]
} deriving (Eq)
newtype Key = Key { unKey :: BS.ByteString
} deriving (Eq, Ord)
newtype Value = Value { unValue :: BS.ByteString
} deriving (Eq, Ord)
type Attribute = (Key, Value) I think a list is most appropriate now as many graphs only have a few attributes. But this can be confirmed with later profiling. Another consideration is the possibility for metadata fields within the graph for example whether the graph is directed or not. I think it will be necessary to enforce an unique key onto the Edges. Otherwise how the pair of nodes uniquely define an edge considering other properties? Thinking about your suggestion for interface to alga, I think |
Let's focus on the smallest/simplest API first, without going into implementation details. The following is what, I think, a user should see, which is not very far from the current implementation. Note that no type classes are required. -- Abstract data type (i.e. the constructor is not exported)
-- Do not provide any instances, such as Eq, Show, etc. yet -- we'll add them in future
data Pangraph
-- These type synonyms are visible to the user, they are added only for readability
type Key = ByteString
type Value = ByteString
type Identifier = ByteString
-- Abstract data types for attributes, vertices and edges
data Attribute
data Vertex
data Edge
-- Accessing the identifier and attributes of a vertex
vertexIdentifier :: Vertex -> Identifier
vertexAttributes :: Vertex -> [Attribute]
-- Similarly, accessing the source/target identifiers and attributes of an edge
edgeSource :: Edge -> Identifier
edgeTarget :: Edge -> Identifier
edgeAttributes :: Edge -> [Attribute]
-- Accessing key/value pairs of attributes
key :: Attribute -> Key
value :: Attribute -> Value
-- Extract the vertices/edges from the graph
vertexList :: Pangraph -> [Vertex]
edgeList :: Pangraph -> [Edge]
-- Also provide constructors for Pangraph, Vertex, Edge and Attribute
pangraph :: [Vertex] -> [Edge] -> Pangraph
vertex :: Identifier -> [Attribute] -> Vertex
edge :: Identifier -> Identifier -> [Attribute] -> Edge
attribute :: Key -> Value -> Attribute The rest of the complexity (maps, algebraic graphs, etc.) can be easily built on top of this simple API. Note: I prefer to use "vertex" instead of "node" to avoid confusion with Alga. @thisiswhereitype Can you write an implementation and submit a PR? |
After further discussion with @thisiswhereitype, here is the current design (no more identifiers): -- Abstract data type (i.e. the constructor is not exported)
-- Do not provide any instances, such as Eq, Show, etc. yet -- we'll add them in future
data Pangraph
-- These type synonyms are visible to the user, they are added only for readability
type Key = ByteString
type Value = ByteString
-- Abstract data types for attributes, vertices and edges
data Attribute
data Vertex
data Edge
-- Instances useful for putting vertices and edges in containers
instance Ord Vertex
instance Ord Edge
-- Accessing the identifier and attributes of a vertex
vertexAttributes :: Vertex -> [Attribute]
-- Similarly, accessing the source/target identifiers and attributes of an edge
edgeSource :: Edge -> Vertex
edgeTarget :: Edge -> Vertex
edgeAttributes :: Edge -> [Attribute]
-- Accessing key/value pairs of attributes
key :: Attribute -> Key
value :: Attribute -> Value
-- Extract the vertices/edges from the graph
vertexList :: Pangraph -> [Vertex]
edgeList :: Pangraph -> [Edge]
-- Also provide constructors for Pangraph, Vertex, Edge and Attribute
pangraph :: [Vertex] -> [Edge] -> Pangraph
vertex :: [Attribute] -> Vertex
edge :: Vertex -> Vertex -> [Attribute] -> Edge
attribute :: Key -> Value -> Attribute |
Here is my interface and data types implementation, I have made some comments below. module Pangraph.Internal.Graph (
-- Abstract Types
Pangraph,
Vertex,
Edge,
Attribute, -- A type alias for (Key, Value)
Key,
Value,
-- Constructors
makePangraph,
makeVertex,
makeEdge,
makeKey,
makeValue,
-- Getters
vertices,
edges,
vertexAttributes,
edgeAttributes,
edgeEndpoints,
key,
value,
) where
data Pangraph = Pangraph
{ vertices' :: Map Identifier Vertex
, edges' :: Map Identifier Edge
} deriving (Eq)
data Vertex = Vertex
{ nodeID' :: Maybe Identifier
, vertexAttributes' :: [Attribute]
} deriving (Eq)
data Edge = Edge
{ edgeID' :: Maybe Identifier
, edgeAttributes' :: [Attribute]
, source :: Vertex
, target :: Vertex
} deriving (Eq)
type Identifier = Word
type Attribute = (Key, Value)
newtype Key = Key BS.ByteString deriving (Eq, Ord, Show)
newtype Value = Value BS.ByteString deriving (Eq, Ord, Show)
I think insertVertex :: Pangraph -> Vertex -> Pangraph
-- or
addVertex :: Pangraph -> Vertex -> Maybe Pangraph
updateVertex :: Pangraph -> Vertex -> Maybe Pangraph |
Also discussing your idea that typeclasses were not ideal for implementing module Interfaces (
Circle,
Square,
makeCircle,
makeSquare,
volume
) where
data Circle = Circle Float deriving (Show,Eq)
data Square = Square Float Float Float deriving (Show,Eq)
makeCircle :: Float -> Circle
makeCircle = Circle
makeSquare :: Float -> Float -> Float -> Square
makeSquare = Square
class Shape a where
volume :: a -> Float
instance Shape Square where
volume (Square x y z) = x * y * z
instance Shape Circle where
volume (Circle r) = r * r * 3.14 module Lib(
test
) where
import qualified Interfaces as I
data Triangle = Equilateral Int Int Int deriving (Eq, Show)
-- error: Not in scope: type constructor or class ‘Shape’
-- instance Shape Triangle where
-- volume (Equilateral hyp adj opp) = (adj + opp) / 2
test :: (Float, Float)
test = (I.volume square, I.volume circle)
where
square = I.makeSquare 6 3 5
circle = I.makeCircle 100 I think that by doing this the API is cleaner as there is no duplication with functions like |
@thisiswhereitype Please submit a pull request, I'll do a review and we'll go from there. I suggest not to use type classes at first -- we can consider such API improvements in future. Let's implement the simplest thing that works first. |
However, in extension to my last comment, using type classes means we are unable to hide a particular |
There are also other reasons for not using type classes: for example, you get more obscure error messages. If you apply |
Given the context of this issue changing in #14 and the interface now relatively settled upon. toAlga :: Pangraph -> Graph Vertex
fromAlga :: Graph Vertex -> ([Vertex], [Edge]) |
Instead of http://hackage.haskell.org/package/algebraic-graphs-0.0.5/docs/Algebra-Graph-Class.html#t:ToGraph. This allows http://hackage.haskell.org/package/algebraic-graphs-0.0.5/docs/Algebra-Graph-Export-Dot.html In this way you can add an export to DOT format very easily (you can try the simplest This Let's come back to |
After implementing this, I discovered alga's |
@thisiswhereitype I don't understand. Are you trying to implement |
After clarifying in person module Alga (
WrappedVertex(..),
...
) where
...
class Alga where
toAlga :: [WrappedVertex] -> [(WrappedVertex, WrappedVertex)] -> Graph WrappedVertex
toPan :: Graph WrappedVertex -> ([Vertex], [Edge])
newtype WrappedVertex = Vertex deriving (Eq, Show)
instance Ord WrappedVertex where
... Is there a way users can define the instance? |
I don't think I suggested anything like this! :-) Can you clarify what problem you are solving? Why do you need |
To extract the list of Vertices and then edges from an algebraic graph and recreate a list of Edges and Vertices? |
Ah, I think you are jumping ahead a little. Let's do the |
I am afraid I am now confused. module Pangraph.AlgebraicGraphs
( toAlga
) where
import qualified Algebra.Graph as G
import Pangraph
toAlga :: Pangraph -> G.Graph Vertex
toAlga p = G.graph (vertices p) (map edgeEndpoints $ edges p) I am not sure what behavior is intended for the |
@thisiswhereitype Well, that's almost exactly what you need. Let me tweak it: {-# LANGUAGE TypeFamilies #-}
module Pangraph -- should be defined here as otherwise it will be an orphan instance
import qualified Algebra.Graph.Class as Alga
instance Alga.ToGraph Pangraph where
type ToVertex = Vertex
toGraph p = Alga.graph (vertices p) (map edgeEndpoints $ edges p) |
I believe that with the above instance definition you can apply DOT |
Given the definition form Alga: class ToGraph t where
type ToVertex t
toGraph :: (Graph g, Vertex g ~ ToVertex t) => t -> g Is the instance not: instance Alga.ToGraph Pangraph where
type ToVertex Pangraph = Vertex
toGraph p = Alga.graph (vertices p) (map edgeEndpoints $ edges p) |
Ah, yes, you are right: I missed |
https://github.com/thisiswhereitype/pangraph/blob/alga/src/Pangraph.hs |
@thisiswhereitype Looks good -- please send a PR with it. |
I have concerns regarding size the binary* may grow too if all Graph libraries can be imported into *Only imported modules are linked from my testing. {-# LANGUAGE TypeFamilies #-}
module Pangraph.AlgebraicGraphs
( toAlga, toPangraph
) where
import Pangraph
import qualified Algebra.Graph.Class as AlgaClass
newtype Wrapper = Wrapper
{ unwrap' :: Pangraph
} deriving (Eq)
-- I used ":t toAlga" to get this but can it be simplified?
toAlga :: (AlgaClass.Vertex g ~ Vertex, AlgaClass.Graph g) => Pangraph -> g
toAlga p = AlgaClass.toGraph (Wrapper p)
toPangraph = undefined
instance AlgaClass.ToGraph Wrapper where
type ToVertex Wrapper = Vertex
toGraph (Wrapper p) = AlgaClass.graph (vertexList p) (map edgeEndpoints $ edgeList p) |
I don't understand what exactly you achieved by wrapping and unwrapping Are you concerned that depending/importing Furthermore, I don't see how a function which is not used by a binary can increase its size. In any case, if the size of the binary is a concern it should be discussed in a separate issue. |
I was mistaken in my interpretation of my binary experiments. The newtype allows for the interface instance to not be orphaned but that is now irrelevant. |
Yes, sure, let me close this. We can discuss |
Seen as pangraph is concerned with parsing and serialisation of graphs. It seems wise to use another library to represent and manipulate the graphs. At the recommendation of @snowleopard Algebraic graphs.
I have implemented the API to produce these graph types with help from @geo2a's parser. However using alga makes
Pangraph
andEdge
types obsolete. They only add boiler plate. Is possible they could be re implemented as a minimal representation for conversion but not manipulation of exceptionally large graph files?The text was updated successfully, but these errors were encountered: