design patterns - how to refactor this Haskell chain of functions code? -
i have software design experience, , learning haskell now. in many real world software developments, 1 faces situation 1 given, instance, below:
suppose, have code
f1 b c d = e e1 = f2 b c (f3 a) e2 = f4 d e = e1 + e2 f2 b c d = n + c + d n = f5 b f5 n = n*n f3 = * 2 f4 = + 3
now if want change f5 takes yet parameter, have change chain of functions right upto f1. can done shown below. note added parameter x.
f1 b c d x = e -- f1 needs changed e1 = f2 b c (f3 a) x e2 = f4 d e = e1 + e2 f2 b c d x = n + c + d -- f2 needs changed n = f5 b x f5 n x = n*n -- f5 changed (**bang**) f3 = * 2 f4 = + 3
is normal haskell way of doing type of thing or there better (more haskell-ish) way? know such change in api disturb client code, how impact can kept minimum , there hasekll way it?
on more general level: how haskell perform in such cases (especially taking consideration immutable state feature)? has offer developers in regard? or haskell has got no role, per se, play in , difficult software engineering problem (no such thing future proof) have keep with?
i apologize asking more 1 question in single post, cannot these related each other. also, not find similar question asked, sorry if might have missed it.
one thing can bundle parameters single parameter object, bhelkir suggests in comment. if add new parameter object still need change client code calls f1
, , change direct consumers of new parameter (here, f5
), both unavoidable: has provide x
@ point, , need client; , have consume x
somehow otherwise why adding begin with?
but, can avoid changing intermediary functions f1
, f2
, because can ignore new fields don't care about. , can little fancy using applicative
instance ((->) t)
(generally called reader
) pass along object, rather doing manually. here 1 way write that:
module test import control.applicative data settings = settings {geta :: int, getb :: int, getc :: int, getd :: int} f1 :: settings -> int f1 = lifta2 (+) f2 f4 -- f1 = -- e1 <- f2 -- e2 <- f4 -- return $ e1 + e2 f2 :: settings -> int -- clever lifta3 , (+) possible here f2 s = f5 s + getc s + f3 s f3 :: settings -> int f3 = lifta (* 2) geta f4 :: settings -> int f4 = lifta (+ 3) getd f5 :: settings -> int -- f5 = lifta (join (*)) getb -- perhaps bit opaque f5 = lifta square getb square b = b * b
now, has pluses , minuses: logic in f1
(ie, knowing need call f3
a
) has moved f3
itself, , happen function read parameter , mucked before passing along subsidiary function. may clearer original, or may obscure intent behind f1
, depending on problem domain. can write function more explicitly, eg having modify passed-in settings
object change a
field before passing along, did f2
example. more generally, can write function in whichever style convenient it: do-notation, applicative functions, or plain old pattern-matching on record object being passed in.
but big plus it's easy add new parameter: add field settings
record, , read in function needs it:
module test import control.applicative data settings = settings {geta :: int, getb :: int, getc :: int, getd :: int, getx :: int} f1 :: settings -> int f1 = lifta2 (+) f2 f4 -- f1 = -- e1 <- f2 -- e2 <- f4 -- return $ e1 + e2 f2 :: settings -> int -- clever lifta3 , (+) possible here f2 s = f5 s + getc s + f3 s f3 :: settings -> int f3 = lifta (* 2) geta f4 :: settings -> int f4 = lifta (+ 3) getd f5 :: settings -> int f5 = lifta2 squareadd getb getx squareadd b x = b * b + x
note same except data settings
, f5
.
Comments
Post a Comment