Haskell best practice design for multiple modules has a similar set of features.

Say that the functions foo , bar noo are basic in my program. In addition, these functions can be implemented differently in different scenarios ( foo1, bar1 , foo2, bar2 , etc.), although foo1 and foo2 still have the same input and output types. According to some input or configuration, the program uses foo1, bar1 in some scenario, and in another scenario - foo2, bar2 .

I could just define them as described above by adding the suffix (1,2,3 ...) to foo, bar, noo . However, this is not so, since the suffix can be long; it also does not allow the special binding of foo1 with bar1 (vs. bar2 ).

An alternative would be to consider each scenario as a separate Module . Now foo, bar, noo for each case are well kept together, and an ugly suffix is ​​excluded. However, this introduces many files when there is one file per Module . Another drawback of this approach is that these Modules terminated separated even if they have some similarities (e.g. three functions).

Decision

A typeclass would be appreciated, but it didn’t come across me, since different foo different scripts have the same input and output.

I am wondering if there is any Haskell best practice for the problem in order to avoid the above-mentioned drawbacks 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 think it’s important for the discussion that I explain how I approach it through the Java Interface (Some nice, but conceptual discussion levels of the Java Interface and Haskell typeclass can be found in this post here too .) Java interface and class can be difficult for many cases, but here the overload 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) {...}; } 
+4
source share
1 answer

One good way would be to turn all functions into one data type. Then they have different values ​​of this type for each other strategy. Finally, select the default strategy and bind the actual functions to the default strategy (for ease of use). For instance:

 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 you to use a number of useful functions:

  • Custom strategies (e.g. customAddStrategy ). You can also mix and match strategies, for example newStrat = defaultStrategy { nooWithStrategy = nooWithStrategy tripleStrategy, fooWithStrategy = (*4) }
  • Users can switch strategies at runtime
  • By default (i.e. foo , bar and noo ) are available for users new to the library
  • Easily extends with lots of strategies from you or other users.
+6
source

Source: https://habr.com/ru/post/1494386/


All Articles