Seeking advice on a style question

In my text/graphics formatting work, I find myself doing a lot of "pipeline" processing, where a data structure will undergo a number of step-by-step transformations from input to output. For example, I have a function that looks like this (the names have been changed to protect the innocent--and to focus on the structure): process :: a -> b -> c -> d -> e process x1 x2 x3 x4 = let y01 = f01 x1 x2 x3; y02 = f02 x1; y03 = f03 y02; y04 = f04 y03; y05 = f05 x1 y01 y04; y06 = f06 x2 y05; (y07,y08) = f07 y01 y06; y09 = f08 y07; (y10,y11) = f09 x2 x4 y09 y08; y12 = f10 y10; y13 = f11 y12; y14 = f12 x1 x2 x3 y01 y13; y15 = f13 y14; y16 = f14 y15 y11 in y16 As you can see, the process is somewhat imperative in overall appearance, with each intermediate function f01..f14 accepting some combination of the original input values and/or intermediate values and returning a new value (or, in some cases, a tuple of values). Obviously, not all of the steps need to be broken out this way. We can, for example, skip the second and third steps and directly write: y04 = f04 $ f03 $ f02 x1; Laying it out this way has a couple of advantages. It makes the steps in the process transparently clear, and if I discover at some point that I need to make a change (e.g., a new requirement causes f13 to need access to x2), it's perfectly obvious where to make the modifications. Nevertheless, it also looks like something that would be amenable to processing with a State monad, except for one thing: The "shape" of the state isn't constant throughout the process. At any given step, new information may be added to the state, and old information may be thrown away, if there is no further need for it. In principle, it could be managed with a bunch of nested StateT monads, but my attempts to do so seem to get so caught up in the bookkeeping that I lose the advantages mentioned above. Alternatively, I can wrap all of the state up into a single universal structure that holds everything I will ever need at every step, but doing so seems to me to fly in the face of strong typing; at the early stages of processing, the structure will have "holes" in it that don't contain useful values and shouldn't be accessed. So here's the question: Is there a reasonable way to express this kind of process (where I suppose that "reasonable" means "in keeping with Haskell-nature") that preserves the advantages mentioned above, but avoids having to explicitly pass all of the various bits of state around? The (unattainable?) ideal would be something that looks like this: process = f14 . f13 . ... . f01 or process = f01 >>= f02 >>= ... >>= f14 Steve Schafer Fenestra Technologies Corp. http://www.fenestra.com/

Hello,
Alternatively, I can wrap all of the state up into a single universal structure that holds everything I will ever need at every step, but doing so seems to me to fly in the face of strong typing; at the early stages of processing, the structure will have "holes" in it that don't contain useful values and shouldn't be accessed.
You might want to look at the following threads discussing how to make variable-state monad like structures. http://permalink.gmane.org/gmane.comp.lang.haskell.cafe/17706 http://www.haskell.org/pipermail/haskell/2006-December/018917.html -Jeff

On Sun, 24 Dec 2006 10:39:19 -0500, you wrote:
You might want to look at the following threads discussing how to make variable-state monad like structures.
http://permalink.gmane.org/gmane.comp.lang.haskell.cafe/17706
http://www.haskell.org/pipermail/haskell/2006-December/018917.html
Thanks. I should have realized that Oleg would have had something to say about it. ;) Steve Schafer Fenestra Technologies Corp. http://www.fenestra.com/

I'm not sure this example really has anything to do with state. Is there
anything about your domain & examples that differs from standard functional
programming (or math)? To my eye, your example code below looks less like
an imperative program than like an intermediate form that a compiler would
generate from an expression built up from nested function applications and a
few "let"s.
Cheers, - Conal
On 12/24/06, Steve Schafer
In my text/graphics formatting work, I find myself doing a lot of "pipeline" processing, where a data structure will undergo a number of step-by-step transformations from input to output. For example, I have a function that looks like this (the names have been changed to protect the innocent--and to focus on the structure):
process :: a -> b -> c -> d -> e process x1 x2 x3 x4 = let y01 = f01 x1 x2 x3; y02 = f02 x1; y03 = f03 y02; y04 = f04 y03; y05 = f05 x1 y01 y04; y06 = f06 x2 y05; (y07,y08) = f07 y01 y06; y09 = f08 y07; (y10,y11) = f09 x2 x4 y09 y08; y12 = f10 y10; y13 = f11 y12; y14 = f12 x1 x2 x3 y01 y13; y15 = f13 y14; y16 = f14 y15 y11 in y16
As you can see, the process is somewhat imperative in overall appearance, with each intermediate function f01..f14 accepting some combination of the original input values and/or intermediate values and returning a new value (or, in some cases, a tuple of values).
Obviously, not all of the steps need to be broken out this way. We can, for example, skip the second and third steps and directly write:
y04 = f04 $ f03 $ f02 x1;
Laying it out this way has a couple of advantages. It makes the steps in the process transparently clear, and if I discover at some point that I need to make a change (e.g., a new requirement causes f13 to need access to x2), it's perfectly obvious where to make the modifications.
Nevertheless, it also looks like something that would be amenable to processing with a State monad, except for one thing: The "shape" of the state isn't constant throughout the process. At any given step, new information may be added to the state, and old information may be thrown away, if there is no further need for it. In principle, it could be managed with a bunch of nested StateT monads, but my attempts to do so seem to get so caught up in the bookkeeping that I lose the advantages mentioned above.
Alternatively, I can wrap all of the state up into a single universal structure that holds everything I will ever need at every step, but doing so seems to me to fly in the face of strong typing; at the early stages of processing, the structure will have "holes" in it that don't contain useful values and shouldn't be accessed.
So here's the question: Is there a reasonable way to express this kind of process (where I suppose that "reasonable" means "in keeping with Haskell-nature") that preserves the advantages mentioned above, but avoids having to explicitly pass all of the various bits of state around? The (unattainable?) ideal would be something that looks like this:
process = f14 . f13 . ... . f01
or
process = f01 >>= f02 >>= ... >>= f14
Steve Schafer Fenestra Technologies Corp. http://www.fenestra.com/ _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

On Mon, 25 Dec 2006 09:52:47 -0800, you wrote:
To my eye, your example code below looks less like an imperative program than like an intermediate form that a compiler would generate from an expression built up from nested function applications and a few "let"s.
That's very true, but the same could be said for many other examples of the use of the State monad (and Reader and Writer as well). They frequently don't do anything that couldn't be done purely functionally. Steve Schafer Fenestra Technologies Corp. http://www.fenestra.com/

I like monadic reformulations when they remove repetitious patterns from code, such as reading/updating a single threaded state. I'm not yet seeing such a pattern in your case. As you mentioned: The "shape" of the
state isn't constant throughout the process. At any given step, new information may be added to the state, and old information may be thrown away, if there is no further need for it.
So I'm still doubtful that a monadic approach is going to simplify your
code. Would you give a real example of some code you'd like to make more
manageable? If you have real examples of State, Reader, and/or Writer
monads that strike you as similar to your example, please share that also.
Cheers, - Conal
On 12/26/06, Steve Schafer
On Mon, 25 Dec 2006 09:52:47 -0800, you wrote:
To my eye, your example code below looks less like an imperative program than like an intermediate form that a compiler would generate from an expression built up from nested function applications and a few "let"s.
That's very true, but the same could be said for many other examples of the use of the State monad (and Reader and Writer as well). They frequently don't do anything that couldn't be done purely functionally.
Steve Schafer Fenestra Technologies Corp. http://www.fenestra.com/ _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

On Tue, 26 Dec 2006 10:28:22 -0800, you wrote:
So I'm still doubtful that a monadic approach is going to simplify your code. Would you give a real example of some code you'd like to make more manageable? If you have real examples of State, Reader, and/or Writer monads that strike you as similar to your example, please share that also.
I didn't mean to imply that a monadic approach would simplify the code, only that it was _conceivable_ to me that such a thing might be true. The code I showed _is_ a real example; I just changed the names of everything to focus on the structure. And the reason for focusing on the structure is that I'm looking for a _general_ principle to apply to make the code more manageable; the process that I showed is just one of many similarly-structured processes that are involved in the actual application. I think the essence of my question might have gotten lost, so I'll try a slightly different approach: I have a process that consists of a series of steps. If each step consumed _only_ the results of the previous step, I could of course describe the process like this: process = f14 . f13 . f12 .... But that isn't quite the case. Each step consumes not only the results of the previous step, but also some combination of the results of prior steps and/or the original inputs. One way to look at this is a directed graph, a sort of "branching pipeline"; see http://www.dendroica.com/Scratch/process.png. Now, the advantages of this kind of visual representation of the process are that it makes it perfectly clear what each step consumes and how the "flow" of the process occurs. Another big advantage (to me, anyway) is that the graphical representation is entirely point-free; the picture isn't cluttered with intermediate values whose only purpose is to hold onto things I need later, but not right now. So here's the (restated) question: Is there some way to represent the process in good ol' text form that preserves the elegance and conciseness of the graphical representation? Steve Schafer Fenestra Technologies Corp. http://www.fenestra.com/

Steve, How would this example look if you named only multiply-used expressions? I'd like to see it in a more conventional pointful style with nested expressions. I'm still wondering whether the awkwardness results from your writing style or is more inherent. Showing the real variable names may also help also. You said Each step consumes not only the results of the previous step, but also some
combination of the results of prior steps and/or the original inputs.
This description fits expression evaluation in general. Maybe you don't
like expression-oriented programming?
- Conal
On 12/26/06, Steve Schafer
On Tue, 26 Dec 2006 10:28:22 -0800, you wrote:
So I'm still doubtful that a monadic approach is going to simplify your code. Would you give a real example of some code you'd like to make more manageable? If you have real examples of State, Reader, and/or Writer monads that strike you as similar to your example, please share that also.
I didn't mean to imply that a monadic approach would simplify the code, only that it was _conceivable_ to me that such a thing might be true. The code I showed _is_ a real example; I just changed the names of everything to focus on the structure. And the reason for focusing on the structure is that I'm looking for a _general_ principle to apply to make the code more manageable; the process that I showed is just one of many similarly-structured processes that are involved in the actual application.
I think the essence of my question might have gotten lost, so I'll try a slightly different approach: I have a process that consists of a series of steps. If each step consumed _only_ the results of the previous step, I could of course describe the process like this:
process = f14 . f13 . f12 ....
But that isn't quite the case. Each step consumes not only the results of the previous step, but also some combination of the results of prior steps and/or the original inputs. One way to look at this is a directed graph, a sort of "branching pipeline"; see http://www.dendroica.com/Scratch/process.png.
Now, the advantages of this kind of visual representation of the process are that it makes it perfectly clear what each step consumes and how the "flow" of the process occurs. Another big advantage (to me, anyway) is that the graphical representation is entirely point-free; the picture isn't cluttered with intermediate values whose only purpose is to hold onto things I need later, but not right now.
So here's the (restated) question: Is there some way to represent the process in good ol' text form that preserves the elegance and conciseness of the graphical representation?
Steve Schafer Fenestra Technologies Corp. http://www.fenestra.com/ _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

On Tue, Dec 26, 2006 at 09:56:11PM -0500, Steve Schafer wrote:
But that isn't quite the case. Each step consumes not only the results of the previous step, but also some combination of the results of prior steps and/or the original inputs. One way to look at this is a directed graph, a sort of "branching pipeline"; see http://www.dendroica.com/Scratch/process.png.
Why not generate Haskell code from such a graph? Best regards Tomasz

On Fri, 29 Dec 2006 18:39:04 +0100, you wrote:
Why not generate Haskell code from such a graph?
Well, that would indeed be a workable solution. But I don't have quite the resources to design Yet Another Visual Programming Language. And a textual representation of the graph would have exactly the same kinds of problems that the textual Haskell has.... Steve Schafer Fenestra Technologies Corp. http://www.fenestra.com/

I know people just had a discussion about not answering simple questions with unsafe! or arrows! or OOHaskell! or oleg! But here is an answer that uses Control.Arrow just for the function combinators: Steve Schafer wrote:
In my text/graphics formatting work, I find myself doing a lot of "pipeline" processing, where a data structure will undergo a number of step-by-step transformations from input to output. For example, I have a function that looks like this (the names have been changed to protect the innocent--and to focus on the structure):
process :: a -> b -> c -> d -> e process x1 x2 x3 x4 = let y01 = f01 x1 x2 x3; y02 = f02 x1; y03 = f03 y02; y04 = f04 y03; y05 = f05 x1 y01 y04; y06 = f06 x2 y05; (y07,y08) = f07 y01 y06; y09 = f08 y07; (y10,y11) = f09 x2 x4 y09 y08; y12 = f10 y10; y13 = f11 y12; y14 = f12 x1 x2 x3 y01 y13; y15 = f13 y14; y16 = f14 y15 y11 in y16
[snip]
The (unattainable?) ideal would be something that looks like this:
process = f14 . f13 . ... . f01
or
process = f01 >>= f02 >>= ... >>= f14
You want to reduce the number of intermediate names that have to be created. Control.Arrow allows for complicated "wiring diagrams" which the Identity arrow reduces to complicated function composition. The above can be rewritten many ways. This "process" happens (by a bit of luck) to be easy to rewrite fairly simply. With some dummy function of the right shape:
module Main where
import Control.Arrow
f01 x1 x2 x3 = [x1,x2,x3] f02=id f03=id f04=id f05 _ _ = id f06 _=id f07 y01 = id &&& (:y01) f08=id f09 _ x4 a b = (a+x4,b) f10=id f11=id f12 _ _ _ y01 = (:y01) f13=id f14 = (,)
-- process :: a -> b -> c -> d -> e process x1 x2 x3 x4 = let y01 = f01 x1 x2 x3 in ($ x1) (f02 >>> f03 >>> f04 >>> f05 x1 y01 >>> f06 x2 >>> f07 y01 >>> first f08 >>> uncurry (f09 x2 x4) >>> first (f10 >>> f11 >>> f12 x1 x2 x3 y01 >>> f13) >>> uncurry f14)
main = return $ process 1 2 3 4 -- returns ([5,1,2,3],[1,1,2,3]) which is the same as your process with -- these dummy function definitions
The fact that some of them return a 2-tuple has been handled by using "first" to act on only on the fst item, while the snd is passed along until an "uncurry fxx" consumes two at once. Other pipelines may be trickier, for which GHC's syntactic sugar "proc" would help. Here it does not seem to (this requires knowing the syntactic sugar, see GHC's user manual):
process'' = curry4 processA
uncurry3 f = (\(a,b,c) -> f a b c) curry4 f = (\ a b c d -> f (a,b,c,d))
processA = proc (x1,x2,x3,x4) -> do y01 <- uncurry3 f01 -< (x1,x2,x3) (f02 >>> f03 >>> f04 >>> f05 x1 y01 >>> f06 x2 >>> f07 y01 >>> first f08 >>> uncurry (f09 x2 x4) >>> first (f10 >>> f11 >>> f12 x1 x2 x3 y01 >>> f13) >>> uncurry f14) -<< x1

On Tue, 26 Dec 2006 18:29:43 +0000, you wrote:
-- process :: a -> b -> c -> d -> e process x1 x2 x3 x4 = let y01 = f01 x1 x2 x3 in ($ x1) (f02 >>> f03 >>> f04 >>> f05 x1 y01 >>> f06 x2 >>> f07 y01 >>> first f08 >>> uncurry (f09 x2 x4) >>> first (f10 >>> f11 >>> f12 x1 x2 x3 y01 >>> f13) >>> uncurry f14)
This is like what I was looking for, although it does still require at least one temporary variable. I'll have to think about it a bit to see how applicable it is in general. Thanks. The tuples do make things a bit messy; they could easily be removed at the cost of introducing a few more steps: (y07,y08) = f07 y01 y06; would become y' = f07 y01 y06; y07 = f07a y'; y08 = f07b y'; where f07a = fst and f07b = snd. Steve Schafer Fenestra Technologies Corp. http://www.fenestra.com/

All of this code, including the original, looks like compiler output to me.
If we're talking about easy to understand reformulations, how about we start
with some compiler input instead?
On 12/26/06, Chris Kuklewicz
I know people just had a discussion about not answering simple questions with unsafe! or arrows! or OOHaskell! or oleg! But here is an answer that uses Control.Arrow just for the function combinators:
Steve Schafer wrote:
In my text/graphics formatting work, I find myself doing a lot of "pipeline" processing, where a data structure will undergo a number of step-by-step transformations from input to output. For example, I have a function that looks like this (the names have been changed to protect the innocent--and to focus on the structure):
process :: a -> b -> c -> d -> e process x1 x2 x3 x4 = let y01 = f01 x1 x2 x3; y02 = f02 x1; y03 = f03 y02; y04 = f04 y03; y05 = f05 x1 y01 y04; y06 = f06 x2 y05; (y07,y08) = f07 y01 y06; y09 = f08 y07; (y10,y11) = f09 x2 x4 y09 y08; y12 = f10 y10; y13 = f11 y12; y14 = f12 x1 x2 x3 y01 y13; y15 = f13 y14; y16 = f14 y15 y11 in y16
[snip]
The (unattainable?) ideal would be something that looks like this:
process = f14 . f13 . ... . f01
or
process = f01 >>= f02 >>= ... >>= f14
You want to reduce the number of intermediate names that have to be created.
Control.Arrow allows for complicated "wiring diagrams" which the Identity arrow reduces to complicated function composition.
The above can be rewritten many ways. This "process" happens (by a bit of luck) to be easy to rewrite fairly simply. With some dummy function of the right shape:
module Main where
import Control.Arrow
f01 x1 x2 x3 = [x1,x2,x3] f02=id f03=id f04=id f05 _ _ = id f06 _=id f07 y01 = id &&& (:y01) f08=id f09 _ x4 a b = (a+x4,b) f10=id f11=id f12 _ _ _ y01 = (:y01) f13=id f14 = (,)
-- process :: a -> b -> c -> d -> e process x1 x2 x3 x4 = let y01 = f01 x1 x2 x3 in ($ x1) (f02 >>> f03 >>> f04 >>> f05 x1 y01 >>> f06 x2 >>> f07 y01 >>> first f08 >>> uncurry (f09 x2 x4) >>> first (f10 >>> f11 >>> f12 x1 x2 x3 y01 >>> f13) >>> uncurry f14)
main = return $ process 1 2 3 4 -- returns ([5,1,2,3],[1,1,2,3]) which is the same as your process with -- these dummy function definitions
The fact that some of them return a 2-tuple has been handled by using "first" to act on only on the fst item, while the snd is passed along until an "uncurry fxx" consumes two at once.
Other pipelines may be trickier, for which GHC's syntactic sugar "proc" would help. Here it does not seem to (this requires knowing the syntactic sugar, see GHC's user manual):
process'' = curry4 processA
uncurry3 f = (\(a,b,c) -> f a b c) curry4 f = (\ a b c d -> f (a,b,c,d))
processA = proc (x1,x2,x3,x4) -> do y01 <- uncurry3 f01 -< (x1,x2,x3) (f02 >>> f03 >>> f04 >>> f05 x1 y01
f06 x2 >>> f07 y01 >>> first f08 >>> uncurry (f09 x2 x4) >>> first (f10 >>> f11 >>> f12 x1 x2 x3 y01 >>> f13) >>> uncurry f14) -<< x1
Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

Steve Schafer wrote:
In my text/graphics formatting work, I find myself doing a lot of "pipeline" processing, where a data structure will undergo a number of step-by-step transformations from input to output. For example, I have a function that looks like this (the names have been changed to protect the innocent--and to focus on the structure):
process :: a -> b -> c -> d -> e process x1 x2 x3 x4 = let y01 = f01 x1 x2 x3; y02 = f02 x1; y03 = f03 y02; y04 = f04 y03; y05 = f05 x1 y01 y04; y06 = f06 x2 y05; (y07,y08) = f07 y01 y06; y09 = f08 y07; (y10,y11) = f09 x2 x4 y09 y08; y12 = f10 y10; y13 = f11 y12; y14 = f12 x1 x2 x3 y01 y13; y15 = f13 y14; y16 = f14 y15 y11 in y16 [...] In principle, it could be managed with a bunch of nested StateT monads, but my attempts to do so seem to get so caught up in the bookkeeping that I lose the advantages mentioned above. [...] So here's the question: Is there a reasonable way to express this kind of process (where I suppose that "reasonable" means "in keeping with Haskell-nature") that preserves the advantages mentioned above, but avoids having to explicitly pass all of the various bits of state around?
To me, it looks more like MonadReader than MonadState because I have the impression that x1 and x2 are "enivronments" to fetch something from. (Btw, MonadReader is best used as an Applicative Functor, but that's a different story). But in general, it's futile trying to simplify things without knowing their meaning: names are *important*. I assume that your proper goal is not to structure pipeline processes in full generality, but to simplify the current one at hand. Even if you wanted to simplify the general structure, I think you'd have to make the types of the different yk explicit. Otherwise, the problem is underspecified and/or one has to assume that they're all different (modulo some equalities implied by type correctness). Regards, apfelmus

On Wed, 27 Dec 2006 17:06:24 +0100, you wrote:
But in general, it's futile trying to simplify things without knowing their meaning: names are *important*. I assume that your proper goal is not to structure pipeline processes in full generality, but to simplify the current one at hand.
No, I'm looking for full generality. ;) I have dozens of these kinds of "quasi-pipelines," all similar in overall appearance, but different in detail.
Even if you wanted to simplify the general structure, I think you'd have to make the types of the different yk explicit. Otherwise, the problem is underspecified and/or one has to assume that they're all different (modulo some equalities implied by type correctness).
Most of them are, in fact, different types (see my reply to Conal). -------- Here's the essence of the problem. If I have this: process1 x y = let u = foo x y; v = bar u; w = baz v in w I can easily rewrite it in point-free style: process1 = baz . bar . foo But if I have this: process2 x y = let u = foo x y; v = bar u; w = baz v u in w then I can't avoid naming and using an intermediate variable. And that annoys me. The u in process2 is of no more value to me (pardon the pun) as the one in process1, but I am forced to use it simply because the data flow is no longer strictly linear. The reason I brought up monads as a possible means of managing this problem is that the State, Reader and Writer monads already handle certain specific "shapes" of nonlinear data flow, which suggested to me that maybe there was a monadic approach to managing nonlinear data flow in a more general way. Of course, if there is a non-monadic, purely functional way to do it, that would be even better, but I've never seen such a thing (short of doing lots of tupling and un-tupling). Steve Schafer Fenestra Technologies Corp. http://www.fenestra.com/

Hello Steve, Friday, December 29, 2006, 5:41:40 AM, you wrote:
then I can't avoid naming and using an intermediate variable. And that annoys me. The u in process2 is of no more value to me (pardon the pun) as the one in process1, but I am forced to use it simply because the data flow is no longer strictly linear.
it force you to give names to intermediate results which is considered as good programing style - program becomes more documented. alternatively, you can give simple names like a b c -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

On Fri, 29 Dec 2006 14:23:20 +0300, you wrote:
it force you to give names to intermediate results which is considered as good programing style - program becomes more documented.
But that would imply that function composition and in-line function definition are also Bad Style. Steve Schafer Fenestra Technologies Corp. http://www.fenestra.com/

Hello Steve, Friday, December 29, 2006, 8:10:29 PM, you wrote:
it force you to give names to intermediate results which is considered as good programing style - program becomes more documented.
But that would imply that function composition and in-line function definition are also Bad Style.
and omitting type signatures too :) yes, to some degree. balance between naked code and comments is your choice -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

Steve Schafer wrote:
Here's the essence of the problem. If I have this:
process1 x y = let u = foo x y; v = bar u; w = baz v in w
I can easily rewrite it in point-free style:
process1 = baz . bar . foo
Not unless you have a much fancier version of function composition, like... http://okmij.org/ftp/Haskell/types.html#polyvar-comp
But if I have this:
process2 x y = let u = foo x y; v = bar u; w = baz v u in w
then I can't avoid naming and using an intermediate variable. And that annoys me. The u in process2 is of no more value to me (pardon the pun) as the one in process1, but I am forced to use it simply because the data flow is no longer strictly linear.
The reason I brought up monads as a possible means of managing this problem is that the State, Reader and Writer monads already handle certain specific "shapes" of nonlinear data flow, which suggested to me that maybe there was a monadic approach to managing nonlinear data flow in a more general way. Of course, if there is a non-monadic, purely functional way to do it, that would be even better, but I've never seen such a thing (short of doing lots of tupling and un-tupling).
-- Use combinators which automate the tupling/un-tupling. -- See also, the Joy language... -- http://www.latrobe.edu.au/philosophy/phimvt/joy/j00rat.html main = process2 test process2 = baz . bar . dup . foo foo = mul . (push 2) . mul bar = rep . swap . (push 'A') baz = add . len test = (2,(3,()))::(Int,(Int,())) dup (a,b) = (a,(a,b)) swap (a,(b,c)) = (b,(a,c)) push a b = (a,b) lift1 f (a,b) = (f a,b) lift2 f (a,(b,c)) = (f a b,c) len = lift1 length add = lift2 (+) mul = lift2 (*) rep = lift2 replicate

On Fri, 29 Dec 2006 09:01:37 -0800, you wrote:
Steve Schafer wrote:
I can easily rewrite it in point-free style:
process1 = baz . bar . foo
Not unless you have a much fancier version of function composition, like...
Sorry; I obviously got a little carried away there: process1 x = let u = foo x; v = bar u; w = baz v in w process1 = baz . bar . foo Steve Schafer Fenestra Technologies Corp. http://www.fenestra.com/

Steve Schafer wrote:
Here's the essence of the problem. If I have this:
process1 x y = let u = foo x y; v = bar u; w = baz v in w
I can easily rewrite it in point-free style:
process1 = baz . bar . foo
That should have been process1 = (.) (baz . bar) . foo or something similar. You might want to define suitable combinators if you have this pattern more often: infix 8 .< (.<) = (.) . (.) process1 = baz . bar .< foo
But if I have this:
process2 x y = let u = foo x y; v = bar u; w = baz v u in w
then I can't avoid naming and using an intermediate variable.
Turns out you can. process2 = \x y -> (\u -> baz (bar u) u) (foo x y) = \x y -> (\u -> (baz . bar) u u) (foo x y) = \x y -> liftM2 (baz . bar) (foo x y) = liftM2 (baz . bar) .< foo In fact, you never need named values. Read "How to Mock a Mockingbird" by Richard Bird (if memory serves) or the documentation for the Unlamda (esoteric) programming language to find out how or let Lambdabot do the transformation to pointless style for you. You don't need to go fully points free in every case. In your original example, only one intermediate (y01) was actually used more than once and deserves naming. Everything else can be composed with the help of 'uncurry'. 'liftM2' is also surprisingly useful, but it's use at the type constructor (r ->) as in the last example probably deserves a name of its own.
The u in process2 is of no more value to me (pardon the pun) as the one in process1, but I am forced to use it simply because the data flow is no longer strictly linear.
Instead you could define intermediate functions by composing functions with the help of a few combinators. I guess, the construction (liftM2 (baz . bar)) could have a very meaningful name. Some combinators might also vanish if you reorder and/or tuple some of the arguments to existing functions.
The reason I brought up monads as a possible means of managing this problem is that the State, Reader and Writer monads already handle certain specific "shapes" of nonlinear data flow
Uhm... that could be said of Reader, but is no good description of the others. If you like, you could plug your y01 into a Reader Monad, but I don't think it simplifies anything. Sometimes naming values is simply the right thing to do. -Udo -- Even if you're on the right track, you'll get run over if you just sit there. -- Will Rogers

Steve Schafer wrote:
In my text/graphics formatting work, I find myself doing a lot of "pipeline" processing, where a data structure will undergo a number of step-by-step transformations from input to output. For example, I have a function that looks like this (the names have been changed to protect the innocent--and to focus on the structure):
process :: a -> b -> c -> d -> e process x1 x2 x3 x4 = let y01 = f01 x1 x2 x3; y02 = f02 x1; y03 = f03 y02; y04 = f04 y03; y05 = f05 x1 y01 y04; y06 = f06 x2 y05; (y07,y08) = f07 y01 y06; y09 = f08 y07; (y10,y11) = f09 x2 x4 y09 y08; y12 = f10 y10; y13 = f11 y12; y14 = f12 x1 x2 x3 y01 y13; y15 = f13 y14; y16 = f14 y15 y11 in y16
Disclaimer: just re-written by hand so needs double-checking before use: process x1 x2 x3 x4 = let y01 = f01 x1 x2 x3 (y07, y08) = f07 y01 (f06 x2 (f05 x1 y01 (f04 (f03 y02)))) (y10, y11) = f09 x2 x4 (f08 y07) y08 in f14 (f13 (f12 x1 x2 x3 y01 (f11 (f10 y10)))) y11 You can also make it look more like the diagram by using more indentation eg: process x1 x2 x3 x4 = let y01 = f01 x1 x2 x3 (y07, y08) = f07 y01 (f06 x2 (f05 x1 y01 (f04 (f03 y02)))) ... Best regards, Brian. -- http://www.metamilk.com
participants (10)
-
apfelmus@quantentunnel.de
-
Brian Hulley
-
Bulat Ziganshin
-
Chris Kuklewicz
-
Conal Elliott
-
Greg Buchholz
-
jeff p
-
Steve Schafer
-
Tomasz Zielonka
-
Udo Stenzel