Reputation: 2741
I'm trying to create a Haskell function with a class to get this function to work with different numbers of arguments.
{-# Language FlexibleInstances #-}
class Titles a where
titleTeX :: String -> a
instance Titles String where
titleTeX str = titleWithFrame 1 "%" "%" "%" [str]
instance Titles (String -> String) where
titleTeX str = (\s -> titleWithFrame 1 "%" "%" "%" (s:[str]))
titleWithFrame::Int -> String -> String -> String -> [String] -> String
titleWithFrame nb beg end com lstr =
cadr++cont++cadr
where
cadr = concat $ replicate nb (beg++rempl++end++"\n")
cont = concatMap (\s -> beg++" "++s++" "++end++"\n") lstr
rempl = take long $ concat $ replicate long com
long = (maximum $ map length lstr) + 2
When I try this function with ghci, I have the following results:
ghci> putStr $ titleTeX "Line 1"
%%%%%%%%%%
% Line 1 %
%%%%%%%%%%
ghci> putStr $ titleTeX "Line 1" "Line 2"
%%%%%%%%%%
% Line 1 %
% Line 2 %
%%%%%%%%%%
ghci> putStr $ titleTeX "Line 1" "Line 2" "Line 3"
<interactive>:4:10: error:
• No instance for (Main.Titles ([Char] -> [Char] -> String))
arising from a use of ‘titleTeX’
(maybe you haven't applied a function to enough arguments?)
• In the second argument of ‘($)’, namely
‘titleTeX "Line 1" "Line 2" "Line 3"’
In the expression: putStr $ titleTeX "Line 1" "Line 2" "Line 3"
In an equation for ‘it’:
it = putStr $ titleTeX "Line 1" "Line 2" "Line 3"
I don't understand where my error is and why my polyvariadic function doesn't work with more than 2 arguments.
Do you know where my error comes from? and How to make my function work with an arbitrary number of arguments?
Upvotes: 2
Views: 424
Reputation: 85897
The error occurs because you have exactly two instances of Titles
in your program:
instance Titles String
instance Titles (String -> String)
These let you call titleTeX
with one and two arguments respectively, but three arguments would require
instance Titles (String -> String -> String)
which doesn't exist. Or as ghc puts it:
• No instance for (Main.Titles ([Char] -> [Char] -> String))
arising from a use of ‘titleTeX’
([Char]
is the same as String
.)
It's as if you'd defined a function
foo :: [Int] -> Int
foo [x] = ...
foo [x, y] = ...
but foo [x, y, z]
is an error.
To make this work for any number of arguments, we need to use recursion. As with list functions (where you'd typically have a base case foo [] = ...
and a recursive case foo (x : xs) = ...
that calls foo xs
somewhere), we need to define a Titles
instance in terms of other instances:
instance Titles String
instance (Titles a) => Titles (String -> a)
The tricky bit is that I don't see a way to implement a titleTeX
that fits the above declarations.
I had to make other changes to your code to make it work:
{-# Language FlexibleInstances #-}
titleTeX :: (Titles a) => String -> a
titleTeX str = titleTeXAccum [str]
titleTeX
isn't a method anymore. It's just a convenience front-end for the actual titleTeXAccum
method.
In principle we could have omitted the String
parameter and defined titleTeX :: (Titles a) => a
as titleTeX = titleTexAccum []
, but then titleTex :: String
would crash at runtime (because we end up calling maximum
on an empty list).
class Titles a where
titleTeXAccum :: [String] -> a
Our method now takes a list of strings that it (somehow) turns into a value of type a
.
instance Titles String where
titleTeXAccum acc = titleWithFrame 1 "%" "%" "%" (reverse acc)
The implementation for String
is easy: We just call titleWithFrame
. We also pass reverse acc
because the order of elements in the accumulator is backwards (see below).
instance (Titles a) => Titles (String -> a) where
titleTeXAccum acc str = titleTeXAccum (str : acc)
This is the crucial part: The general titleTeXAccum
method forwards to another titleTeXAccum
method (of a different type / different Titles
instance). It adds str
to the accumulator. We could have written acc ++ [str]
to add the new element at the end, but that's inefficient: Calling titleTeXAccum
with N elements would take O(N^2) time (due to repeated list traversals in ++
). Using :
and only calling reverse
once at the end reduces this to O(N).
Upvotes: 2
Reputation: 64750
It works if you use the function you presented, titleTeX
, and not some other function you have yet to show, titleLaTeX
.
Upvotes: 2