2011-12-04 13 views
7

El mensaje de uso para Set nos recuerda que múltiples asignaciones se pueden realizar fácilmente en dos listas, sin tener que separar nada. Por ejemplo:Error al generar variables localizadas (como constantes)

Remove[x1, x2, y1, y2, z1, z2]; 
{x1, x2} = {a, b} 

Realiza la asignación y vuelve:

{a, b} 

Thread, comúnmente utilizado para generar listas de reglas, también puede ser llamado explícitamente para lograr el mismo resultado:

Thread[{y1, y2} = {a, b}] 
Thread[{z1, z2} -> {a, b}] 

Da:

{a, b} 
{z1 -> a, z2 -> b} 

Sin embargo, emplear este enfoque para generar constantes localizadas genera un error. Considere esta función ejemplo trivial:

Remove[f]; 
f[x_] := 
With[{{x1, x2} = {a, b}}, 
    x + x1 + x2 
    ] 
f[z] 

Aquí el mensaje de error:

With::lvset: "Local variable specification {{x1,x2}={a,b}} contains 
{x1,x2}={a,b}, which is an assignment to {x1,x2}; only assignments 
to symbols are allowed." 

La documentación mensaje de error (ref/message/With/lvw), dice en la sección 'Más información' que, "Este mensaje se genera cuando el primer elemento en With no es una lista de asignaciones a los símbolos ". Dada esta explicación, entiendo la mecánica de por qué mi tarea falló. No obstante, estoy desconcertado y me pregunto si esto es una restricción necesaria por parte de la IRG, o una supervisión menor del diseño que debe ser informada.

Así que aquí es mi pregunta:

¿Alguien puede arrojar algo de luz sobre este comportamiento y/u ofrecer una solución? Experimenté intentando forzar Evaluation, sin suerte, y no estoy seguro de qué más probar.

+1

A menudo reflexiono sobre tales elecciones de diseño, y a veces publico tales preguntas aquí. Lamentablemente, es raro que tales cosas se expliquen. Me he contentado con el hecho de que With/Block/Module solo funciona de manera diferente, y que 'Set' en el primer argumento de estos no está realmente realizando la operación' Set', sino que es sintaxis. –

+0

De mi lectura de la documentación del mensaje de error (parte citada al final de mi Q), me parece que hay una coincidencia de patrón simple que impulsa el comportamiento actual, es decir, que acepta patrones que tienen la Lista principal, que contiene solo elementos que evalúan a True para algo como MatchQ [Hold [Set [x, 1]], Hold [Set [Symbol_, _]]]. Si este es el caso, sería posible, al menos en mi pequeño mundo miope, modificar la coincidencia/prueba de patrones para permitir el comportamiento que busco, y que la documentación sobre Set parece implicar que debería funcionar (como una forma válida) de asignación). – telefunkenvf14

+0

No estoy en desacuerdo, pero no conozco ninguna forma de efectuar ese cambio. –

Respuesta

11

Lo que usted solicita es engañoso. Este es un trabajo para macros, como ya han expuesto los demás. Exploraré una posibilidad diferente: usar los mismos símbolos pero poner algunos envoltorios alrededor del código que desea escribir. La ventaja de esta técnica es que el código se transforma "léxicamente" y en "tiempo de compilación", en lugar de en tiempo de ejecución (como en las otras respuestas). En general, esto es más rápido y más fácil de depurar.

lo tanto, aquí es una función que transformaría el With con su sintaxis propuesta:

Clear[expandWith]; 
expandWith[heldCode_Hold] := 
Module[{with}, 
    heldCode /. With -> with //. { 
     HoldPattern[with[{{} = {}, rest___}, body_]] :> 
       with[{rest}, body], 
     HoldPattern[ 
     with[{ 
      Set[{var_Symbol, otherVars___Symbol}, {val_, otherVals___}], rest___}, 
      body_]] :> 
       with[{{otherVars} = {otherVals}, var = val, rest}, body] 
    } /. with -> With] 

Tenga en cuenta que este opera en código retenida. Esto tiene la ventaja de que no tenemos que preocuparnos por la posible evaluación del código ni al principio ni cuando finaliza expandWith. Aquí está cómo funciona:

In[46]:= [email protected][With[{{x1,x2,x3}={a,b,c}},x+x1+x2+x3]] 
Out[46]= Hold[With[{x3=c,x2=b,x1=a},x+x1+x2+x3]] 

Esto, sin embargo, no es muy cómodo de usar.Aquí es una función de la conveniencia de simplificar esto:

ew = Function[code, [email protected]@[email protected], HoldAll] 

podemos utilizar ahora como:

In[47]:= [email protected][{{x1,x2}={a,b}},x+x1+x2] 
Out[47]= a+b+x 

Por lo tanto, para que la expansión ocurra en el código, simplemente envuelva ew alrededor de ella. Aquí está su caso, para la definición de la función:

Remove[f]; 
ew[f[x_] := With[{{x1, x2} = {a, b}}, x + x1 + x2]] 

Ahora comprobar y ver que lo que tenemos es una definición más amplia:

?f 
Global`f 
f[x_]:=With[{x2=b,x1=a},x+x1+x2] 

La ventaja de este enfoque es que se puede envolver ew en torno a una arbitrariamente gran parte de tu código. Lo que sucede es que primero se genera código expandido a partir de él, como si lo escribiera usted mismo, y luego ese código se ejecuta. Para el caso de las definiciones de función, como f anterior, podemos decir que la generación de código ocurre en "tiempo de compilación", por lo que evita cualquier sobrecarga en tiempo de ejecución cuando use la función más tarde, lo que puede ser sustancial si la función se llama con frecuencia.

Otra ventaja de este enfoque es su capacidad de compilación: puede obtener muchas extensiones de sintaxis, y para cada una de ellas escribir una función similar a ew. Luego, siempre que estas funciones personalizadas de transformación de códigos no se concuerden entre sí, simplemente puede componerlas (anidar) para obtener un efecto acumulativo. En cierto sentido, de esta manera usted crea un generador de código personalizado que genera código válido de Mathematica a partir de algunas expresiones de Mathematica que representan programas en su lenguaje personalizado, que puede crear dentro de Mathematica usando estos medios.

EDITAR

Al escribir expandWith, que utiliza la aplicación de reglas iterativo para evitar el trato con el control de la evaluación, que puede ser un desastre. Sin embargo, para los interesados, aquí hay una versión que hace un trabajo explícito con fragmentos de código no evaluados.

Clear[expandWithAlt]; 
expandWithAlt[heldCode_Hold] := 
Module[{myHold}, 
    SetAttributes[myHold, HoldAll]; 
    heldCode //. HoldPattern[With[{Set[{vars__}, {vals__}]}, body_]] :> 
    With[{eval = 
       (Thread[Unevaluated[Hold[vars] = Hold[vals]], Hold] /. 
        Hold[decl___] :> myHold[With[{decl}, body]])}, 
     eval /; True] //. myHold[x_] :> x] 

Aunque me parece mucho más complicado que el primero.

+0

¡Maravilloso, Leonid! –

+0

Esto se acerca al ideal de usar 'With', pero no modifica la funcionalidad predeterminada. Fácil +1. –

+0

¿Crees que esto es posible combinar con una modificación de tipo [de Gayley-Villegas] (http://stackoverflow.com/a/5149656/421225) del 'With' incorporado? – Simon

6

el tutorial "LocalConstants", dice

el camino con [{x = Subíndice [x, 0], ...}, cuerpo] funciona es tomar cuerpo, y reemplazar cada ocurrencia de x , etc. en él por Subíndice [x, 0], etc. Puede pensar en Con como una generalización de /. operador, adecuado para la aplicación al código de Mathematica en lugar de otras expresiones.

En referencia a esta explicación parece obvio que algo así como

x + x1 + x2 /. {x1, x2} -> {a, b} 

no funcionará como se podría esperar en el Con la notación.

Supongamos que realmente quiere hackear esto. With[] tiene el atributo HoldAll, por lo tanto, todo lo que das como primer parámetro no se evalúa. Para que una asignación de vector así funcione, debería crear

With[{x1=a, x2=b}, ...] 

de la nota vectorial.Por desgracia,

Thread[{a, b} = {1, 2}] 

no funciona porque el argumento de hilo no se lleva a cabo y la asignación se evalúa antes de rosca se puede hacer nada.

Permite solucionar este

SetAttributes[myThread, HoldFirst]; 
myThread[Set[a_, b_]] := mySet @@@ Transpose[{a, b}] 

da

In[31]:= myThread[{a, b, c} = {1, 2, 3}] 
Out[31]= {mySet[a, 1], mySet[b, 2], mySet[c, 3]} 

Lo que parece prometedor en un primer momento, se acaba de mudar el problema un poco lejos. Para usar esto en With[], debe reemplazar en algún momento el mySet con el Juego real. Exactamente entonces, With[] no ve la lista {a = 1, b = 2, c = 3}, pero, puesto que ha de ser evaluado, el resultado de todas las asignaciones

In[32]:= With[ 
Evaluate[myThread[{a, b, c} = {1, 2, 3}] /. mySet :> Set], a + b + c] 

During evaluation of In[32]:= With::lvw: Local 
variable specification {1,2,3} contains 1, which is not an assignment to a symbol. >> 

Out[32]= With[{1, 2, 3}, a + b + c] 

Parece que hay no manera fácil alrededor de esto y hay una segunda pregunta aquí: si hay una forma de evitar esta restricción, ¿es tan rápido como sería o perderemos la ventaja de velocidad en comparación con el Módulo? Y si la velocidad no es tan importante, ¿por qué no usar Módulo o Bloque en primer lugar?

+0

halirutan, welcome a StackOverflow. –

+0

Su último párrafo parece sugerir que este problema no afecta a 'Módulo' o' Bloque', pero lo hace. O bien, ¿está diciendo que hay una solución alternativa para 'Module' o' Block' que no funciona en 'With'? –

+2

@Mr.Asistente En Módulo y Bloque puede hacer al menos Módulo [{a, b, c}, {a, b, c} = {1, 2, 3}; a + b + c] que no funciona en With. – halirutan

7

El problema es mantener el primer argumento de Set no evaluado. Aquí está mi sugerencia (abierto a mejoras, por supuesto):

SetAttributes[myWith, HoldAll]; 
    myWith[{s : Set[a_List, b_List]}, body_] := 
    [email protected] 
     Hold[With][ 
     Table[Hold[Set][Extract[Hold[s], {1, 1, i}, Hold], 
     Extract[Hold[s], {1, 2, i}]], {i, [email protected]}], [email protected]] 
    x1 = 12; 
    Remove[f]; 
    f[x_] := myWith[{{x1, x2} = {a, b}}, x + x1 + x2] 
    f[z] 

resultados en

a+b+z 

Inspirado por halirutan continuación creo que su solución, hecha un poco más segura, es equivalente a la anterior:

SetAttributes[myWith, HoldAll]; 
myWith[{Set[a : {__Symbol}, b_List]} /; Length[a] == Length[b], 
    body_] := 
[email protected] 
    Hold[With][ 
    Replace[Thread[Hold[a, b]], Hold[x_, y_] :> Hold[Set[x, y]], 1], 
    [email protected]] 
+0

¡Agradable! Sostener 'With' parece ser la clave. A continuación se usa el mismo truco: 'f [x_] = ReleaseHold @ Mantener [Con] [ Flatten @ {({x1, x2} -> {a, b} // Thread) /. (Regla [q_, r_] -> HoldForm @ Set [q, r])}, x + x1 + x2] 'Usando' x1 = 5; f [z] 'da' a + b + z'. – kglr

+0

Si reemplaza el primer argumento '{s: Set [a_List, b_List]' con 's: (Set | Rule) [a_List, b_List]' sin cambiar nada más, entonces uno puede usar ambos como 'g [x_] : = myWith [{{x1, x2) -> {a, b}}, x + x1 + x2] 'usando reglas y como' h [x _]: = myWith [{{x1, x2) = {a, b }}, x + x1 + x2] 'usando la asignación para la inicialización de variables locales. – kglr

+0

¡Buenas cosas, Rolf! Lo siento, solo volví ahora para comentar y votar. +1. –

3

Se podría utilizar para acortar transposición solución Rolfs en 100 caracteres:

SetAttributes[myWith, HoldAll]; 
myWith[{Set[a_List, b_List]}, body_] := 
ReleaseHold[Hold[With][Hold[Set[#1, #2]] & @@@ Transpose[{a, b}], 
    [email protected] 
    ]] 

@Heike, sí, lo anterior se rompe si cualquiera de las variables ya tiene un valor. ¿Qué pasa con esto:

SetAttributes[myWith, HoldAll]; 
myWith[{Set[a_List, b_List]}, body_] := 
[email protected] 
    Hold[With][Thread[Hold[a, b]] /. Hold[p__] :> Hold[Set[p]], 
    [email protected]] 
+1

Esto se rompe cuando una de las variables en 'a' se ha establecido anteriormente, p. 'a = 3; myWith [{{a, b, c} = {1, 2, 3}}, a^2 + b] 'da un error, pero esto se soluciona fácilmente poniendo 'Block [{a}, ...]' alrededor del lado derecho de 'myWith'. – Heike

+0

Lo vi después de publicar la respuesta. – halirutan

Cuestiones relacionadas