具有不同数量参数的Haskell函数

时间:2018-11-23 01:20:16

标签: haskell typeclass polyvariadic

我正在尝试使用一个类创建一个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个参数。

您知道我的错误来自哪里吗?以及如何使函数使用任意数量的参数?

2 个答案:

答案 0 :(得分:2)

如果您使用显示的功能titleTeX,而不使用尚未显示的其他功能titleLaTeX,则此功能有效。

答案 1 :(得分:1)

发生错误是因为程序中恰好有两个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会在运行时崩溃(因为我们最终调用了{{1 }}上的空白列表。

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来在末尾添加新元素,但这效率不高:用N个元素调用acc ++ [str]会花费O(N ^ 2)时间(由于{{1 }})。使用titleTeXAccum并仅最后一次调用++会将其减少为O(N)。