Haskell设计了与类似功能集相关的多个模块的最佳实践

时间:2013-07-30 23:44:23

标签: java design-patterns haskell interface typeclass

假设函数foobar noo是我程序中的基础知识。此外,这些功能可以在不同的场景foo1, bar1foo2, bar2等)中以不同的方式实现,但foo1foo2仍有相同的输入和输出类型。根据某些输入或配置,程序在某些场景中使用foo1, bar1,而在另一个场景中使用foo2, bar2

我可以如上所述定义它们,将后缀(1,2,3 ..)附加到foo, bar, noo。然而,这并不漂亮,因为后缀可能很长;它也不允许foo1bar1(与bar2)进行特殊绑定。

另一种方法是将每个场景视为单独的Module。现在每个案例的foo, bar, noo很好地保持在一起,避免了丑陋的后缀。但是,当每个Module有一个文件时,这会引入许多文件。这种方法的另一个缺点是这些Modules完全分开,即使它们确实共享一些相似性(例如三个函数)。

typeclass解决方案会受到赞赏,但我不介意,因为不同场景的不同foo具有相同的输入和输出。

我想知道是否有任何Haskell问题的最佳实践,以避免上述这些方法的缺点。

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]

编辑:我想这对于解释如何通过Java Interface来解决这个问题很重要(对Java interface的一些不错但概念层面的讨论可以在this posthere找到Haskell typeclass。)Java interface and class在很多情况下都很复杂,但这里的重载实际上很简洁。

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) {...};
}

1 个答案:

答案 0 :(得分:6)

一个好方法是将所有函数放入单个数据类型中。然后针对每种不同的策略使用该类型的不同值。最后,选择默认策略,并将实际功能链接到默认策略(为了便于使用)。例如:

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 :)
  }

这允许许多有用的功能:

  1. 可自定义的策略(例如customAddStrategy)。您还可以混合和匹配策略,例如newStrat = defaultStrategy { nooWithStrategy = nooWithStrategy tripleStrategy, fooWithStrategy = (*4) }
  2. 用户可以在运行时切换策略
  3. 默认值(即foobarnoo)可供新手使用的用户使用
  4. 您或其他用户使用更多策略轻松扩展。