[erlang-questions] Must and May convention

zxq9 <>
Fri Sep 29 16:49:56 CEST 2017


On 2017年09月29日 金曜日 10:05:12 Fred Hebert wrote:
> It could be. That's how the fancier forms are implemented in most cases 
> I've seen. The fancyflow forms I've mentioned earlier in the thread are 
> in fact just macros over to run:
> 
>     fancyflow:pipe(InitialState, [fun(Var) -> Exp1 end,
>                                   fun(Var) -> Exp2 end,
>                                   ...,
>                                   fun(Var) -> ExpN end])
>     fancyflow:maybe(InitialState, [fun(Var) -> Exp1 end,
>                                    fun(Var) -> Exp2 end,
>                                    ...,
>                                    fun(Var) -> ExpN end])

I see. I actually like to write them explicitly (without funs, where
possible) as:

    Steps =
        [fun change_thingy/2,
         fun check_thingy/2,
         fun do_other_thing/2],
    pipes:shortcircuit(Steps, Value).

But whenever I *really* need this sort of thing, the creation of `Steps`
winds up being dynamic and it is a pretty special situation. Most of the
time this stuff doesn't survive refactoring for one reason or another.

> The `maybe' function would probably still be worth it though. Nested 
> cases are annoying enough to be more costly to maintain than the 
> abstracted version.

This is the main one I see, or at least some form of it. Usually I run
into one of three variants of a `maybe`:

- A shortcut pipe that returns immediately when it hits {error, R} or
  'false'.

- A side-effecty pipe that accumulates errors and returns a list of them
  of the type [{error, term()}] in the order they were accumulated.

- A mapfold that permits a stack of operations on a stack of values.
  These usually shortcut a return right away and are pretty important
  to keep free of side effects (can be a complexity grenade otherwise).

> I'm not quite sure 'lists' would be the module for them; they're kind of 
> flipping the concept of a 'fold' where you apply a function to multiple 
> list elements updating a single state to 'applying a list of functions 
> to a term'. They're definitely mappable to the lists:foldl operations, 
> but it feels like the 'lists' part of it is not really what matters 
> here, but the control flow is.

I can see that. I've actually got a convenience lib that includes a few
pipelines that I have yet to open source. I still haven't made up my mind
whether I really *like* these or not -- usually I have a few places where
I start with them, but after refactoring somewhere further upstream in
the flow of the program they wind up going away. Not because they are
marked for death, but just because they sort of melt away once other stuff
gets figured out more completely (particularly once data is understood
well enough that it finally flattens out, ADTs get built if needed, etc.).

That tendency is another reason I don't like special syntax for this,
because it seems to often be less useful in a mature project than in ones
where you haven't quite figured out what you're doing yet. Having a
special syntax sort of supports crystalization of convoluted code -- or at
least psychologically lends support to leaving a bunch of stuff clumped
together that *really* doesn't belong together once the code is understood
better. Special syntax to make prototype-grade code live longer seems like
a mistake to me.

Unwinding also tends to happen when I start attacking cases of

X1 = foo(X),
X2 = bar(X1),
...

That's a code smell that has actually never once unwound itself entirely
as other areas of a project have been cleaned up (particularly once data
abstractions have been put in place). At most I ever wind up with three
versions of a value if it is undergoing some complex update that involves
something like `lists:keytake/3` -- which is somewhat rare. What used to
be local transform assignments tend to wind up being changes happening
out in some other function somewhere and I just get the completed value
back. That's not always obvious at the outset of a project, though,
because a lot of time it is a bit indefinite what the right balance (and
level of need) for carrying some local state around might be to get the
job done. Those "refactoring for purity" [1] efforts is when all the
X1, X2, .. XN type variable names disappear if they had popped up in the
first place.

Regarding using lists:foldl/3 to build pipelines... I occasionally see
(and occasionally write) pipelines using lists:foldl/3 in prototype code,
knowing those bits almost certainly won't survive the next refactoring.
You read my mind...

[1] "purity" I feel sure this isn't a proper word!

-Craig


More information about the erlang-questions mailing list