【问题标题】:Choosing an implementation of a function with a given signature选择具有给定签名的函数的实现
【发布时间】:2014-04-19 07:30:26
【问题描述】:

我想要一些代码,我想调用一个函数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...

然而,这基本上是在尖叫“有一个更优雅™的解决方案,但你就是不明白!”有没有更好的解决方案?

【问题讨论】:

  • 既然可以直接传函数,为什么还要使用moduleToChoose呢?能不能直接做bar :: (String -> IO [String]) -> IO [String],以后调用函数的时候,在需要的地方直接做baz <- bar A.foo x

标签: haskell


【解决方案1】:

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

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

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

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.

另一种可能性是使用 newtype 包装 StringFooable 类,类似于 Lee Duhem 的回答。无论哪种方式,使用类对我来说都感觉有点过头了,所以我会坚持使用简单的 sum 类型的解决方案。

另一种方法是使用函数记录而不是类型类。这在这里也太过分了,尽管可以说比使用类要少:

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 }

【讨论】:

  • 我同意除了第一个解决方案之外的每个解决方案都有些矫枉过正,所以我就这么做了。感谢您的想法!
【解决方案2】:

我会更改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] 函数的过程应该归入程序的最外层。这样,依赖关系被最小化,并且“未找到策略”错误被更快地检测到。

【讨论】:

  • 是的,我也考虑过使用地图。但由于我有很多不会改变的实现,所以不必如此。我还重构了这些东西,以将选择与传递函数分离。
【解决方案3】:

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

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"])

【讨论】:

  • AB 在这种情况下最好用 newtype 定义。
  • @duplode 在这种特殊情况下,是的,你是对的。感谢您指出这一点。
猜你喜欢
  • 1970-01-01
  • 2021-10-16
  • 2018-07-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-11
  • 1970-01-01
相关资源
最近更新 更多