选择具有给定签名的函数的实现

时间:2014-04-19 07:30:26

标签: haskell

我想要一些代码,我想调用一个函数foo,其中foo的不同实现驻留在不同的模块中。

喜欢

foo :: String -> IO[String]

模块A:

foo :: String -> IO[String]
foo x = whatever

模块B:

foo :: String -> IO[String]
foo x = whatever (different)

然后根据某个参数调用相应的函数。我可以使用合格的导入:

import qualified ModuleA as A
import qualified ModuleB as B

bar :: String -> String -> IO[String]
bar moduleToChoose x = case moduleToChoose of
    "A" -> A.foo x
    "B" -> B.foo x
    _ -> Uh oh...

然而,这基本上是尖叫着“有一个更优雅的解决方案,但你只是没有得到它!”有更好的解决方案吗?

3 个答案:

答案 0 :(得分:5)

模块在Haskell中不是第一类,因此无法直接将它们用作参数。无论如何,对您的解决方案的改进将是使用和类型以更丰富和更安全的方式对区别进行编码。对于模块,类型和值(即实际反映您要尝试做的名称)的有意义的名称,这可以感觉很自然:

import qualified Formatting.Mimsy as Mimsy
import qualified Formatting.Slithy as Slithy

data FormattingStyle = Mimsy | Slithy

foo :: FormattingStyle -> String -> IO [String]
foo style x = case style of
    Mimsy  -> Mimsy.foo x
    Slithy -> Slithy.foo x

可能想要更进一步,并使用类型类对case开关进行编码:

class Fooer a where
    foo :: a -> String -> IO [String]

data Mimsy = Mimsy

instance Fooer Mimsy where
    foo _ x = undefined -- etc.

data Slithy = Slithy

instance Fooer Slithy where
    foo _ x = undefined -- etc.

另一种可能性是在newtypeString类附近使用Fooable包装,类似于Lee Duhem的回答。无论哪种方式,使用类对我来说都有点矫枉过正,所以我坚持使用简单的求和类型解决方案。

另一种方法是使用函数记录而不是类型类。这也是过度的,尽管可能不如使用类:

data Fooer = Fooer { foo :: String -> IO [String] }

-- Export only mimsy and slithy, and not the Fooer constructor.

mimsy :: Fooer
mimsy = Fooer { foo = Mimsy.foo }

slithy :: Fooer
slithy = Fooer { foo = Slithy.foo }

答案 1 :(得分:1)

我会更改bar直接将辅助功能作为参数,而不是使用选择器参数。

bar :: (String -> IO [String]) -> String -> IO [String]

在函数外部(可能在我的Main.hs中)我将构建以下映射:

strategies :: M.Map String (String -> IO [String])
strategies = M.fromList [("A",A.foo), ("B",B.foo)]

此映射允许我们将已知实现的聚合与选择要使用的实现分开。

查看地图并构建我们感兴趣的实际bar' :: String -> IO [String]函数的过程应该降级到程序的最外层。这样,依赖性被最小化并且更快地检测到“未找到策略”错误。

答案 2 :(得分:0)

一种可能的解决方案是使用用户定义的类型类,如下所示:

FooClass.hs

module FooClass (FooClass(..)) where

class FooClass a where
    bar :: a -> IO [String]

A.hs

module A where

import FooClass

data A = A String deriving (Show)

foo :: A -> IO [String]
foo (A s) = return ["A", s]

instance FooClass A where
    bar = foo

B.hs

module B where

import FooClass

data B = B String deriving (Show)

foo :: B -> IO [String]
foo (B s) = return ["B", s]

instance FooClass B where
    bar = foo

在所有这些代码之后,您可以像这样使用它们:

t.hs

import FooClass
import A
import B

main = do
    a <- bar (A "bar")
    b <- bar (B "bar")
    putStrLn $ show (a, b)

测试:

$ runhaskell t.hs
(["A","bar"],["B","bar"])