2

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

你知道我的错误来自哪里吗?以及如何使我的函数与任意数量的参数一起工作?

4

2 回答 2

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 xsTitles

instance Titles String
instance (Titles a) => Titles (String -> a)

棘手的一点是我没有看到一种方法来实现titleTeX符合上述声明的 a 。

我必须对您的代码进行其他更改才能使其正常工作:

{-# Language FlexibleInstances #-}

titleTeX :: (Titles a) => String -> a
titleTeX str = titleTeXAccum [str]

titleTeX不再是一种方法。titleTeXAccum它只是实际方法的便利前端。

原则上我们可以省略String参数并定义titleTeX :: (Titles a) => atitleTeX = titleTexAccum [],但是titleTex :: String会在运行时崩溃(因为我们最终调用maximum了一个空列表)。

class Titles a where
  titleTeXAccum :: [String] -> a

我们的方法现在接受一个字符串列表,它(不知何故)变成了 type 的值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) 时间(由于重复的列表遍历 in ++)。最后使用:并且只调用reverse一次将其减少到 O(N)。

于 2018-11-23T22:35:51.743 回答
2

如果您使用您提供的功能,它可以工作titleTeX,而不是您尚未展示的其他功能,titleLaTeX

于 2018-11-23T02:29:59.457 回答