import Data.ConfigFile
data Test = Test
{ field1 :: Int
, field2 :: Bool
, field3 :: String
} deriving (Show)
whatMyConfigLooksLike =
[ ("field1", "5")
, ("field2", "True")
, ("field3", "I am a string")
]
options = fst . unzip $ whatMyConfigLooksLike
readConfigFile = do
rv <- runErrorT $ do
cp <- join . liftIO $ readfile emptyCP "theconfig.cfg"
let printn = liftIO . putStrLn
getn = get x "DEFAULT"
x = cp
printn "Loading configuration file..."
-- I don't want to do the following
one <- getn "field1"
two <- getn "field2"
three <- getn "field3"
return $ Test one two three -- ...
-- ... and so on because I have a data type with many fields
-- I want to fold them onto the data constructor instead
return $ foldl (\f s -> getn s >>= f) (Test) options
-- but I think this doesn't type check because f's type is constantly changing?
print rv
In the above code I have a lambda with a very polymorphic type foldl (\f s -> getn s >>= f)
. From what I can tell, this causes it to not typecheck in its following recursions.
I think that I can use the RankNTypes language extension for my purpose to define a polymorphic recursive type that can represent any partial application of a function and, hence, allow the function to typecheck. With experimentation, much trial and equal amounts of error, though, I have been unable to come up with anything which compiles.
I would be very grateful if somebody can show me how to implement the RankNTypes extension in terms of the example code above (or suggest alternatives). I'm using GHC 7.4.2.
TL;DR: Generate your code for you using the module listed at the bottom under "Code Summary"
You can't bear to write all that boilerplate code. I can very much sympathise. There are other approaches, but we could use Template Haskell to generate the code we need.
If you're new to Template Haskell, you should have a look at the haskellwiki page.
First, let's turn on the Template Haskell extension, and import Control.Applicative to tidy the code up a bit:
What template haskell code should we generate?
And let's ask ghci to convert an appropriate expression for us. (I faked a
getn
function for convenience so I can use it in standalone code.)Woah! let's tidy that up a bit, and make it valid haskell code. Firstly note that an expression such as
Data.Functor.<$>
is actually of typeName
. To get it we could domkName "<$>"
, but string mangling is the ugliest sort of source code manipulation, so let's do'(<$>)
instead, which generates the (fully qualified) name from the function:The (hidden) beauty of this is that it's just a load of very similar expressions that we can fold together.
Generating the expressions we need
Let's use
<<*>>
as a sort of lift of<*>
to glue expressions together with<*>
:Now when we get the fields, we'll first apply the constructor via
<$>
to the first field, then we can use that as the base for a fold over the other fields.A quick check:
The stage restriction bites
We could test/use that in the same source file with
except that you'll run into the rather inconvenient stage restriction:
That means that you'll have to define
getFields
etc in another module, and then import that into your main file where you can splice it in.Code Summary
GetFields.hs:
Main.hs:
What you are trying to do is not possible. The typechecker does not know how many elements are in the list, and thus how many arguments you are trying to pass to the constructor. You do, but that is irrelevant in a statically checked language.
RankNTypes is not going to help because the root problem is not the type of your lambda (even if that is where the typechecker throws the error. The problem is with your accumulator:
foldl
has type(a -> b -> a) -> a -> [b] -> a
; Note, in particular, that no matter how many extensions you throw at the typechecker, the accumulator must have the same type at each point in the fold.Test
has typeInt -> Bool -> String -> Test
; its first partial application has typeBool -> String -> Test
, and there is no way to unify those types.If the rest of your program is well-typed, however, you should be able to use simply
liftM3 Test (getn "field1") (getn "field2") (getn "field3")
as your return, which is little more verbose and far clearer than what you were attempting.