Warum weist der Zuweisungsoperator der linken Seite zu?


47

Ich habe vor kurzem angefangen, einer Freundin Programmieren beizubringen (wir verwenden Python), und als wir angefangen haben, die Erstellung von Variablen und den Zuweisungsoperator zu besprechen, fragte sie, warum der Wert auf der rechten Seite dem Namen auf der linken Seite zugewiesen wird und nicht umgekehrt .

Ich hatte vorher nicht zu viel darüber nachgedacht, weil es mir natürlich erschien, aber sie sagte, dass von links nach rechts für sie natürlicher erschien, da die meisten von uns auf diese Weise natürliche Sprachen lesen.

Ich habe darüber nachgedacht und bin zu dem Schluss gekommen, dass der Code dadurch viel einfacher zu lesen ist, da die zugewiesenen Namen (die der Programmierer wiederverwenden muss) leicht sichtbar sind und links ausgerichtet sind.

aligned = 2
on = 'foo' + 'bar' + 'foobar'
the = 5.0 / 2
left = 2 + 5

Im Gegensatz zu:

2 = aligned 
'foo' + 'bar' + 'foobar' = on
5.0 / 2 = the 
2 + 5 = right 

# What were the names again...?

Nun frage ich mich, ob es auch andere Gründe für diesen Standard gibt. Gibt es eine Geschichte dahinter? Oder gibt es einen technischen Grund, warum dies eine gute Option ist (ich weiß nicht viel über Compiler)? Und gibt es Programmiersprachen, die der richtigen Seite zugeordnet sind?


15
R kann der rechten Seite zugewiesen werden ( value -> variable).
Sie

Vielleicht liegt das an falschen Variablennamen? Wie wäre es mit "numAligned is 2" und "chickensLeft is 2 + 5"?
Job

18
Heißt dein Freund ... Yoda?
Adrian

1
Wenn sie darüber nachdenkt, frage ich mich, was sie tun wird, wenn sie C ++ & Co. sieht.
BlackBear

3
Nur eine Randnotiz: Khan Academy hat einige Lektionen zu Python für Anfänger-Programmierer: khanacademy.org/#computer-science und Google hat einige für Fortgeschrittene: code.google.com/edu/languages/google-python-class/set- up.html
Lindon Fox

Antworten:


56

Das Gleiche gilt für paxdiablo. Die frühen Programmiersprachen wurden von Mathematikern geschrieben - eigentlich alle. In der Mathematik macht es nach ihrem eigenen Prinzip - von links nach rechts zu lesen - Sinn, wie es funktioniert.

x = 2y - 4.

In der Mathematik würde man folgendes sagen: Sei x gleich 2y -4.

Auch in der Algebra tun Sie dies. Wenn Sie eine Gleichung für eine Variable lösen, isolieren Sie die zu lösende Variable auf der linken Seite. dh y = mx + b;

Wenn eine ganze Familie von Sprachen - wie beispielsweise die C-Familie - eine bestimmte Syntax hat, ist die Änderung zudem kostenintensiver.


19
@ FarmBoy: In der Mathematik sind Zuweisung und Gleichheit dasselbe, da es in einer Formel keine Sequenz gibt wie in Computern. (a gleich b und gleichzeitig b gleich a)
Petruza

1
@FB außer in einigen funktionalen Sprachen mit Einzelzuweisung wie zB Erlang. Zuordnung und Gewährleistung der Gleichheit sind die gleichen wie in der Mathematik
Peer Stritzinger

18
@Petruza Nein, in der Mathematik sind Aufgabe und Gleichheit nicht dasselbe. Wenn ich 'Lass x = 2y - 3' sage, ist es anders als 'Also x = 2y - 3'. Ich denke, normalerweise unterscheidet sie der Kontext. Da der Kommentar, der mich bestreitet, so allgemein anerkannt war, erwähne ich, dass ich einen Doktortitel habe. in Mathematik bin ich mir da ziemlich sicher.
Eric Wilson

2
Ich kenne Mathematik nicht in der Nähe eines Doktortitels. Ich stelle fest, dass es in der Mathematik keine Reihenfolge der Ausführung gibt, sowohl in einer Aufgabe als auch in einer Gleichheit, anders als in der Programmierung, in der beide Seiten einer Aufgabe sein können zu irgendeinem Zeitpunkt unterschiedlich, und sie enden zu irgendeinem anderen Zeitpunkt gleich. Aber in der Mathematik sind in einer Aufgabe wie der, let a be...in der es keine Zeit gibt, beide Seiten der Aufgabe gleich, so dass die Aufgabe in der Tat eine Gleichheit ist, kein Wunder, warum beide dasselbe Zeichen verwenden:=
Petruza

5
@Petruzza - aber es gibt Sequentialität. Mathematische Dokumente werden wie jedes andere Dokument von Anfang bis Ende geschrieben. Wenn ich x = 1in Kapitel eins, aber x = 2in Kapitel zwei behaupte , ist das kein schrecklicher Widerspruch - jede Behauptung gilt nur innerhalb eines bestimmten Kontexts. Der Unterschied bei der imperativen Programmierung besteht zum einen in der Beseitigung einer Barriere (wir brauchen keinen Kontextwechsel), zum anderen in der Implementierung und dem Nutzen.
Steve314

26

BASIC, eine der frühesten Computersprachen hatte die "richtige" Form von:

10 LET AREA = HEIGHT * WIDTH

Dies entspricht der mathematischen Denkweise, eine Variable wie "Sei H die Höhe des Objekts" anzugeben.

COBOLwar auch ähnlich mit seiner COMPUTEAussage. Wie bei vielen Arten, Dinge zu tun, kann es einfach eine willkürliche Entscheidung gewesen sein, die durch viele Sprachen übertragen wurde.


7
Ich nehme an, dass diese Form natürlicher ist, wenn Programmzeilen als "Anweisungen" und nicht als "Operationen" betrachtet werden. Wie in, I declare that X must equal THISanstelle der linearenEvaluate THIS and store it in X
Voithos

Warten Sie, BASIC war eine "frühe" Computersprache?
Alex Feinman

2
@Alex Wenn man bedenkt, dass es aus den 1960ern stammt, würde ich sagen, dass das ziemlich früh ist.
Ben Richards

1
Andererseits können Sie in COBOL schreiben MULTIPLY HEIGHT BY WIDTH GIVING AREA, sodass sich die Variable, die das Ergebnis erhält, ganz rechts in der Anweisung befindet.
User281377

25

Tatsächlich gibt es eine Programmiersprache, die der rechten Seite zuweist: TI-BASIC ! Darüber hinaus wird "=" nicht als Zuweisungsoperator verwendet, sondern ein Pfeil, der als "STO" -Operator bezeichnet wird.

Beispiele:

5→A
(A + 3)→B
(A - B)→C

Im obigen Beispiel werden drei Variablen deklariert und mit Werten versehen. A wäre 5, B wäre 8 und C wäre -3. Die erste Deklaration / Zuweisung kann als 'Speicher 5 als A' gelesen werden.

Was den Grund betrifft, warum TI-BASIC ein solches System für die Zuweisung verwendet, schreibe ich das so, weil es eine Programmiersprache für einen Taschenrechner ist. Der "STO" -Operator bei TI-Taschenrechnern wurde im normalen Taschenrechnerbetrieb am häufigsten verwendet, nachdem eine Zahl berechnet wurde. Wenn es sich um eine Zahl handelte, an die sich der Benutzer erinnern wollte, drückte er die Taste "STO", und der Taschenrechner forderte sie zur Eingabe eines Namens auf (wobei die Alpha-Sperre automatisch aktiviert wurde, sodass Tastatureingaben Buchstaben anstelle von Zahlen ergaben):

Sin(7 + Cos(3))
                    -.26979276
Ans→{variable name}
                    -.26979276

und der Benutzer kann die Variable benennen, was auch immer er wählt. Wenn Sie die Alpha-Sperre aktivieren müssten, geben Sie den Namen ein und drücken Sie dann "STO". Das Drücken der "Ans" -Taste wäre für den normalen Betrieb viel zu umständlich gewesen. Da alle Taschenrechnerfunktionen in TI-BASIC verfügbar sind, wurden keine weiteren Zuweisungsoperatoren hinzugefügt, da "STO" die gleiche Aufgabe ausführte, wenn auch im Vergleich zu den meisten anderen Sprachen in umgekehrter Reihenfolge.

(Anekdote: TI-BASIC war eine der ersten Sprachen, die ich lernte. Als ich zum ersten Mal im College Java lernte, hatte ich das Gefühl, dass es ungewöhnlich und „rückwärts“ war, der LINKEN zuzuweisen!)


+1 Das habe ich total vergessen! TI Basic war auch meine allererste Sprache, aber ich erinnere mich nicht an dieses Detail.
Barjak

Tatsächlich ist der STO-Bediener näher dran, wie die Maschine funktioniert und wie eine Sprache tatsächlich arbeitet. Der Wert wird zuerst berechnet und dann gespeichert.
Kratz

15

Heuristik 1: Wenn Sie beim Entwerfen einer Sprache mehr als eine Möglichkeit haben, etwas zu tun, wählen Sie die häufigste, intuitivste, sonst erhalten Sie Perl +.

Nun, wie ist es natürlicher (zumindest zu einem englischen Sprecher)? Schauen wir uns an, wie wir Dinge auf Englisch schreiben / sagen:

Steven ist jetzt 10 Jahre alt (im Gegensatz zu Steven, der jetzt 10 Jahre alt ist). Ich wiege mehr als 190 Pfund (im Gegensatz zu mehr als 190 Pfund, die ich wiege).

In Code:

steven = 10
i > 190

Folgendes klingt auch natürlicher:

"Wenn Mary 18 Jahre alt ist, kann sie eine Süßigkeit haben". "Wenn ich jünger als 21 Jahre bin, werde ich meinen Bruder bitten, mir Tequila zu schicken."

if (mary == 18) { ... }
if (i < 21) { ... }

als:

"Wenn Maria 18 Jahre alt ist ..." "Wenn 21 Jahre älter sind als ich ..."

Nun der Code:

if (18 == mary) { ... }
if (21 > i) { ... }

Beachten Sie, dass dies weder für Programmierer noch für englischsprachige Benutzer selbstverständlich ist. Die Sätze klingen wie Yoda-Sprechen, und der Code trägt den Spitznamen Yoda-Bedingungen. Dies mag in C ++ hilfreich sein, aber ich bin mir sicher, dass die meisten Leute dem zustimmen würden: Wenn ein Compiler das schwere Heben übernehmen und die Notwendigkeit von Yoda-Bedingungen verringern könnte, wäre das Leben ein bisschen einfacher.

Natürlich kann man sich an alles gewöhnen. Zum Beispiel lautet Nummer 81 wie folgt:

Eighty One (englisch) Eighty and one (spanisch) One and Eighty (deutsch).

Endlich sind es 4! = 24 gültige Ausdrücke für "grüner Apfel liegt auf dem Tisch" auf Russisch - die Reihenfolge spielt (fast) keine Rolle, außer dass 'on' mit 'table' zusammenkommen muss. Wenn Sie also beispielsweise Russisch als Muttersprache sprechen, ist es Ihnen vielleicht egal, ob Sie schreiben a = 10oder 10 = aweil beide gleichermaßen natürlich erscheinen.

Obwohl Linguistik ein faszinierendes Fach ist, habe ich es nie offiziell studiert und kenne nicht so viele Sprachen. Hoffentlich habe ich aber genug Gegenbeispiele geliefert.


4
... und auf Französisch wird 81 als "vier mal einundzwanzig" bezeichnet ... :)
Martin Sojka

1
@ Martin, ich glaube, Dänisch ist sehr ähnlich.
ein Lebenslauf

7
@ Martin Das ist wirklich komisch, denn vier mal einundzwanzig ist 84.
Peter Olson

6
@ Peter: Du hast eine falsche Klammer, es ist(four times twenty) one
SingleNegationElimination

4
@Job: Als ich dein "Wenn Maria 18 Jahre alt ist ..." las, wurde ich unweigerlich daran erinnert, dass Yoda sagte: "Wenn du neunhundert Jahre alt bist, siehst du so gut aus, wie du es nicht wirst, hmm?" im
Gegenzug

11

Es begann mit FORTRAN in den 1950er Jahren. Während FORTRAN eine Abkürzung für FORmula TRANslation war, handelte es sich bei den fraglichen Formeln um einfache algebraische Gleichungen, die nach der Konvention immer links stehen.

Sein zeitnahes COBOL hingegen sollte englischartig sein und (meistens!) Der Rechten zugeordnet werden.

MOVE 1 TO COUNTER.
ADD +1 TO LINE-CNT.
MULTIPLY QTY BY PRICE GIVING ITEM-PRICE.

Ich denke, dies ist das beste Beispiel hier, da es einen Kontext in einer Sprache perfekt darstellt, in der es für einen normalen englischen Sprecher lesbar ist, aber die richtige Zuordnung hat. Ich sage, das ist wichtig, weil viele Leute auf Englisch sagen, es würde nur von links nach rechts gelesen, damit die Zuordnung Sinn ergibt, was absolut nicht wahr ist.
Joshua Hedges

5

Nun, wie @ diceguyd30 hervorhob, gibt es beide Notationen.

  • <Identifier> = <Value>bedeutet " Bezeichner sei Wert ". Oder um dies zu erweitern: Definieren Sie die Variable Identifier to Value (oder definieren Sie sie neu) .
  • <Value> -> <Identifier>bedeutet " Wert zu Identifikator speichern ". Oder um dies zu erweitern: Setzen Sie den Wert an den von Identifier angegebenen Ort .

Natürlich kann der Identifikator im Allgemeinen tatsächlich ein beliebiger L-Wert sein.

Der erste Ansatz berücksichtigt das abstrakte Konzept der Variablen, der zweite Ansatz befasst sich mehr mit der tatsächlichen Speicherung.

Beachten Sie, dass der erste Ansatz auch in Sprachen üblich ist, die keine Zuweisungen haben. Beachten Sie auch, dass variable Definition und Zuordnung relativ nahe <Type> <Identifier> = <Value>vs. <Identifier> = <Value>.


3

Wie bereits erwähnt, funktionierten alle frühen Computersprachen auf diese Weise. ZB FORTRAN, das viele Jahre vor BASIC kam.

Tatsächlich ist es sehr sinnvoll, die zugewiesene Variable links vom Zuweisungsausdruck zu haben. In einigen Sprachen haben Sie möglicherweise mehrere verschiedene überladene Routinen mit SAME NAME, die unterschiedliche Ergebnistypen zurückgeben. Indem der Compiler zuerst den Typ der zugewiesenen Variablen sieht, weiß er, welche überladene Routine aufzurufen ist oder welche implizite Umwandlung beim Konvertieren von (z. B.) einer Ganzzahl in einen Gleitkommawert zu generieren ist. Das ist eine vereinfachende Erklärung, aber Sie werden hoffentlich auf die Idee kommen.


2
Ich verstehe Ihre Erklärung, aber konnte der Compiler nicht einfach bis zum Ende der Anweisung nach vorne schauen?
Voithos

Das mag den Lexer in den heutigen Sprachen einfacher machen, aber wie viele Programmiersprachen haben sogar benannte Methoden unterstützt, geschweige denn Methodenüberladungen, als diese Art von Syntax neu war?
ein Lebenslauf

2
Hallo @voithos. Ja - der Compiler könnte nach vorne schauen, aber das wäre in den Anfängen des Compiler-Schreibens wahrscheinlich ein inakzeptables Maß an Komplexität gewesen - was oft ein handcodierter Assembler war! Ich halte es für pragmatisch, die zugewiesene Variable nach links zu stellen: Es ist für Mensch und Maschine einfacher, sie zu analysieren.
Dave Jewell

Ich denke, es wäre trivial, wenn eine Aufgabe der richtigen zugewiesen würde. Wenn ein Ausdruck wie 3 + 4 == 6 + 7 ist, werden beide Seiten vor dem Operator ausgewertet, da die Sprache rekursiv definiert ist. Das Sprachelement 'Variable = Ausdruck' kann leicht in 'Ausdruck = Variable' geändert werden. Ob dies zu mehrdeutigen Situationen führt oder nicht, hängt vom Rest der Sprache ab.
Kratz

@Kratz - das gilt jetzt sicherlich für Compiler, aber möglicherweise gab es ein kleines Problem bei sehr alten interpretierten Sprachen, die mit tokenisierten Quellen funktionierten. OTOH, das hätte Variable-auf-der-Rechten-vor Variable-auf-der-Linken bevorzugen können.
Steve314

3

Es könnte ein Überbleibsel früher Parsing-Algorithmen sein. Denken Sie daran, dass das LR-Parsing erst 1965 erfunden wurde und es durchaus möglich ist, dass LL-Parser Probleme hatten (im Rahmen der zeitlichen und räumlichen Beschränkungen der Maschinen zu dieser Zeit), das Gegenteil zu erreichen. Erwägen:

identifier = function();
function();

Die beiden sind deutlich vom zweiten Token zu unterscheiden. Auf der anderen Seite,

function() = identifier;
function();

Kein Spaß. Dies wird schlimmer, wenn Sie anfangen, Zuweisungsausdrücke zu verschachteln.

function(prev_identifier = expression) = identifier;
function(prev_identifier = expression);

Eine leichtere Disambiguierung für Maschinen bedeutet natürlich auch eine leichtere Disambiguierung für Menschen. Ein weiteres einfaches Beispiel wäre die Suche nach der Initialisierung eines bestimmten Bezeichners.

identifier1 = expressionOfAnArbitraryLength;
identifier2 = expressionOfAReallyReallyReallyArbitraryLength;
identifier3 = expression;
identifier4 = AlongLineExpressionWithAFunctionCallWithAssignment(
    identifier = expr);

Ganz einfach, schau einfach auf der linken Seite nach. Auf der anderen Seite die rechte Seite

expressionOfAnArbitraryLength = identifier1;
expressionOfAReallyReallyReallyArbitraryLength = identifier2;
expression = identifier3;
AlongLineExpressionWithAFunctionCallWithAssignment(expr = identifier
    ) = identifier4;

Besonders wenn Sie keine grepLochkarten finden können, ist es viel schwieriger, die gewünschte Kennung zu finden.


Genau an den Punkt, an den ich gedacht hatte, ja.
Voithos

2

Assemblersprachen haben das Ziel als Teil des linken Opcodes. Höhere Sprachen folgten in der Regel den Konventionen der Vorgängersprachen.

Wenn Sie sehen =(oder :=für Pascalische Dialekte), können Sie diese als aussprechen is assigned the value, dann ist die Links-Rechts-Natur sinnvoll (da wir in den meisten Sprachen auch von links nach rechts lesen). Da die Programmiersprachen überwiegend von Leuten entwickelt wurden, die von links nach rechts lesen, blieben die Konventionen bestehen.

Es ist eine Art Pfadabhängigkeit . Ich nehme an, wenn die Computerprogrammierung von Leuten erfunden wurde, die Hebräisch oder Arabisch (oder eine andere Sprache von rechts nach links) sprachen, dann würden wir das Ziel vermutlich auf die rechte Seite stellen.


Ja, aber ich vermute, dass der Text in der Redaktion auch rechtsbündig ist ...
voithos

8
Sie können nicht so über Assemblersprachen verallgemeinern. Sie unterscheiden sich darin, wo sich der Zieloperand befindet.
quick_now

2
@quickly_now: richtig; Tatsächlich hatten die meisten primitiven Maschinensprachen (nach heutigen Maßstäben nicht einmal Assembler) kein Ziel, da es normalerweise nur ein oder zwei Universalakkumulatoren gab. Bei den meisten Operationen wurde der Akkumulator als Ziel verwendet, mit Ausnahme der "Speicher" -Opcodes, die nur die Speicheradresse und nicht die Quelle (die der Akkumulator war) angaben. Ich glaube wirklich nicht, dass es einen Einfluss auf die Zuweisungssyntax für ALGOL-ähnliche Sprachen hatte.
Javier

3
@Tangurena - Einige Assemblersprachen haben das Ziel auf der linken Seite. Nicht vom Opcode (das ist der zusammengesetzte Objektcode), sondern links von der Argumentliste für die Anweisungs-Mnemonik. Andere haben jedoch das Ziel auf der rechten Seite. In 68000 Assembler schreiben Sie mov.b #255, d0zum Beispiel, wohin d0das Register zugewiesen werden soll. Ältere Assembler haben nur ein einziges Argument pro Anweisung. Im 6502 LDA #255(Akku laden) könnte man behaupten, dass sich das Aauf der linken Seite befindet, aber es befindet sich auch auf der linken Seite in STA wherever(Akku laden).
Steve314

2
Sogar der Intel 4004 (der ultimative 4-Bit-Vorfahr der 8086-Familie, zwischen dem 8008 und dem 8080) wurde nach der Zuweisung in Hochsprachen entwickelt. Wenn Sie davon ausgehen, dass die 8086-Serie repräsentativ für das ist, was Assembler in den 50er Jahren und früher gemacht haben, bezweifle ich sehr, dass dies zutrifft.
Steve314

2

Die meisten Anweisungen in COBOL lauten von links nach rechts, sodass die beiden Operanden zuerst und das Ziel zuletzt benannt wurden multiply salary by rate giving tax.

Ich werde jedoch nicht vorschlagen, dass Ihr Schüler COBOL bevorzugen könnte, aus Angst, dass ich (zu Recht) dafür gekennzeichnet wäre, dass ich einen so leisen, unhöflichen, geschmacklosen Kommentar mache! :-)


1

sie sagte, dass von links nach rechts ihr natürlicher erscheine, da die meisten von uns auf diese Weise natürliche Sprachen lasen.

Ich halte das für einen Fehler. Einerseits kann man sagen "10 zu x zuweisen" oder "10 zu x verschieben". Auf der anderen Seite können Sie sagen "setze x auf 10" oder "x wird 10".

Mit anderen Worten, abhängig von Ihrer Verbwahl kann die zugewiesene Variable das Subjekt sein oder nicht und kann sich links befinden oder nicht. "Was natürlich ist" hängt also nur ganz von Ihrer gewohnten Wortwahl ab, um die Aufgabe darzustellen.


0

Im Pseudocode steht der Zuweisungsoperator sehr häufig rechts. Zum Beispiel

2*sqrt(x)/(3+y) -> z

In Casio-Rechnern, auch nicht programmierbaren Varianten, wird rechts zusätzlich die Zuordnungsvariable angezeigt

A+2B → C

In Forth ist die Variable auch rechts

expression variable !

In x86 hat die Intel-Syntax das Ziel auf der linken Seite, aber die GAS-Syntax kehrt die Reihenfolge um, was für viele Leute zu Verwirrung führt, insbesondere bei Anweisungen bezüglich der Reihenfolge der Parameter wie Subtraktion oder Vergleiche. Diese Anweisungen sind in 2 verschiedenen Dialekten gleich

mov rax, rbx    ; Intel syntax
movq %rbx, %rax ; GAS syntax

Beide verschieben den Wert in rbx nach rax. Keine andere Assembler-Sprache, die ich kenne, schreibt das Ziel auf der rechten Seite wie GAS.

Einige Plattformen setzen den Ausdruck links und die Variable rechts:

MOVE expression TO variable      COBOL
expression → variable            TI-BASIC, Casio BASIC
expression -> variable           BETA, R
put expression into variable     LiveCode

https://en.wikipedia.org/wiki/Assignment_%28computer_science%29#Notation

Die meisten Sprachen weisen den Wert der linken Seite zu. Einer der Gründe dafür ist die einfache Ausrichtung der Operatoren, das einfachere Lesen und Erkennen der Variablen, da die Positionen der Zuweisungsoperatoren und Variablen in den Zeilen nicht stark variieren und das Lesen einfacher ist msgstr "Variable soll ein Wert sein".

Einige Leute bevorzugen es jedoch, "Wert x nach y verschieben" zu sagen und die Variable rechts zu schreiben.


-1

Ich denke, es folgt einer logischen Denkweise.
Es muss zuerst eine Box (Variable) geben, dann setzen Sie ein Objekt (Wert) hinein.
Sie legen das Objekt nicht in die Luft und stellen dann eine Schachtel darum.


2
Ja, das tust du. In den meisten Sprachen wird die rechte Seite vor der linken ausgewertet.
Javier

3
Es ist eine Art "Fahren auf der rechten Straßenseite". Es scheint logisch, aber nur, weil es so ist, wie Sie es immer getan haben. Alle überlegenen Länder fahren links.
James Anderson

1
@ James Anderson überlegenen Ländern? : o
nawfal

Sie haben Recht, es ist nur logisch für ein von links nach rechts geschriebenes System wie das römische Alphabet, das vermutlich von fast jeder Programmiersprache, wenn nicht allen, verwendet wird.
Petruza
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.