通过将函数定义拆分为多个文件来提高模块性

时间:2011-09-17 19:00:06

标签: haskell types typeclass

我无法将我的haskell代码视为我想要的模块化代码。我可能只是陷入了面向对象的范式,在功能上思考困难,但我完全被难倒了。

我有一个数据和两个操作它的函数:

data TruthType = TT_Boolean String 
               | TT_Percent Double

conjunction :: TruthType -> TruthType -> TruthType
disjunction :: TruthType -> TruthType -> TruthType

通常,您可以将这些功能紧挨着彼此实现,如下所示:

conjunction :: TruthType -> TruthType -> TruthType
conjunction (TT_Percent x) (TT_Percent y) = TT_Percent (x*y)
conjunction (TT_Boolean "t") (TT_Boolean "t") = TT_Boolean "t"
conjunction (TT_Boolean "t") (TT_Boolean "f") = TT_Boolean "f"
conjunction (TT_Boolean "f") (TT_Boolean "t") = TT_Boolean "f"
conjunction (TT_Boolean "f") (TT_Boolean "f") = TT_Boolean "f"

disjunction :: TruthType -> TruthType -> TruthType
disjunction (TT_Percent x) (TT_Percent y) = TT_Percent (x + (1-x)*y)
disjunction (TT_Boolean "t") (TT_Boolean "t") = TT_Boolean "t"
disjunction (TT_Boolean "t") (TT_Boolean "f") = TT_Boolean "t"
disjunction (TT_Boolean "f") (TT_Boolean "t") = TT_Boolean "t"
disjunction (TT_Boolean "f") (TT_Boolean "f") = TT_Boolean "f"

这个编译和运行完全像我期望的那样。问题是,我计划实现大约20种不同的TruthTypes,以及每种TruthTypes的更多功能。因此,根据它们所采用的TruthType构造函数对函数进行分组更有意义:

-- TT_Percent
conjunction (TT_Percent x) (TT_Percent y) = TT_Percent (x*y)
disjunction (TT_Percent x) (TT_Percent y) = TT_Percent (x + (1-x)*y)

-- TT_Boolean
conjunction (TT_Boolean "t") (TT_Boolean "t") = TT_Boolean "t"
conjunction (TT_Boolean "t") (TT_Boolean "f") = TT_Boolean "f"
conjunction (TT_Boolean "f") (TT_Boolean "t") = TT_Boolean "f"
conjunction (TT_Boolean "f") (TT_Boolean "f") = TT_Boolean "f"

disjunction (TT_Boolean "t") (TT_Boolean "t") = TT_Boolean "t"
disjunction (TT_Boolean "t") (TT_Boolean "f") = TT_Boolean "t"
disjunction (TT_Boolean "f") (TT_Boolean "t") = TT_Boolean "t"
disjunction (TT_Boolean "f") (TT_Boolean "f") = TT_Boolean "f"

如果这两个部分都在同一个文件中,我会收到一个编译错误,声称我正在重新定义连接和析取函数。我不想删除旧的定义,我希望两个定义都有效。是否有任何编译器标志可用于重新定义?

最终,我的目标是让每个不同的TruthTypes都在其自己的文件中定义。如果我这样做,那么我会得到一个歧义错误,因为它不知道使用哪个函数。有没有办法让GHC尝试所有这些,因为只有一个实际上会被定义在被调用的TruthType上?

PS。这似乎是类型类的一个很好的用例,但实际上并非如此。我必须能够编写返回TruthType“实例”的函数,类似于此示例中的“classReturn”函数:

class (Show a, Eq a) => TruthClass a where
    conjunction :: a -> a -> a
    disjunction :: a -> a -> a

instance TruthClass Bool where

    conjunction True  True  = True
    conjunction True  False = False
    conjunction False True  = False
    conjunction False False = False

    disjunction True  True  = True
    disjunction True  False = True
    disjunction False True  = True
    disjunction False False = False

instance TruthClass Double where
    conjunction x y = x*y
    disjunction x y = x + (1-x)*y

classReturn :: (TruthClass a) => String -> a   -- This fails to compile because it would allow the failure function below, which violates conjunction's type
classReturn "True" = True
classReturn "False" = False
classReturn "1" = 1
classReturn "0" = 0

failure = conjunction (classReturn "True") (classReturn "1")

编辑:

好的,我现在可以更好地解释为什么我无法使类型类工作,以及为什么提供的解决方案对我不起作用。请看以下内容(基于下面的奥古斯都解决方案):

*Main> conjunction True True -- works because type is inferred
True

*Main> classReturn "True" :: Bool -- works because type is explicitly stated
True

*Main> classReturn "True" -- does not work, but this is what I need

<interactive>:1:0:
    Ambiguous type variable `a' in the constraint:
      `TruthClass a'
        arising from a use of `classReturn' at <interactive>:1:0-17
    Probable fix: add a type signature that fixes these type variable(s)

在我的程序中,我将无法指定它是哪种类型。我正在使用parsec解析输入文件。当它命中一行“#bool”时,创建的所有后续变量都应该是TT_Boolean类型。当它达到“#percent”时,所有后续变量都应为TT_Percent类型。因此,当我调用函数时,我无法硬编码类型,如果使用类型类,似乎必须硬编码。使用数据的解决方案解决了这个问题,但是由于数据导致缺乏模块化。

2 个答案:

答案 0 :(得分:3)

class (Read a, Show a, Eq a) => TruthClass a where
    conjunction :: a -> a -> a
    disjunction :: a -> a -> a
    classReturn :: String -> a
    classReturn = read

instance TruthClass Bool where
    conjunction = (&&)
    disjunction = (||)

instance TruthClass Double where
    conjunction x y = x*y
    disjunction x y = x + (1-x)*y

答案 1 :(得分:0)

但您也可以保留原始设计,只是在disjunction的等式之间不能有conjunction的等式,反之亦然。

函数由其所有方程组成,但它们必须在源代码中连续出现。

编辑:展示迈克想要做的一个例子:

如果你有这么多条款,你可以将一个单一的大功能分成多个:

conjunction PrincipleCase1 = conjunctionForCase1 ...
conjunction PrincipleCase2 = conjunctionForCase2 ...

然后你可以将详细案例的功能放在不同的位置,模块等等。