Reputation: 1123
Say the functions foo
, bar
noo
are the basics in my program. Furthermore, these functions can be implemented in different way in different scenarios (foo1, bar1
, foo2, bar2
, etc), though foo1
and foo2
still have the same input and output types. According to some input or configuration, the program use foo1, bar1
in some scenario, while in another scenario, foo2, bar2
.
I could have just defined them as described above, appending suffix (1,2,3..) to foo, bar, noo
. However, this is not pretty as the suffix could be long; neither does it allow special binding of foo1
with bar1
(vs. bar2
).
An alternative would be treating each scenario as a separate Module
. Now foo, bar, noo
for each case are nicely hold together, and the ugly suffix is avoided. However, this introduces many files when having one file per Module
. Another downside of this approach is that these Modules
are completed separated even though they do share some similarity (e.g. three functions).
A typeclass
solution would be appreciated but not came across my mind, as the different foo
s of different scenarios have the same input and output.
I am wondering is there any Haskell best practice for the problem to avoid the aforementioned shortcomings of these approaches.
foo1 :: Double -> Double
bar1 :: Int -> Int
noo1 :: [Int] -> [Int]
foo2 :: Double -> Double
bar2 :: Int -> Int
noo2 :: [Int] -> [Int]
...
foo9 :: Double -> Double
bar9 :: Int -> Int
noo9 :: [Int] -> [Int]
EDIT: I guess it is relevant for the discussion to explain how I would approach it through Java Interface
(A few nice, but conceptual-level, discussion of Java interface
and Haskell typeclass
can be found at this post and here.) Java interface and class
can be complicated for many cases, but here the overloading is actually concise.
interface Scenario {
double foo(double d);
int bar(int i);
Array<int> noo(Array<int> a);
}
class UseScenario {
void use(Scenario ss) {
ss.foo(...);
ss.bar(...);
ss.noo(...);
}
}
class S1 implements Scenario {
double foo(double d) {...};
int bar(int i) {...};
Array<int> noo(Array<int> a) {...};
}
class S2 implements Scenario {
double foo(double d) {...};
int bar(int i) {...};
Array<int> noo(Array<int> a) {...};
}
Upvotes: 4
Views: 466
Reputation: 14678
One good way would be to put all the functions into a single data type. Then have different values of that type for each different strategy. Finally, choose a default strategy, and link the actual functions tho the default strategy (for ease of use). Eg:
module MyModule where
data Strategy = Strategy {
fooWithStrategy :: Double -> Double
, barWithStrategy :: Int -> Int
, nooWithStrategy :: [Int] -> [Int]
}
defaultStrategy :: Strategy
defaultStrategy = Strategy {
fooWithStrategy = (*2)
, barWithStrategy = (+2)
, nooWithStrategy = id
}
foo :: Double -> Double
foo = fooWithStrategy defaultStrategy
bar :: Int -> Int
bar = barWithStrategy defaultStrategy
noo :: [Int] -> [Int]
noo = nooWithStrategy defaultStrategy
tripleStrategy :: Strategy
tripleStrategy = Strategy {
fooWithStrategy = (*3)
, barWithStrategy = (*3)
, nooWithStrategy = \x -> x ++ x ++ x
}
customAddStrategy :: Int -> Strategy
customAddStrategy n = Strategy {
fooWithStrategy = (+ (fromIntegral n))
, barWithStrategy = (+ n)
, nooWithStrategy = (n :)
}
This allows a number of useful features:
customAddStrategy
). You could also mix and match strategies, eg newStrat = defaultStrategy { nooWithStrategy = nooWithStrategy tripleStrategy, fooWithStrategy = (*4) }
foo
, bar
and noo
) are available for users new to the libraryUpvotes: 6