2011-12-04 9 views
7

Il messaggio di utilizzo per Set ci ricorda che è possibile eseguire più assegnazioni su due elenchi, senza dover eseguire alcuna separazione. Per esempio:Errore durante la generazione di variabili localizzate (come costanti)

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

Esegue l'assegnazione e ritorni:

{a, b} 

Thread, comunemente usato per generare liste di regole, può anche essere chiamato in modo esplicito per ottenere lo stesso risultato:

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

Gives:

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

Tuttavia, l'utilizzo di questo approccio per generare costanti localizzate genera un errore. Consideriamo questa funzione esempio banale:

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

Ecco il messaggio di errore:

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 documentazione messaggio di errore (ref/message/With/lvw), dice nella sezione 'Ulteriori informazioni' che, "Questo messaggio viene generato quando il primo elemento in Con non è un elenco di assegnazioni ai simboli. " Data questa spiegazione, capisco la meccanica del perché il mio incarico è fallito. Nondimeno, sono perplesso e mi chiedo se questa sia una restrizione necessaria da parte di WRI o una supervisione di progetto minore che dovrebbe essere segnalata.

Quindi, ecco la mia domanda:

Qualcuno può fare luce su questo comportamento e/o offrire una soluzione? Ho provato a provare a forzare Evaluation, senza fortuna, e non sono sicuro di cos'altro provare.

+1

Rifletto spesso su tali scelte di progettazione e a volte pongo domande del genere. Sfortunatamente è raro che tali cose vengano spiegate. Mi sono opposto al fatto che Con/Block/Module funzionano in modo diverso, e che 'Set' nel primo argomento di questi non sta eseguendo l'operazione' Set', ma è invece la sintassi. –

+0

Dalla mia lettura della documentazione del messaggio di errore (citata parte verso la fine della mia Q), mi sembra che ci sia un semplice pattern matching che guida il comportamento corrente --- cioè, accettando pattern che hanno la Head List, contenente solo elementi che valutano True per qualcosa come MatchQ [Hold [Set [x, 1]], Hold [Set [Symbol_, _]]]. Se così fosse, sembrerebbe possibile, almeno nel mio miope mondo, modificare il pattern matching/testing per consentire il comportamento che cerco, e che la documentazione su Set sembra implicare dovrebbe funzionare (come una forma valida di incarico). – telefunkenvf14

+0

Non sono d'accordo, ma non conosco alcun modo per effettuare quel cambiamento. –

risposta

11

Ciò che richiede è difficile. Questo è un lavoro per macro, come già esposto dagli altri. Esplorerò una possibilità diversa: usare gli stessi simboli ma inserire alcuni wrapper attorno al codice che vuoi scrivere. Il vantaggio di questa tecnica è che il codice viene trasformato "lessicalmente" e in "compile-time", piuttosto che in fase di esecuzione (come nelle altre risposte). In genere è più facile e veloce eseguire il debug.

Così, qui è una funzione che avrebbe trasformato la With con la sintassi proposto:

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] 

Si noti che questo funziona con il codice in attesa. Questo ha il vantaggio che non dobbiamo preoccuparci di una possibile valutazione del codice né all'inizio né al termine di expandWith. Ecco come funziona:

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]] 

Questo non è, tuttavia, molto conveniente da usare.Ecco una funzione comodità per semplificare questo:

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

possiamo usarlo ora come:

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

Così, per far accadere l'espansione nel codice, basta avvolgere ew intorno ad esso. Qui è il vostro caso per la definizione della funzione:

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

Ora controllare e vedere che ciò che otteniamo è una definizione estesa:

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

Il vantaggio di questo approccio è che si può avvolgere ew attorno a un pezzo arbitrariamente grande del tuo codice. Quello che succede è che da esso viene generato il primo codice espanso, come se lo scrivessi tu stesso, e poi quel codice viene eseguito. Per il caso delle definizioni della funzione, come f sopra, possiamo dire che la generazione del codice avviene in "tempo di compilazione", così eviterai qualsiasi sovraccarico di run-time quando userai la funzione in un secondo momento, che potrebbe essere sostanziale se la funzione viene chiamata spesso.

Un altro vantaggio di questo approccio è la sua componibilità: è possibile creare numerose estensioni di sintassi e per ognuna di esse scrivere una funzione simile a ew. Quindi, a condizione che queste funzioni di trasformazione del codice personalizzate non siano in conflitto tra loro, è sufficiente comporle (annidarle) per ottenere un effetto cumulativo. In un certo senso, in questo modo crei un generatore di codice personalizzato che genera codice Mathematica valido da alcune espressioni Mathematica che rappresentano programmi nella tua lingua personalizzata, che puoi creare all'interno di Mathematica usando questi mezzi.

EDIT

Scrivendo expandWith, ho usato l'applicazione regola iterativa per evitare di trattare con il controllo di valutazione, che può essere un pasticcio. Tuttavia, per chi è interessato, ecco una versione che fa un lavoro esplicito con pezzi di codice non valutati.

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] 

Lo trovo molto più complicato del primo però.

+0

Meraviglioso, Leonid! –

+0

Questo si avvicina all'ideale dell'uso di 'With' stesso, ma non modifica la funzionalità di default. Facile +1. –

+0

Pensi che sia possibile combinare una modifica di tipo [With With di [Gayley-Villegas] (http://stackoverflow.com/a/5149656/421225) ? – Simon

6

il tutorial "LocalConstants", dice

la strada con [{x = Pedice [x, 0], ...}, corpo] funziona è quello di prendere il corpo, e sostituire ogni occorrenza di x , ecc. in base a Subscript [x, 0], ecc. Si può pensare a Con come generalizzazione del /. operatore, adatto per l'applicazione al codice Mathematica al posto di altre espressioni.

Facendo riferimento a questa spiegazione sembra ovvio che qualcosa di simile

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

non funzionerà come ci si potrebbe aspettare in con la notazione.

Supponiamo che vogliate davvero violare questo. With[] ha l'attributo HoldAll, quindi tutto ciò che dai come primo parametro non viene valutato. Per realizzare un lavoro di assegnazione dei vettori devi creare

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

dalla notazione vettoriale.Sfortunatamente,

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

non funziona perché l'argomento di Thread non viene mantenuto e l'assegnazione viene valutata prima che Thread possa fare qualsiasi cosa.

correggiamo questo

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

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

cosa sembra essere molto promettente in un primo momento, appena spostato il problema un po 'lontano. Per utilizzare questo in With[] devi sostituire a un certo punto il mySet con il set reale. Esattamente quindi, With[] non vede la lista {a = 1, b = 2, c = 3}, ma, dal momento che è da valutare, il risultato di tutte le assegnazioni

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] 

Ci sembra non essere così facile intorno a questo e c'è una seconda domanda qui: se c'è un modo per aggirare questa restrizione, è veloce come con sarebbe o perdiamo il vantaggio di velocità rispetto al modulo? E se la velocità non è così importante, perché non utilizzare Module o Block in primo luogo?

+0

halirutan, welcome a StackOverflow. –

+0

L'ultimo paragrafo sembra suggerire che questo problema non influisce su 'Module' o' Block', ma lo fa. Oppure stai dicendo che esiste una soluzione alternativa per 'Module' o' Block' che non funziona su 'With'? –

+2

@Mr.Wizard In Module e Block puoi fare almeno Module [{a, b, c}, {a, b, c} = {1, 2, 3}; a + b + c] che non funziona con. – halirutan

7

Il problema delicato è mantenere il primo argomento di Set non valutato. Ecco il mio suggerimento (aperto a miglioramenti naturalmente):

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] 

risultati in

a+b+z 

Ispirato halirutan sotto Credo che la sua soluzione, resa un po 'più in modo sicuro, è equivalente a quanto sopra:

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

Bello! Tenere premuto 'With' sembra essere la cosa fondamentale. Quanto segue utilizza lo stesso trucco: 'f [x_] = ReleaseHold @ Hold [With] [ Flatten @ {({x1, x2} -> {a, b} // Thread) /. (Regola [q_, r_] -> HoldForm @ Set [q, r])}, x + x1 + x2] 'Usando' x1 = 5; f [z] 'dà' a + b + z'. – kglr

+0

Se si sostituisce il primo argomento '{s: Set [a_List, b_List]' con 's: (Imposta | Regola) [a_List, b_List]' senza modificare altro, allora si può usare sia come 'g [x_] : = myWith [{{x1, x2) -> {a, b}}, x + x1 + x2] 'usando le regole e come' h [x _]: = myWith [{{x1, x2) = {a, b }}, x + x1 + x2] 'utilizzando l'assegnazione per l'inizializzazione delle variabili locali. – kglr

+0

Roba buona Rolf! Mi dispiace, sono tornato solo ora per commentare e votare. +1. –

3

Si potrebbe utilizzare Trasposizione per accorciare soluzione Rolfs da 100 caratteri:

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

@Heike, sì le suddette interruzioni se una variabile ha già un valore. Che dire di questo:

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

Si interrompe quando una delle variabili in 'a' è stata impostata in precedenza, ad es. 'a = 3; myWith [{{a, b, c} = {1, 2, 3}}, a^2 + b] 'dà un errore, ma questo è facilmente risolto mettendo'Block [{a}, ...]' sul lato destro di 'myWith'. – Heike

+0

L'ho visto dopo aver postato la risposta. – halirutan

Problemi correlati