Mathematica中的柯里化

33

在Mathematica中,可以使用以下结构实现一种有限形式的柯里化

f[a_][b_][c_] := (a^2 + b^2)/c^2

允许人们执行以下操作,例如:

f[4][3] /@ Range@5
{25,25/4,25/9,25/16,1}

这里有一个问题: Attributes 只能应用于第一个(组)参数。请考虑:

ClearAll[f]
SetAttributes[f, HoldAllComplete]

f[a_][b_][c_] :=
  {ToString@Unevaluated@a,
   ToString@Unevaluated@b,
   ToString@Unevaluated@c}

f[2 + 2][ 8/4 ][3 + 5]
{"2 + 2", "2", "8"}

我的意图是在列表中返回"8 / 4""3 + 5"


因此:

  • 有没有一种方法可以扩展到这个结构的属性?

  • 有没有其他方便的构造实现这个?

  • 除了属性之外,还有其他方式可以扩展Mathematica中的Currying吗?


2
自从11.3版本以后,现在可以使用本地的Curry:https://reference.wolfram.com/language/ref/Curry.html - gdelfino
提醒一下:根据文档,Curry 正在逐步被 CurryAppliedOperatorApplied 取代。 - evanb
5个回答

18

我认为没有办法使属性适用于这种“upvalue”模式定义的后部。

一种替代方法是使用具有属性的纯函数。虽然不如模式匹配方便,但当你评估f [2 + 2] [8/4]时,它实际上会给出Curry喜欢的结果。 (如果您熟悉lambda演算,则“Function”是Mathematica的“lambda”。)

f = Function [a,Function [b,Function [c,HoldForm @ {a,b,c},HoldAll],HoldAll],HoldAll]

我假设你想要像下面这样做:

f [2 + 2] [2/1] / @ Unevaluated @ {1 + 1,3 + 3} →    {{2 + 2,2 / 1,1 + 1},{2 + 2,2 / 1,3 + 3}}

如果您经常执行此类操作,则可以使其输入稍微容易些:

hf [args_,body _]:= Function [args,body,HoldAll]; SetAttributes [hf,HoldAll];

f = hf [a,hf [b,hf [c,HoldForm @ {a,b,c}]]]

Mathematica Cookbook在第73-77页介绍了一种完全不同的Currying方法。

作为一般准则,如果您尝试控制Mathematica何时评估表达式,您会感到痛苦。在许多情况下,更好的方法是使用符号作为不想要评估的表达式的占位符,然后在评估其中一个表达式时,可以将所需表达式替换为该符号。


15

很抱歉发表一个可能与主题无关的评论。我刚刚搜索了“在Mathematica中使用currying”,这个问题是谷歌列表中的第一个结果。虽然这个问题已经有一年的历史并且已经得到了答案,但我认为所提出的解决方案不太优雅。我的简单修改应该如下:

ClearAll[f]
SetAttributes[f, HoldAllComplete]
f[a_, b_, c_] := {ToString@Unevaluated@a, ToString@Unevaluated@b,
ToString@Unevaluated@c}
f[a__] := Function[x, f[a, x], HoldAll]

这会得到所期望的结果:

f[2+2][2+1] /@ Unevaluated@{1+1, 3+3}{{2+2, 2+1, 1+1}, {2+2, 2+1, 3+3}}

对于三个可能的参数分割,它运作良好。

f[1 + 1, 2 + 2, 6 + 1]
f[1 + 1, 2 + 2][6 + 1]
f[1 + 1][2 + 2][6 + 1]

对于{"1+1", "2+2", "6+1"},此方法可以给出正确的结果,但是对于f[1 + 1][2 + 2, 6 + 1]则会失败。为了解决这个问题,可以使用稍微高级一点的版本:

ClearAll[f, g]
SetAttributes[f, HoldAllComplete]
SetAttributes[g, HoldAllComplete]
f[a_, b_, c_] := (ClearAll[g]; SetAttributes[g, HoldAllComplete]; 
  Thread[Hold[{a, b, c}]] /. {Hold[e_] :> ToString@Unevaluated[e]})
f[a__] := (g[x__] := f[a, x]; g)

欢迎Grisha Kirilin。虽然这是已经介绍过的相同基本方法,但它看起来确实更加简洁。自从我提出这个问题以来,我学到了很多,并且在相关应用程序中使用了类似于您展示的形式,因此我希望这能够工作,但我需要更仔细地测试一下才能确定。谢谢!顺便说一句,现在有一个完整的StackExchange网站专门为Mathematica用户服务。请考虑加入我们这里 - Mr.Wizard
哇,我没想到会有人读到它。感谢您的评论。我添加了一个更高级的版本,以便函数可以处理像这样的组合 f[1+1][2+2,6+1]。所以我需要在Mathematica StackExchange上开一个新账户。 - Grisha Kirilin
当我的问题有新回答时,我会收到通知,并且无论它们何时出现,我都会阅读所有收到的答案。期待在Mathematica.SE见到你 :-) - Mr.Wizard
顺便说一句:List @@ ToString /@ Unevaluated /@ Hold[a, b, c] - Mr.Wizard

10

我不知道有什么方法可以将属性扩展到第二个或更多的柯里化参数列表,虽然我很想听听有关这方面的信息。

您可以使用纯函数实现与柯里化表达式具有相同外观的表达式的定义(尽管我不会称其为“方便”):

ClearAll[f, f1, f2]
SetAttributes[{f, f1, f2}, HoldAllComplete]
f[a_] := Function[b, f1[a, b], HoldAllComplete]
f1[a_, b_] := Function[c, f2[a, b, c], HoldAllComplete]
f2[a_, b_, c_] :=
  { ToString@Unevaluated@a
  , ToString@Unevaluated@b
  , ToString@Unevaluated@c
  }

f[2+2][8/4][3+5]

使用现在不被记录的符号HeadCompose可以匹配一个柯里化表达式:

In[65]:= MatchQ[g[x][y][z], HeadCompose[g, x_, y_, z_]]
Out[65]= True

虽然这种能力对于当前的问题没有帮助。 HeadCompose 几个版本前就已经被弃用了,最终从文档中删除。但我不知道有任何其他方法可以匹配柯里化表达式。我推测它被弃用的原因正是因为不能有效地附加属性和定义,使其成为一个可怕的状态:此符号尚未完全集成到长期的Mathematica系统中,并且可能会发生变化。


1
我相信我们可以使用一个单一的符号 f 替代 ff1f2,这似乎更合适。 - Mr.Wizard
@Mr.W同意,我们可以使用一个单一的符号。我想象中f1f2可能是私有符号,这样用户就不会意外输入f[1, 2, 3]并得到结果。当然,这可能是期望的行为。 - WReach
我理解你的观点。虽然我需要试用一段时间,但我认为第二种形式甚至更加可取。 - Mr.Wizard

6
有一种自动实现的方法。考虑以下函数:
f[a_, b_, c_] := {a, b, c}

我们希望将其隐式地“柯里化”,这样它就可以以以下任何一种方式调用:

f[1, 2, 3]
f[1, 2][3]
f[1][2][3]

如果有一种自动生成以下定义的方法(我们在下面进行),那么这就可以实现:
f[a_, b_, c_] := {a, b, c}
f[a_, b_] := Function[c, f[a, b, c]]
f[a_] := Function[b, Function[c, f[a, b, c]]]

如Matt在上面的答案中所述,我们可以只定义一个函数:f:=Funcion[a,Function[b,Function[c, BODY]]],但是这样我们将无法通过f[a,b,c]或f[a,b]调用f,而只能像f[a][b]或f[a][b][c]那样调用它。使用多个定义,我们可以选择不同的风格。

生成这些定义可以通过函数CurryableSetDelayed(下面定义)来完成,只需调用:

CurryableSetDelayed[f[a_, b_, c_], {a, b, c}]

即使定义了这些符号中的任何一个,它也会像SetDelayed一样正常工作。

此外,使用Notation包,您可以将其显示为赋值运算符;例如f [a_,b_,c]#={c,b,a},但我没有尝试过。

在下面的源代码中,我使用了一些临时符号,这些符号可能与会话冲突,因此如果您要使用此代码,请将其放入包名称空间中。

完整代码:

ClearAll[UnPattern];
ClearAll[MakeFunction]
ClearAll[CurriedDefinitions]
ClearAll[MyHold]
ClearAll[MyHold2]
ClearAll[CurryableSetDelayed]

SetAttributes[UnPattern,HoldAllComplete];
SetAttributes[MakeFunction,HoldAllComplete];
SetAttributes[CurriedDefinitions,HoldAllComplete]
SetAttributes[MyHold,HoldAllComplete]
SetAttributes[MyHold2,HoldAllComplete]
SetAttributes[CurryableSetDelayed,HoldAllComplete]

UnPattern[x_]:=Block[{pattern},MyHold[x]/. Pattern->pattern/. pattern[v_,_]:>v]

MakeFunction[param_,body_,attrs_]:=With[{p=UnPattern[param],b=UnPattern[body]},
  Block[{function},MyHold[function[p,b,attrs]]/. function->Function]]

CurriedDefinitions[fname_[args__],body_,attrs_]:=MapThread[MyHold2[#1:=#2]&,
  {Rest[(MyHold[fname]@@#1&)/@NestList[Drop[#1,-1]&,{args},Length[{args}]-1]],
   Rest[FoldList[MakeFunction[#2,MyHold[#1],Evaluate[attrs]]&,MyHold[fname[args]],
     Reverse[Drop[{args},1]]]]}]

CurryableSetDelayed[fname_[args__],body_]:={MyHold2[fname[args]:=body],
  Sequence@@CurriedDefinitions[fname[args],body,Attributes[fname]]}
  //. MyHold[x_]:>x/. MyHold2[x_]:>x

更新,现在 Attributes (HoldAllComplete 等) 扩展到所有参数,因此只要在调用 CurryableSetDelayed 之前设置属性,以下代码将按预期工作:

In[1185]:= ClearAll[f];
SetAttributes[f, {HoldAllComplete}]
CurryableSetDelayed[
  f[a_, b_, c_], {ToString@Unevaluated@a, ToString@Unevaluated@b, 
   Unevaluated@c, Hold@c}];
f[1 + 1, 2 + 2, c + 1]
f[1 + 1, 2 + 2][c + 1]
f[1 + 1][2 + 2][c + 1]

Out[1188]= {"1 + 1", "2 + 2", Unevaluated[c + 1], Hold[c + 1]}

Out[1189]= {"1 + 1", "2 + 2", Unevaluated[c + 1], Hold[c + 1]}

Out[1190]= {"1 + 1", "2 + 2", Unevaluated[c + 1], Hold[c + 1]}

我尝试了你的代码,但是设置fHoldAllComplete似乎并没有使第二个和第三个参数被保留。你的意图是让它们被保留吗? - Mr.Wizard
谢谢您的欢迎。为使其有效,我们需要让生成的所有Function[]继承与f相同的属性。我现在尝试解决的唯一问题是当f的主体中有Hold[]时,我的代码会干扰它们。一旦我找到解决方案,我将更新答案。 - Mohammad Alaggan
更新后的代码适用于“Unevaluated”,就像你的问题一样,但如果主体包含“Hold”,它就不起作用。 - Mohammad Alaggan
现在它已经可以正确地工作了。代码需要清理,一旦准备好了,我将更新为更干净的版本。 - Mohammad Alaggan

6

虽然我来晚了,不能直接回答问题(其他帖子已经回答得很好了)。但是我想指出,可以通过使用Stack和异常来实现一种非局部控制。这有点丑陋,但我认为它还没有被完全探索。下面是一个例子:

ClearAll[f];
f := With[{stack = Stack[_]},
   With[{fcallArgs = 
      Cases[stack, HoldForm[f[x_][y_][z_]] :> Hold[x, y, z]]},
      Throw[First@fcallArgs] /; fcallArgs =!= {}]];


In[88]:= Catch[f[2+2][8/4][3+5]]

Out[88]= Hold[2+2,8/4,3+5]

这利用了头部优先于元素的递归求值事实。从这里可以看出,人们能够以这种方式提取未求值的参数,并且可以在进一步处理中使用它们。然而计算被中断了。从Stack[_]中提取足够的信息以恢复计算也应该是可能的。我不确定能否在Mathematica中实现continuations,但如果可以的话,应该沿着这些线路进行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接