Функция Haskell с другим количеством аргументов
Я пытаюсь создать функцию Haskell с классом, чтобы заставить эту функцию работать с различным количеством аргументов.
{-# 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
Когда я пробую эту функцию с помощью ghci, я получаю следующие результаты:
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"
Я не понимаю, где моя ошибка и почему моя поливариадная функция не работает с более чем двумя аргументами.
Вы знаете откуда моя ошибка? и как заставить мою функцию работать с произвольным числом аргументов?
2 ответа
Ошибка возникает потому, что у вас есть ровно два экземпляра Titles
в вашей программе:
instance Titles String
instance Titles (String -> String)
Это позволит вам позвонить titleTeX
с одним и двумя аргументами соответственно, но для трех аргументов потребуется
instance Titles (String -> String -> String)
который не существует Или, как говорит GHC:
• No instance for (Main.Titles ([Char] -> [Char] -> String))
arising from a use of ‘titleTeX’
([Char]
такой же как String
.)
Это как если бы вы определили функцию
foo :: [Int] -> Int
foo [x] = ...
foo [x, y] = ...
но foo [x, y, z]
это ошибка.
Чтобы это работало для любого количества аргументов, нам нужно использовать рекурсию. Как и в случае со списком функций (где у вас обычно есть базовый случай foo [] = ...
и рекурсивный случай foo (x : xs) = ...
что вызывает foo xs
где-то), нам нужно определить Titles
экземпляр с точки зрения других случаев:
instance Titles String
instance (Titles a) => Titles (String -> a)
Хитрость в том, что я не вижу способа реализовать titleTeX
это соответствует вышеуказанным декларациям.
Мне пришлось внести другие изменения в ваш код, чтобы он работал:
{-# Language FlexibleInstances #-}
titleTeX :: (Titles a) => String -> a
titleTeX str = titleTeXAccum [str]
titleTeX
больше не метод. Это просто удобный интерфейс для актуального titleTeXAccum
метод.
В принципе, мы могли бы опустить String
параметр и определяется titleTeX :: (Titles a) => a
как titleTeX = titleTexAccum []
, но потом titleTex :: String
произойдет сбой во время выполнения (потому что мы в конечном итоге вызов maximum
в пустом списке).
class Titles a where
titleTeXAccum :: [String] -> a
Наш метод теперь принимает список строк, которые он (каким-то образом) превращает в значение типа a
,
instance Titles String where
titleTeXAccum acc = titleWithFrame 1 "%" "%" "%" (reverse acc)
Реализация для String
это просто: мы просто звоним titleWithFrame
, Мы также передаем reverse acc
потому что порядок элементов в аккумуляторе обратный (см. ниже).
instance (Titles a) => Titles (String -> a) where
titleTeXAccum acc str = titleTeXAccum (str : acc)
Это важная часть: общее titleTeXAccum
метод переходит к другому titleTeXAccum
метод (другого типа / другого Titles
пример). Добавляет str
к аккумулятору. Мы могли бы написать acc ++ [str]
добавить новый элемент в конце, но это неэффективно titleTeXAccum
с N элементами займет O(N^2) времени (из-за повторного обхода списка в ++
). С помощью :
и только звонит reverse
один раз в конце сводит это к O(N).
Это работает, если вы используете функцию, которую вы представили, titleTeX
и не какую-то другую функцию, которую вам еще предстоит показать, titleLaTeX
,