Define Functions with arg list, or Sequence
如凉老师上文所述, 我们可以通过 MyFun @@ {arg0, arg1, ..., argn} 的方式调用函数 MyFun[arg0, arg1, ..., argn]
以及通过 Symbol["arg" <> ToString[i]] 的方式从字符串产生变量 argi
于是我们可以向函数中传入一长串的参数, 而不使用列表:
In[1]:= MyFun @@ Table[Symbol["arg" <> ToString[i]], {i, 5}]
Out[1]= MyFun[arg1, arg2, arg3, arg4, arg5]
下面我们还需要定义一个可以处理任意输入的函数, 不然我们只能盯着这个函数干瞪眼, 什么也运行不了.
定义方式很简单, 直接使用 MyFun[args___]:= (参数名后面加三个下划线) 就可以定义一个有参数列表的函数. 如果除了一个可变长度的参数列表, 还需要几个固定参数, 我们还可以通过在args前后指定一些固定参数来实现混合模式如
In[2]:= MyFun1[args___, a_, b_] := {"...ab", {args}, {a, b}}
MyFun1[args___, a_, b_, c_] := {"...abc", {args}, {a, b, c}}
MyFun1[1, 2, 3, 4, 5, 6]
MyFun1[1, 2, 3]
MyFun1[1, 2]
Out[2]= {"...abc", {1, 2, 3}, {4, 5, 6}}
{"...abc", {}, {1, 2, 3}}
{"...ab", {}, {1, 2}}
In[3]:= MyFun1[a_, args___] := {"a...", {args}, {a}}
MyFun1[a_, args___, b_] := {"a...b", {args}, {a, b}}
MyFun1[a_, b_, args___] := {"ab...", {args}, {a, b}}
MyFun1[1, 2, 3, 4, 5, 6]
MyFun1[1, 2]
MyFun1[1]
Out[3]= {"a...b", {2, 3, 4, 5}, {1, 6}}
{"a...b", {}, {1, 2}}
{"a...", {}, {1}}
上面的例子说明, Mathematica 选择带参数列表的函数时, 优先级顺序从上到下:
- 排除固定参数数量大于输入参数数量的函数 (输入参数数量不足的函数重载不处理)
- 尽可能选择不以参数列表开头的函数
- 除参数列表外, 固定参数最多的函数
- 若固定参数长度相同, 使用先定义的函数
关于 args 的形式
上面的定义中我都使用了 {args] 的呈现形式, 它会被转为 {arg1, arg2,...} , 这其中是什么过程呢.
我们可以直接使用 MyFun[args___]:=args 这种不使用花括号的形式, 它会产生 Sequence[1, 2, 3] 这种样式的输出. 这说明 args 的类型是 Sequence.
Sequence[expr1,expr2,…] 表示将参数序列自动拼接到函数. 我认为它可以被看出是一种临时数据类型, 只能在函数调用时自动生效, 它会将逗号拼接起来 MyFun[1, Sequence[2, 3, 4], 5] 变成 MyFun[1, 2, 3, 4, 5] 再调用. 当未发生函数调用时, 它如同一个基本类型一样不可分解.
因此, 我们上面使用的 {args} 本质上是进行了一次列表构造函数调用, {args} 等效于 List[args] 等效于 List[Sequence[arg0, arg1, ...]] 等效于 List[arg0, arg1, ...] 最后会获得一个列表 {arg0, arg1, ...}
简单的用法
我们可以定义简单的函数
In[4]:= MyArray[1] := {1}
MyArray[n_] := {n}~Join~MyArray[n - 1]
MyReduce[f_, a_] := a
MyReduce[f_, a_, args___] := f[a, MyReduce[f, args]]
MyReduce[Plus, Sequence @@ MyArray[10]]
MyReduce[f, Sequence @@ MyArray[10]]
Out[4]= 55
f[10, f[9, f[8, f[7, f[6, f[5, f[4, f[3, f[2, 1]]]]]]]]]
或者一些复杂的公式(例如这种多项式推导)
In[5]:= MyPolynomials[n_] := MyReduce @@ {D[(x^#1 - 1)/#1*#2, x] &}~Join~MyArray[n]~Join~{Sin[x]/(x^2 + 1)};
MyReduce @@ {Hold[D[(x^#1 - 1)/#1*#2, x]] &}~Join~MyArray[4]~Join~{Sin[x]/(x^2 + 1)} (* 打印形式 *)
Plot[Evaluate[MyPolynomials /@ MyArray[7]], {x, -1, 1}, PlotLegends -> ("MyPolynomials_" <> ToString[#] & /@ MyArray[7])]


利用函数调用, 整一些好玩的.
比如, 我们可以搞一个混乱的二分递归计算 (因为你也不知道哪些数字被略过了...)
In[6]:= Remove[BinHalf, BinReduce]
BinHalf[args___] := Partition[{args}, Floor[Length[{args}]/2]]
(* 将参数列表分成一样大的两半, 如果长度不是 2 的倍数, 忽略最后一个 *)
BinReduce[f_, a_] := a
(* 当参数只有一个时, 直接返回该参数 *)
BinReduce[f_, a_, b_] := f[a, b]
BinReduce[f_, a_, b_, c_] := f[a, b]
(* 当恰有两或三个参数时, 返回 f[a, b] *)
BinReduce[f_, args___] := f @@ (BinReduce[f, Sequence @@ #] & /@ BinHalf[args])
(* 当参数大于两个时, 分成两半, 各自作为参数递归调用, 然后计算两个结果的 f[res1, res2] *)
In[7]:= BinReduce @@ {Plus}~Join~Table[i, {i, 11}]
(* = (((1+2)+(3+4))!5)+((6+7)+(8+9))!10)!11 其中, 感叹号后的数字是被扔掉的 *)
BinReduce @@ {NIntegrate[x^(1/(2 + #1)), {x, 0, #2}] &}~Join~Table[i, {i, 23}]
BinReduce @@ {f}~Join~Table[i, {i, 23}] (* 可以通过这种未定义f的形式打印调用结构 *)
Out[7]= 40
31.0691
f[f[f[f[1, 2], f[3, 4]], f[f[6, 7], f[8, 9]]], f[f[f[12, 13], f[14, 15]], f[f[17, 18], f[19, 20]]]]