如何在一个地方定义与With []一起使用的常量,然后再应用它们?

时间:2012-01-07 09:26:36

标签: wolfram-mathematica

我喜欢使用With[]和我需要在两个不同位置使用的常量。

我没有在2个地方输入相同长的常量列表,而是试图计算如何使用这个列表的变量,然后在我想要使用列表的几个地方使用这个变量。

问题是我需要Hold列表,然后ReleaseHold稍后才能使用它,但我无法正确使用此部分。 (尝试过很多东西,对我来说没什么用)

以下是一个例子:

With[{$age = 2, $salary = 3},
 x = $age
 ]

With[{$age = 2, $salary = 3},
 y = $age
 ]

我不想在周围键入相同的常量(在我的情况下,这些常量非常长),我试图做这样的事情:

c = HoldForm[{$age = 2, $salary = 3}]

With[Release[c],
 x = $age
 ]

With[Release[c],
 y = $age
 ]

我尝试了上述的许多其他组合。如此多的Hold *和Release *版本,我发现它们都非常令人困惑。

问题是:任何人都知道如何执行上述操作,因此我可以在多个地方重复使用该列表而不实际复制它?

在上下文中,这是一个需要这样的例子:

我无法在Manipulate中使用With包围所有内容:(并且我无法将With放在Manipulate之外,因为我正在做的事情,一切都必须在Manipulate

Manipulate[

 Evaluate@With[{$age = 2, $salary = 3},
   x;
   $age,

   {{x, $age, "age="}, 0, 10, 1}
   ]

 ]

语法无效。 (由于“,”需要将Manipulate表达式与控件分开)。 (它现在认为有3个参数)

我当然可以做到

Manipulate[

 With[{$age = 2, $salary = 3},
  x;
  $age
  ],

 Evaluate@With[{$age = 2, $salary = 3},
   {{x, $age, "age="}, 0, 10, 1}
   ]

 ]

但是如你所见,我不得不复制常量列表。

如果我可以计算一次如何定义列表,我可以将其放在Initialization上的Manipulate部分并使用它,如下所示:

Manipulate[

 With[ReleaseHold[myList],
  x;
  $age
  ],

 Evaluate@With[ReleaseHold[myList],
   {{x, $age, "age="}, 0, 10, 1}
   ],

 Initialization :>
  (
   myList = HoldAll[{$age = 2, $salary = 3}];
   )

 ]

我认为我想做的事情是可能的,我无法想出用于保持/释放保持部分的正确命令。

编辑(1)

我想我举例说明了为什么我要在With中使用常量。

我想出了这个新方法:)允许我模拟记录或结构。

常量值将是记录的命名字段(实际上只是一个列表)。

对于字段名称,我给它一个序号,(我从1开始)然后我使用$field=number,然后在代码中,我写struct[[$field]]=...来访问该字段。

我需要将命名字段的值共享到Manipulate表达式和控件区域之间的结构,因为两者都需要使用相同的结构。

下面是一个简单的Manipulate示例,它从UI和表达式中读取年龄和当前工资,并根据当前工资和以前的工资分配新工资。

该记录用于在控制区域与表达式和低级函数之间传递数据。

在我目前的演示中,我有数百个这样的参数,(我实际上现在有几个演示,所有在一个演示中,我根据在UI上选择的选项在不同的UI布局(控件)之间切换)并使用记录将简化我的生活,因为现在我可以进行函数调用,只传递几个参数,包含UI参数的记录,而不是100个单独的参数,这就是我现在要做的。正如我之前多次说过的,Mathematica需要一个真正的记录/结构作为基本数据结构,除了List和Array等,它们也被集成到M中。

(UI参数必须全部发送到较低级别的函数,除了这样做之外别无选择。我不想使用全局变量。设计不好。)

我现在也可以通过引用传递此记录如果我想允许更新发生在其他更低级别的函数中。我仍在评估这种方法,看看我是否可以在我的主代码中实际使用它。

 (*verison 1.1*)
Manipulate[

 With[{$age = 1, $salary = 2, $newSalary = 3},

  updateRecord[record_] := Module[{},

    (*update/process UI input*)

    record[[$newSalary]] = record[[$salary]] + record[[$age]]*10;

    (*return result*)
    record
    ];

  (*call lower level function to process UI parameters*)
  myRecord = updateRecord[Unevaluated@myRecord];

  (*display the result *)
  Grid[{
    {"age=", myRecord[[$age]]},
    {"current salary=", myRecord[[$salary]]},
    {"new salary=", myRecord[[$newSalary]]}
    }]
  ],

 (* build the UI controls *)
 Evaluate@With[{$age = 1, $salary = 2, $newSalary = 3},
   Grid[{

     {"age=",
      Manipulator[Dynamic[age, {age = #; myRecord[[$age]] = age} &],
       {10, 100, 1}, ImageSize -> Tiny], Dynamic[age]},

     {"salary=",
      Manipulator[
       Dynamic[salary, {salary = #; myRecord[[$salary]] = salary} &],
       {10, 10000, 10}, ImageSize -> Tiny], Dynamic[salary]}

     }
    ]
   ],

 {{myRecord, {10, 100, 100}}, None},
 {{age, 10}, None},
 {{salary, 1000}, None},
 TrackedSymbols -> {age, salary}

 ]

enter image description here

修改(2)

在尝试使用下面的Leonid时,我可以在Manipulate表达式中使用它,但无法确定如何在控制区域中使用它。

以下是在2个地方使用With的原始示例:

Manipulate[

 With[{$age = 2, $salary = 3},
  x + $age
  ],

 Evaluate@With[
   {$age = 2, $salary = 3},
   {{x, $age, "age="}, 0, 10, 1}
   ],
 {x, None}
 ]

现在使用下面的新Leonid方法,这是我到目前为止所做的:

Manipulate[

 env[
  x + $age
  ],

 Evaluate@With[
   {$age = 2, $salary = 3},
   {{x, $age, "age="}, 0, 10, 1}
   ],
 {x, None},

 Initialization :>
  (
   ClearAll[makeCustomEnvironment];
   SetAttributes[makeCustomEnvironment, HoldAll]; 
   makeCustomEnvironment[values : (_Symbol = _) ..] := 
    Function[code, With @@ Hold[{values}, code], HoldAll];

   env = makeCustomEnvironment[$age = 2, $salary = 3];

   )
 ]

但也可以将它用于控制吗?我不能这样做:

Manipulate[

 env[
  x + $age
  ],

 env[
  {$age = 2, $salary = 3},
  {{x, $age, "age="}, 0, 10, 1}
  ],
 {x, None},

 Initialization :>
  (
   ClearAll[makeCustomEnvironment];
   SetAttributes[makeCustomEnvironment, HoldAll]; 
   makeCustomEnvironment[values : (_Symbol = _) ..] := 
    Function[code, With @@ Hold[{values}, code], HoldAll];

   env = makeCustomEnvironment[$age = 2, $salary = 3];

   )
 ]

因为上面给出了很多错误。

修改(3)

已删除错误的

编辑(4)

删除了上面编辑(3)的内容,因为它包含我身边的用户错误报告问题。

以下是WRI对我收到错误的原因的支持回复

Manipulate::vsform: Manipulate argument env[{{age,100,age},10,200,1}] does
not have the correct form for a variable specification. >>

当我写下面代码时:

Manipulate[
  env[
   record[[$age]] = age;
   record[[$salary]] = 60*age;
   {record[[$age]], record[[$salary]]}
   ],

 env[
    {{age, 100, "age"}, 10, 200, 1}
    ],

  {{record, {40, 5}}, None},
  {{salary, 40000}, None},
  TrackedSymbols :> {age},
  Initialization :>
   (
    makeCustomEnvironmentAlt =
     Function[Null, Function[code, With @@ Hold[{##}, code], HoldAll],
      HoldAll];
    env = makeCustomEnvironmentAlt[$age = 1, $salary = 2];
    )

  ]

这是对此错误出现原因的支持说明:

The issue is specifically with the section:

Evaluate@env[{{age, 100, "age"}, 10, 200, 1}]

Manipulate doesn't really evaluate until it gets to the Initialization
option, but it will check its input for correct form. Mathematica reads the
main body of the Manipulate before running the Initialization option. This
is can be verified by using a Print statement:

Initialization -> (Print["Test"]; 
  makeCustomEnvironmentAlt = 
   Function[Null, Function[code, With @@ Hold[{##}, code], HoldAll], 
    HoldAll];
  env = makeCustomEnvironmentAlt[$age = 1, $salary = 2]; 
  Print["Test"])

Test does not print. 

Getting around this will be probably not be clean.
....
Having  the code for the controller for age depend on evaluation of 
some function which must be initialized does not appear to be possible 
with simply Manipulate. 

我希望这些信息有所帮助。感谢大家的帮助以及WRI的支持和解释。

2 个答案:

答案 0 :(得分:6)

我要做的是编写一个函数生成器来创建自定义(词法)环境:

ClearAll[makeCustomEnvironment];
SetAttributes[makeCustomEnvironment, HoldAll];
makeCustomEnvironment[values : (_Symbol = _) ..] :=
   Function[code, With @@ Hold[{values}, code], HoldAll];

这需要一个声明列表并创建一个纯函数,它在内部使用With封装的常量。我使用With@@Hold[...]来欺骗Function的重命名机制,以便它不会重命名With中的变量(而不是With,可以使用withRules @Szabolcs建议的函数,这将导致稍微不同的替换语义。)

现在,我们创建自定义函数:

env = makeCustomEnvironment[$age = 2, $salary = 3];

并像这样使用它:

In[25]:= 
env[x=$age];
x

Out[26]= 2

In[27]:= 
env[y=$age];
y

Out[28]= 2

这个构造对于保存的变量(带有规则或其他)的优点是,我们在这里封装行为而不是状态。这可以说是更清晰,更符合函数式编程范式(我们在这里创建一个闭包而不是实例化一个类)。

修改

显然,演示规则非常严格,不允许使用过时的代码。希望这个版本可以接受:

makeCustomEnvironmentAlt = 
 Function[Null,
   Function[code, With @@ Hold[{##}, code], HoldAll],
   HoldAll]

但是你必须记住输入参数的格式(由于使用了暗示模式,这对于初始解决方案来说很清楚)。

答案 1 :(得分:1)

我建议您使用ReplaceRule而不是With来实现此目的。请参阅例如withRules function I described here,我将其用于与您描述的目的类似的目的:将预定义的常量列表注入代码,而不必将这些常量定义为全局变量。

ClearAll[withRules]
SetAttributes[withRules, HoldAll]
withRules[rules_, expr_] :=
  Internal`InheritedBlock[
    {Rule, RuleDelayed},
    SetAttributes[{Rule, RuleDelayed}, HoldFirst];
    Unevaluated[expr] /. rules
  ]

用法:

constants = { a -> 1, b :> 2+2 }

withRules[ constants, a+b ]

请注意,您可以同时使用->:>,因此无需Hold常量。

一句警告:

另请注意,它与With的不同之处在于它并未真正本地化变量。考虑With[{x=1}, With[{x=2}, x]],它会重命名内部x中的With以避免冲突。 withRules不会这样做:无情地替换x中的每个withRules[{x-> ...}, ...],无论它是否出现在另一个FunctionWithModule中或不。


关于您的Manipulate示例,我只想将整个ManipulateWith打包在一起,如

With[
  {age = 2}, 
  Manipulate[{x, age}, {{x, age}, 0, 10, 1}]
]

你能解释一下为什么你想要里面的所有 Manipulate(如果你没有操纵age),以及为什么你不能用它包裹它With