Функция 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,

Другие вопросы по тегам