Reputation: 21
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
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
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