Sankey Diagram with monads

Hi all, Take a look at this disaster area, or just scroll down to where I come to the point... ======================= type SankeyBrain = (P2,CircleFrac,Double) -- like a turtle plus width data SankeyWorld tb = SankeyWorld ((Trail R2,Trail R2),tb) --outgoing and returning trails, plus brain emptySankey :: SankeyWorld SankeyBrain emptySankey = SankeyWorld ((mempty,mempty),(origin,0,0)) sankeyFrom:: CircleFrac -> Double -> SankeyWorld SankeyBrain sankeyFrom a w = SankeyWorld ((mempty,mempty),(p2 (0,0),a,w)) -- kick off with an angle and width instance Monad SankeyWorld where return a = SankeyWorld ((mempty,mempty), a) --never use this (SankeyWorld l) >>= f = let (SankeyWorld r) = f (snd l) in -- out = left then right, return = right then left SankeyWorld ( (((fst.fst) l <> (fst.fst) r),((snd.fst) r <> (snd.fst) l)),(snd r) ) sankeyVia :: Double -> SankeyBrain -> SankeyWorld SankeyBrain sankeyVia d (p,a,w) = let -- draw parallel lines and move them into place l1 = hrule 1 # scaleX d # translateX (d/2) # translateY (w/2) # rotate a # translate (origin .-. p) l2 = hrule 1 # scaleX (-d) # translateX (d/2) # translateY (-w/2) # rotate a # translate (origin .-. p) in SankeyWorld ( ( l1 , l2 ) , ( p .+^ (unitX # scale d # rotate a), a, w) ) sankeyTo :: SankeyBrain -> SankeyWorld SankeyBrain sankeyTo (p,a,w) = SankeyWorld ( --arrow at the end of the flow ( hrule w # translateX (w/2) # translateY (w/2) # rotate (-1/8::CircleFrac) # scale (0.7071) # rotate a # translate (origin .-. p) , hrule (-w) # translateX (w/2) # translateY (-w/2) # rotate (1/8::CircleFrac) # scale (0.7071) # rotate a # translate (origin .-. p) ), (p,a,w)) sankeyTurn r a' (p,a,w) = let (outr, inr, qu) = if a'>=0 then (r, -w-r, -0.25::CircleFrac) else (-w-r, r, 0.25::CircleFrac) in SankeyWorld ( -- turn a corner with nice round edges ( arc' outr (a+qu) (a+a'+qu) # translate (unitY # rotate (a+a' )# scale w) , arc' inr (a+a'+qu) (a+qu) # translate (unitY # rotate (a+a' )# scale w) ),(p,a+a',w)) -- bump... sankeySplit :: [(Double, SankeyBrain -> SankeyWorld SankeyBrain)] -> SankeyBrain -> SankeyWorld SankeyBrain sankeySplit fs (p,a,w) = let (placed,_) = ( foldl ( \(l,t) -> \(i,c) -> ( l++[( ( p .+^ (unitY # rotate a # scale (((t+i/2)-0.5)*w)), a, w*i) ,c )],t+i) ) ([],0) fs ) in foldl (\(SankeyWorld ((lo,lr),lb)) -> \(SankeyWorld ((ro,rr),rb)) -> SankeyWorld ( ( lo <> ro , rr <> lr ), rb ) ) emptySankey $ map (\(b,f)-> f b) placed SankeyWorld ((turtb,turta),_) = {- This is the bit that fails: sankeyFrom 0 5 >>= sankeyVia 5 >>= sankeySplit [ (0.3, sankeyVia 10 ) , (0.7, sankeyVia 15 ) ] -} sankeyFrom 0 5 >>= sankeyVia 5 >>= sankeyTurn 1 (-0.125) >>= sankeyVia 10
= sankeyTurn 1 (0.25) >>= sankeyTo -- >>= turn 0.25 >>= forward 10 >>= turn 0.25 >>= forward 20 >>= turn 0.25 >>= forward 10
pic3 = (strokeT (close ( turtb<>turta) )) # fc red ====================== The idea is that SankeyWorld is a monad containing two trails (outbound and inbound) and a turtle-like state. I bind it onto functions like SankeyBrain -> SankeyWorld, whereby >>= passes the state across. >>= draws the left hand outward trail, then the right hand outward trail, then the right hand inward trail, then the left hand inward trail, so it all makes a nice polygon and I can colour it in. sankeyFrom angle width is already a monad, sankeyVia length is such a function and I could have sankeyTo contain () in place of the brain (i.e. state) cos you're not supposed to continue from it. The tricky bit is splitting the flow. I want a function that takes the brain, splits the width according to named shares and shoves each share into a function SankeyBrain -> SankeyWorld that might have lots more stages and splits downwind. It was all going fine until I discovered that if I can say m >>= f, then I can't say f >>= f. So I don't know how to write the bits after the split. Silly me. But what should I do instead to model Sankey diagrams splitting? Is MonadPlus the trick? If so, am I gonna have to make [SankeyWorld] a monad as well? TIA, Adrian. PS: I rarely have any use for the polymorphism of the parameter to Monad. In this case, it's a SankeyBrain, end of story. Is there a simpler kind of monad that doesn't throw this complication at me?

Well I figured out that I should be using the State monad, but it seems not
to be behaving like most of the tutorials on the web. Did the syntax
change? ....
type SankeyState = (P2,CircleFrac,Double)
type SankeyPic = (Trail R2, Trail R2)
type Sankey = State SankeyState SankeyPic
saBlank :: Sankey
saBlank = return (mempty, mempty)
saVia :: Double -> Sankey
saVia l = state (
\(p,a,w) ->
(
( hrule l # translateX 0.5 # translateY (w/2) # rotate a
, hrule (-l) # translateX 0.5 # translateY (-w/2) # rotate a
)
, (p .+^ (unitX # scale l # rotate a),a,w)
)
)
saTo :: Sankey
saTo = state (
\(p,a,w) ->
(
( hrule w # translateX (w/2) # translateY (w/2) # rotate
(-1/8::CircleFrac) # scale (0.7071) # rotate a # translate (origin .-. p)
, hrule (-w) # translateX (w/2) # translateY (-w/2) # rotate
(1/8::CircleFrac) # scale (0.7071) # rotate a # translate (origin .-. p)
)
, (p,a,w)
)
)
x :: SankeyPic
x = evalState ( saTo ) (origin, 0, 5) -- works and looks nice
--x = evalState ( saVia 10 ) (origin, 0, 5) -- works but not much
to see
--x = evalState ( saVia 10 >>= saTo ) (origin, 0, 5) -- barfs with
something unintelligible
pic3 = strokeT ( close ( fst x <> snd x)) # fc red
The unintelligible bit is:
Couldn't match expected type `SankeyPic
-> StateT SankeyState
Data.Functor.Identity.Identity SankeyPic'
with actual type `Sankey'
In the second argument of `(>>=)', namely `saTo'
In the first argument of `evalState', namely `(saVia 10 >>= saTo)'
In the expression: evalState (saVia 10 >>= saTo) (origin, 0, 5)
TIA,
Adrian.
On 31 May 2013 21:16, Adrian May
Hi all,
Take a look at this disaster area, or just scroll down to where I come to the point...
=======================
type SankeyBrain = (P2,CircleFrac,Double) -- like a turtle plus width data SankeyWorld tb = SankeyWorld ((Trail R2,Trail R2),tb) --outgoing and returning trails, plus brain
emptySankey :: SankeyWorld SankeyBrain emptySankey = SankeyWorld ((mempty,mempty),(origin,0,0))
sankeyFrom:: CircleFrac -> Double -> SankeyWorld SankeyBrain sankeyFrom a w = SankeyWorld ((mempty,mempty),(p2 (0,0),a,w)) -- kick off with an angle and width
instance Monad SankeyWorld where return a = SankeyWorld ((mempty,mempty), a) --never use this (SankeyWorld l) >>= f = let (SankeyWorld r) = f (snd l) in -- out = left then right, return = right then left SankeyWorld ( (((fst.fst) l <> (fst.fst) r),((snd.fst) r <> (snd.fst) l)),(snd r) )
sankeyVia :: Double -> SankeyBrain -> SankeyWorld SankeyBrain sankeyVia d (p,a,w) = let -- draw parallel lines and move them into place l1 = hrule 1 # scaleX d # translateX (d/2) # translateY (w/2) # rotate a # translate (origin .-. p) l2 = hrule 1 # scaleX (-d) # translateX (d/2) # translateY (-w/2) # rotate a # translate (origin .-. p) in SankeyWorld ( ( l1 , l2 ) , ( p .+^ (unitX # scale d # rotate a), a, w) )
sankeyTo :: SankeyBrain -> SankeyWorld SankeyBrain sankeyTo (p,a,w) = SankeyWorld ( --arrow at the end of the flow ( hrule w # translateX (w/2) # translateY (w/2) # rotate (-1/8::CircleFrac) # scale (0.7071) # rotate a # translate (origin .-. p) , hrule (-w) # translateX (w/2) # translateY (-w/2) # rotate (1/8::CircleFrac) # scale (0.7071) # rotate a # translate (origin .-. p) ), (p,a,w))
sankeyTurn r a' (p,a,w) = let (outr, inr, qu) = if a'>=0 then (r, -w-r, -0.25::CircleFrac) else (-w-r, r, 0.25::CircleFrac) in SankeyWorld ( -- turn a corner with nice round edges ( arc' outr (a+qu) (a+a'+qu) # translate (unitY # rotate (a+a' )# scale w) , arc' inr (a+a'+qu) (a+qu) # translate (unitY # rotate (a+a' )# scale w) ),(p,a+a',w))
-- bump... sankeySplit :: [(Double, SankeyBrain -> SankeyWorld SankeyBrain)] -> SankeyBrain -> SankeyWorld SankeyBrain sankeySplit fs (p,a,w) = let (placed,_) = ( foldl ( \(l,t) -> \(i,c) -> ( l++[( ( p .+^ (unitY # rotate a # scale (((t+i/2)-0.5)*w)), a, w*i) ,c )],t+i) ) ([],0) fs ) in foldl (\(SankeyWorld ((lo,lr),lb)) -> \(SankeyWorld ((ro,rr),rb)) -> SankeyWorld ( ( lo <> ro , rr <> lr ), rb ) ) emptySankey $ map (\(b,f)-> f b) placed
SankeyWorld ((turtb,turta),_) = {- This is the bit that fails: sankeyFrom 0 5 >>= sankeyVia 5 >>= sankeySplit [ (0.3, sankeyVia 10 ) , (0.7, sankeyVia 15 ) ] -} sankeyFrom 0 5 >>= sankeyVia 5 >>= sankeyTurn 1 (-0.125) >>= sankeyVia 10
= sankeyTurn 1 (0.25) >>= sankeyTo -- >>= turn 0.25 >>= forward 10 >>= turn 0.25 >>= forward 20 >>= turn 0.25 >>= forward 10
pic3 = (strokeT (close ( turtb<>turta) )) # fc red
======================
The idea is that SankeyWorld is a monad containing two trails (outbound and inbound) and a turtle-like state. I bind it onto functions like SankeyBrain -> SankeyWorld, whereby >>= passes the state across. >>= draws the left hand outward trail, then the right hand outward trail, then the right hand inward trail, then the left hand inward trail, so it all makes a nice polygon and I can colour it in.
sankeyFrom angle width is already a monad, sankeyVia length is such a function and I could have sankeyTo contain () in place of the brain (i.e. state) cos you're not supposed to continue from it.
The tricky bit is splitting the flow. I want a function that takes the brain, splits the width according to named shares and shoves each share into a function SankeyBrain -> SankeyWorld that might have lots more stages and splits downwind.
It was all going fine until I discovered that if I can say m >>= f, then I can't say f >>= f. So I don't know how to write the bits after the split. Silly me. But what should I do instead to model Sankey diagrams splitting? Is MonadPlus the trick? If so, am I gonna have to make [SankeyWorld] a monad as well?
TIA, Adrian.
PS: I rarely have any use for the polymorphism of the parameter to Monad. In this case, it's a SankeyBrain, end of story. Is there a simpler kind of monad that doesn't throw this complication at me?

On Fri, May 31, 2013 at 11:06 AM, Adrian May wrote: Well I figured out that I should be using the State monad, but it seems
not to be behaving like most of the tutorials on the web. Did the syntax
change? .... mtl-2.x changed all the base monads (State, Reader, Writer, etc.) from
standalone to being transformers atop an Identity monad; this cleans up the
implementation considerably (since we don't have almost exactly the same
code for both the standalone and transformer versions, but means that all
uses of the standalone constructors must be replaced with functions that
build the appropriate transformer. (e.g. State becomes state, unless you
want to spell it out as StateT Identity.)
--
brandon s allbery kf8nh sine nomine associates
allbery.b@gmail.com ballbery@sinenomine.net
unix, openafs, kerberos, infrastructure, xmonad http://sinenomine.net

= about the thing on the left. Neither do I have a reason for >>= to be
Thanks, but I still don't know how to fix it.
In the meantime, I'm struggling with something more basic. I plan to write
a basic monad that puts diagrams on top of each other, then I'll let State
take care of pushing the origin and angle along (turtle style). But I'm
already stuck on that basic monad.
It's >>= has to explicitly use the fact that each monad has a journey there
and a journey back. The thing on the right of >>= will be inserted in
between them. But I always get either "something is a rigidly bound type
variable" or "Monad should have kind * -> *"
I just don't know how this is supposed to work.
I want the monad to contain two trails, in the sense of the Diagrams
module. If I bind two of them together, for the time being, I'll just stick
them on top of each other (at least I think the State monad will rescue me
from that.) I have no particular reason to tell the thing on the right of
polymorphic.
Right now I'm thinking that I'll have to define a class for things that
provide a journey there and a journey back. I'd rather not, because there's
only one of them but I can't seem to restrict the game any other way, but
this way isn't helping either.
Ideally I'd be able to write something like this:
data Sankey = Sankey {there, back :: Trail R2}
instance Monad Sankey where
return t b = Sankey t b
l@(Sankey t b) >>= f = let (Sankey t' b') = f l in
Sankey (t <> t') (b <> b')
although I have no reason to pass l to f. But the compiler barfs anyway. I
feel that Haskell is more complicated than what I'm trying to do. Under
duress I tried:
class Pic a where
there :: a -> Trail R2
back :: a -> Trail R2
data Trails p = Trails p
instance (Pic p) => Monad (Trails p) where
return = Trails
(Trails l) >>= f = let (Trails r) = f l in
((there l <> there r),(back r <> back l))
But it doesn't like that either. What am I missing?
Adrian.
On 31 May 2013 23:42, Brandon Allbery
On Fri, May 31, 2013 at 11:06 AM, Adrian May < adrian.alexander.may@gmail.com> wrote:
Well I figured out that I should be using the State monad, but it seems not to be behaving like most of the tutorials on the web. Did the syntax change? ....
mtl-2.x changed all the base monads (State, Reader, Writer, etc.) from standalone to being transformers atop an Identity monad; this cleans up the implementation considerably (since we don't have almost exactly the same code for both the standalone and transformer versions, but means that all uses of the standalone constructors must be replaced with functions that build the appropriate transformer. (e.g. State becomes state, unless you want to spell it out as StateT Identity.)
-- brandon s allbery kf8nh sine nomine associates allbery.b@gmail.com ballbery@sinenomine.net unix, openafs, kerberos, infrastructure, xmonad http://sinenomine.net
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners

It got a lot easier when I forgot all about the monads:
type SaState = ( P2, CircleFrac, Double )
type SaPic = ( (Trail R2), (Trail R2) )
type SaWorld = ( SaPic, SaState )
type SaAct = SaState -> SaWorld
bigbang :: Double -> SaWorld
bigbang w = ((mempty,mempty), (origin,0,w))
(>%) :: SaWorld -> SaAct -> SaWorld
((t1,b1),s1) >% f = let ((t2,b2),s2) = f s1 in ((t1<>t2,b2<>b1),s2)
(>%=) :: SaAct -> SaAct -> SaAct
f >%= g = \s1 ->
let ((t2,b2),s2) = f s1 in
let ((t3,b3),s3) = g s2 in
((t2<>t3,b3<>b2),s3)
saVia :: Double -> SaAct
saVia l (p,a,w) =
(
( hrule l # translateX (l/2) # translateY (w/2) # rotate a
, hrule (-l) # translateX (l/2) # translateY (-w/2) # rotate a
)
,
(p .+^ (unitX # scale l # rotate a),a,w)
)
saTo :: SaAct
saTo (p,a,w) =
(
( hrule w # translateX (-w/2) # rotate (-0.125 :: CircleFrac) #
scale 0.7071 # translateX (w/2) # rotate a # translate (origin .-. p)
, hrule (-w) # translateX (-w/2) # rotate ( 0.125 :: CircleFrac) #
scale 0.7071 # translateX (w/2) # rotate a # translate (origin .-. p)
)
,
(p,a,0)
)
saTurn :: Double -> CircleFrac -> SaAct
saTurn r a' (p,a,w) = let (outr, inr, qu) = if a'>=0 then (r, -w-r,
-0.25::CircleFrac) else (-w-r, r, 0.25::CircleFrac) in
(
( arc' outr (a+qu) (a+a'+qu) # translate (unitY # rotate (a+a' )#
scale w)
, arc' inr (a+a'+qu) (a+qu) # translate (unitY # rotate (a+a' )# scale
w)
)
,
(p,a+a',w)
)
saSplit :: [(Double, SaAct)] -> SaAct
saSplit fs (p,a,w) =
let (placed,_) = ( foldl ( \(l,t) -> \(i,f) -> ( l++[( ( p .+^ (unitY #
rotate a # scale (((t+i/2)-0.5)*w)), a, w*i) ,f )],t+i) ) ([],0) fs ) in
let ws = map (\(b,f)-> f b) placed in
((foldl (<>) mempty (map (\((tt,bb),_)->tt<>bb) ws),mempty),(origin,0,0))
p3 = bigbang 5 >% (saVia 10 >%= (saSplit
[ (0.80,(saVia 10 >%= saTo))
, (0.20,saTurn 1 (-0.25) >%= (saVia 2 >%= (saSplit
[ (0.10,saTurn 1 (0.25) >%= (saVia 5 >%= saTo))
, (0.80,(saVia 3 >%= saTo))
, (0.10,saTurn 1 (-0.25) >%= (saVia 5 >%= saTo))
] )))
] ))
pic3 = let p = fst $ p3 in
(strokeT $ close $ (fst p) <> (snd p) ) # fc red
But still I feel I'm missing something.
Adrian.
On 1 June 2013 00:31, Adrian May
Thanks, but I still don't know how to fix it.
In the meantime, I'm struggling with something more basic. I plan to write a basic monad that puts diagrams on top of each other, then I'll let State take care of pushing the origin and angle along (turtle style). But I'm already stuck on that basic monad.
It's >>= has to explicitly use the fact that each monad has a journey there and a journey back. The thing on the right of >>= will be inserted in between them. But I always get either "something is a rigidly bound type variable" or "Monad should have kind * -> *"
I just don't know how this is supposed to work.
= about the thing on the left. Neither do I have a reason for >>= to be
I want the monad to contain two trails, in the sense of the Diagrams module. If I bind two of them together, for the time being, I'll just stick them on top of each other (at least I think the State monad will rescue me from that.) I have no particular reason to tell the thing on the right of polymorphic.
Right now I'm thinking that I'll have to define a class for things that provide a journey there and a journey back. I'd rather not, because there's only one of them but I can't seem to restrict the game any other way, but this way isn't helping either.
Ideally I'd be able to write something like this:
data Sankey = Sankey {there, back :: Trail R2}
instance Monad Sankey where return t b = Sankey t b l@(Sankey t b) >>= f = let (Sankey t' b') = f l in Sankey (t <> t') (b <> b')
although I have no reason to pass l to f. But the compiler barfs anyway. I feel that Haskell is more complicated than what I'm trying to do. Under duress I tried:
class Pic a where there :: a -> Trail R2 back :: a -> Trail R2
data Trails p = Trails p
instance (Pic p) => Monad (Trails p) where return = Trails (Trails l) >>= f = let (Trails r) = f l in ((there l <> there r),(back r <> back l))
But it doesn't like that either. What am I missing?
Adrian.
On 31 May 2013 23:42, Brandon Allbery
wrote: On Fri, May 31, 2013 at 11:06 AM, Adrian May < adrian.alexander.may@gmail.com> wrote:
Well I figured out that I should be using the State monad, but it seems not to be behaving like most of the tutorials on the web. Did the syntax change? ....
mtl-2.x changed all the base monads (State, Reader, Writer, etc.) from standalone to being transformers atop an Identity monad; this cleans up the implementation considerably (since we don't have almost exactly the same code for both the standalone and transformer versions, but means that all uses of the standalone constructors must be replaced with functions that build the appropriate transformer. (e.g. State becomes state, unless you want to spell it out as StateT Identity.)
-- brandon s allbery kf8nh sine nomine associates allbery.b@gmail.com ballbery@sinenomine.net unix, openafs, kerberos, infrastructure, xmonad http://sinenomine.net
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners

On Fri, May 31, 2013 at 12:31 PM, Adrian May wrote: Thanks, but I still don't know how to fix it. In the meantime, I'm struggling with something more basic. I plan to write
a basic monad that puts diagrams on top of each other, then I'll let State
take care of pushing the origin and angle along (turtle style). But I'm
already stuck on that basic monad. My first question would be: are you sure it's actually a monad? Your
descriptions so far make me think that it quite possibly is not.
--
brandon s allbery kf8nh sine nomine associates
allbery.b@gmail.com ballbery@sinenomine.net
unix, openafs, kerberos, infrastructure, xmonad http://sinenomine.net
participants (2)
-
Adrian May
-
Brandon Allbery