Mike Izbicki
Mike Izbicki

Reputation: 21

Increasing modularity by splitting a function definition into multiple files

I can't get my haskell code as modular as I want it to be. I'm probably just stuck in my object oriented paradigm and having trouble thinking functionally, but I'm totally stumped.

I have a data and two functions which operate on it:

data TruthType = TT_Boolean String 
               | TT_Percent Double

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

Normally, you would implement these functions right next to each other, like this:

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"

This compiles and runs exactly like I expect it. The problem is, that I plan on implementing about 20 different TruthTypes, and many more functions for each one. So it makes more sense to group my functions based on which TruthType constructor they are acting upon:

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

If both of these sections are in the same file, I get a compilation error claiming that I am redefining the conjunction and disjunction functions. I don't want to erase the old definition, I want both definitions to be valid. Are there any compiler flags that I can use to allow this redefining?

Ultimately, my goal is to have each of these different TruthTypes defined in it's own file. If I do that, then I get an ambiguity error because it doesn't know which function to use. Is there a way to get GHC to try all of them since only one will actually be defined on the TruthType being called against?

PS. This may seem like a great use case for type classes, but it's actually not. I have to be able to write functions that return "instances" of TruthType, something like the "classReturn" function in this example:

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

Edit:

Okay, I can now explain better why I couldn't get the type classes to work, and why the offered solutions don't work for me. Look at the following (based on augustss's solution below):

*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)

In my program, I won't be able to specify which type it is. I am parsing an input file using parsec. When it hits a line "#bool" all the subsequent variables created should be of type TT_Boolean. When it hits "#percent" all the subsequent variables should be of type TT_Percent. Therefore, I can't hard code what the type will be when I call a function, and it seems that you must hard code it if you use a type class. The solution using data solves this problem, but runs into the lack of modularity caused by data.

Upvotes: 1

Views: 231

Answers (2)

Ingo
Ingo

Reputation: 36349

But you can also keep your original design, only that you must not have equations for disjunction between equations for conjunction and vice versa.

A function consists of all its equations, but they must occur contiguously in the source code.

EDIT: show an example how what Mike wants can be done:

If you have that many clauses, you can split you one single great function into multiple ones:

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

and then you can put the function that handsle the detailed case in different positions, modules and whatever.

Upvotes: 0

augustss
augustss

Reputation: 23014

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

Upvotes: 3

Related Questions