如何创建多变量 Haskell 函数?

发布于 2024-09-14 05:15:04 字数 542 浏览 4 评论 0原文

我需要一个函数,它接受任意数量的参数(全部相同类型),对它们执行某些操作,然后返回结果。在我的具体情况下,列出参数是不切实际的。

当我查看 haskell 库时,我发现函数 printf(来自模块 Text.Printf)使用了类似的技巧。不幸的是,我无法通过查看源代码来理解这种魔力。

有人可以解释如何实现这一点,或者至少在一些网页/论文/任何我可以找到对此的良好描述的地方吗?

动机:

我需要这个的原因非常简单。对于学校(计算机科学课),我们需要编写一个能够“记录”数学表达式的模块,将其表达为字符串(通过为自己的数据类型编写 Num/Real/etc 的实例),并执行对其进行各种操作。

此数据类型包含变量的特殊构造函数,可以用指定函数的值或其他内容替换该变量。目标之一是编写一个函数,该函数采用带有一定数量变量((Char,Rational) 类型对)的表达式并计算表达式的结果。我们应该看看如何最好地表达函数的目标。 (我的想法:该函数返回另一个函数,该函数接受与函数中定义的变量完全相同的参数 - 似乎是不可能的)。

I need a function which takes an arbitrary number of arguments (All of the same type), does something with them and afterwards gives a result back. A list of arguments is impracticable in my specific case.

As I looked through the haskell libs, I saw that the function printf (from module Text.Printf) uses a similar trick. Unfortunately, I couldn't understand that magic by looking at the source.

Can somebody explain how to achieve this, or at least some webpage/paper/whatever where I could find a good description for this?

Motivation:

The reason I need this is really quite simple. For school (computer science class), we are required to write a module that is able to "record" a mathematical expression, express it as a string (Via writing an instance of Num/Real/etc for an own datatype), and perform various operations on it.

This datatype contains a special constructor for a variable, which may be replaced by a value or whatever by a specified function. One of the goals is to write a function, which takes such an expression with some number of variables (pairs of type (Char,Rational)) and calculates the result of the expression. We should look at how to express the goal of the function best. (My idea: The function returns another function which takes exactly as many arguments as vars that are defined in the function - seems to be impossible).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

提笔书几行 2024-09-21 05:15:05

KennyTM 的回答很棒。下面以 sumOf 1 4 7 10 :: Integer 的执行过程为例来更好地说明。

sumOf 1 4 7 10
(( \ x -> ( sumOf . (x +) . toInteger ) 1 ) 4 7 10
((sumOf . (1 + ) . toInteger) 4 ) 7 10
( sumOf 5 ) 7 10
( sumOf . (5 + ) . toInteger ) 7 10
sumOf 12 10
sumOf . (12 + ) . toInteger 10
sumof 22
id 22
22

KennyTM's answer is great. Below is an example of the exec process of sumOf 1 4 7 10 :: Integer to give a better illustration.

sumOf 1 4 7 10
(( \ x -> ( sumOf . (x +) . toInteger ) 1 ) 4 7 10
((sumOf . (1 + ) . toInteger) 4 ) 7 10
( sumOf 5 ) 7 10
( sumOf . (5 + ) . toInteger ) 7 10
sumOf 12 10
sumOf . (12 + ) . toInteger 10
sumof 22
id 22
22
最佳男配角 2024-09-21 05:15:05

很多人告诉你如何创建可变参数函数,但我认为在这种情况下,你实际上最好只使用 [(Char,Rational)] 类型的列表。

Lots of people are telling you how to create variadic functions, but I think in this case you're actually better off just using a list of type [(Char,Rational)].

愛放△進行李 2024-09-21 05:15:05

在有关可变参数函数的 wiki 文章中,引用了这篇文章。我想这就是 printf 所做的,但我也不明白。无论如何,这肯定是一种矫枉过正,特别是因为你的论点都是同一类型。只需将它们全部放在一个列表中即可。这就是列表的好处——任意数量的相同类型的事物。好吧,它不是很漂亮,但它不会比完整的多变量函数更难看。

In the wiki article on variadic functions, this article was referenced. I suppose this is what printf does, but I don't understand it either. Anyway, this is certainly an overkill, especially since your arguments are all of the same type. Just put them all in one list. That's what lists are good for - an arbitary number of things of the same type. Fine, it's not very beautiful, but it will hardly be uglier than a complete polyvariadic function.

零度° 2024-09-21 05:15:05

我查看了 delnan 引用的文章中链接的示例。盯着它看了一会儿之后,我想我终于明白了发生了什么:

它从这个类型类开始:

class BuildList a r  | r-> a where
    build' :: [a] -> a -> r

管道(|)后面的那一位是函数依赖。它表示可以通过r 表示的类型来确定a 表示的类型。换句话说,您无法定义具有相同 r(返回类型)但不同 aBuildList 类型类的两个实例。

跳转到 build' 函数实际使用的地方:

> build True :: [Bool]

因为 build 只是调用 build' 并将空列表作为第一个参数,这与:

> build' [] True :: [Bool]

在此示例中,build' 显然返回一个列表。由于功能依赖性,我们只能绑定到 BuildList 类型类的此实例:

instance BuildList a [a] where
    build' l x = reverse$ x:l

非常简单。第二个例子更有趣。扩展build的定义,它变成:

> build' [] True False :: [Bool]

在这种情况下build'的类型是什么?好吧,Haskell 的优先规则意味着上面的内容也可以这样写:

> (build' [] True) False :: [Bool]

现在很明显,我们将两个参数传递给 build',然后将该表达式的结果应用到一个参数值为“假”。换句话说,表达式 (build' [] True) 预计会返回 Bool -> 类型的函数。 [布尔]。这将我们绑定到 BuildList 类型类的第二个实例:

instance BuildList a r => BuildList a (a->r) where
    build' l x y = build'(x:l) y

在此调用中,l = []x = Truey = False,因此定义扩展为 build' [True] False :: [Bool]。该签名绑定到 build' 的第一个实例,并且从那里开始的走向相当明显。

I took a look at an example linked from the article that delnan referenced. After staring at it for a bit, I think I finally comprehend what is going on:

It starts with this type class:

class BuildList a r  | r-> a where
    build' :: [a] -> a -> r

That bit after the pipe (|) is a functional dependency. It says that the type represented by a can be determined by the type represented by r. In other words, you are prevented from defining two instances of the BuildList typeclass with the same r (return type), but different a.

Jumping ahead a bit to where the build' function is actually used:

> build True :: [Bool]

Since build is just calling build' with an empty list as the first parameter, this is the same as:

> build' [] True :: [Bool]

In this example, build' is clearly returning a list. Because of the functional dependency, we can only be binding to this instance of the BuildList type class:

instance BuildList a [a] where
    build' l x = reverse$ x:l

Pretty straightforward. The second example is more interesting. Expanding the definition of build, it becomes:

> build' [] True False :: [Bool]

What's the type of build' in this case? Well, the precedence rules of Haskell mean that the above could also be written like this:

> (build' [] True) False :: [Bool]

Now it becomes clear that we are passing two parameters to build' and then applying the result of that expression to a parameter with value 'False'. In other words, the expression (build' [] True) is expected to return a function of type Bool -> [Bool]. And that binds us to the second instance of the BuildList typeclass:

instance BuildList a r => BuildList a (a->r) where
    build' l x y = build'(x:l) y

In this invocation, l = [] and x = True and y = False, so the definition expands to build' [True] False :: [Bool]. That signature binds to the first instance of build', and it's fairly obvious where it goes from there.

圈圈圆圆圈圈 2024-09-21 05:15:04

printf 的关键点是能够返回字符串或函数。复制自 http ://www.haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/src/Text-Printf.html

printf :: (PrintfType r) => String -> r
printf fmts = spr fmts []

class PrintfType t where
    spr :: String -> [UPrintf] -> t

instance (IsChar c) => PrintfType [c] where
    spr fmts args = map fromChar (uprintf fmts (reverse args))

instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) where
    spr fmts args = \a -> spr fmts (toUPrintf a : args)

我们可以提取出的基本结构是

variadicFunction :: VariadicReturnClass r => RequiredArgs -> r
variadicFunction reqArgs = variadicImpl reqArgs mempty

class VariadicReturnClass r where
   variadicImpl :: RequiredArgs -> AccumulatingType -> r

instance VariadicReturnClass ActualReturnType where
   variadicImpl reqArgs acc = constructActualResult reqArgs acc

instance (ArgClass a, VariadicReturnClass r) => VariadicReturnClass (a -> r) where
   variadicImpl reqArgs acc = \a -> variadicImpl reqArgs (specialize a `mappend` acc)

例如:

class SumRes r where 
    sumOf :: Integer -> r

instance SumRes Integer where
    sumOf = id

instance (Integral a, SumRes r) => SumRes (a -> r) where
    sumOf x = sumOf . (x +) . toInteger

那么我们可以使用

*Main> sumOf 1 :: Integer
1
*Main> sumOf 1 4 7 10 :: Integer
22
*Main> sumOf 1 4 7 10 0 0  :: Integer
22
*Main> sumOf 1 4 7 10 2 5 8 22 :: Integer
59

The key points of printf is the ability to either return a String or a function. Copied from http://www.haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/src/Text-Printf.html,

printf :: (PrintfType r) => String -> r
printf fmts = spr fmts []

class PrintfType t where
    spr :: String -> [UPrintf] -> t

instance (IsChar c) => PrintfType [c] where
    spr fmts args = map fromChar (uprintf fmts (reverse args))

instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) where
    spr fmts args = \a -> spr fmts (toUPrintf a : args)

and the basic structure we can extract out is

variadicFunction :: VariadicReturnClass r => RequiredArgs -> r
variadicFunction reqArgs = variadicImpl reqArgs mempty

class VariadicReturnClass r where
   variadicImpl :: RequiredArgs -> AccumulatingType -> r

instance VariadicReturnClass ActualReturnType where
   variadicImpl reqArgs acc = constructActualResult reqArgs acc

instance (ArgClass a, VariadicReturnClass r) => VariadicReturnClass (a -> r) where
   variadicImpl reqArgs acc = \a -> variadicImpl reqArgs (specialize a `mappend` acc)

For instance:

class SumRes r where 
    sumOf :: Integer -> r

instance SumRes Integer where
    sumOf = id

instance (Integral a, SumRes r) => SumRes (a -> r) where
    sumOf x = sumOf . (x +) . toInteger

then we could use

*Main> sumOf 1 :: Integer
1
*Main> sumOf 1 4 7 10 :: Integer
22
*Main> sumOf 1 4 7 10 0 0  :: Integer
22
*Main> sumOf 1 4 7 10 2 5 8 22 :: Integer
59
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文