Gehen Sie Beispiele und Redewendungen [geschlossen]


91

Es gibt nicht viel Go-Code, aus dem man die Sprache lernen kann, und ich bin sicher, dass ich nicht der einzige bin, der damit experimentiert. Wenn Sie also etwas Interessantes über die Sprache herausgefunden haben, posten Sie bitte hier ein Beispiel.

Ich suche auch

  • idiomatische Wege, um Dinge in Go zu tun,
  • C / C ++ - Denkstil "portiert" nach Go,
  • häufige Fallstricke in Bezug auf die Syntax,
  • wirklich alles interessante.

ARM-Unterstützung wie 8-Bit oder 16-Bit. D Sprache noch nicht.

1
Die Bibliothek ( golang.org/pkg ) ist eine hervorragende Quelle, um zu erfahren, wie go verwendet wird. Persönlich finde ich, dass das Lernen, wie Datenstrukturen implementiert werden, hilfreich ist, um die Sprache zu lernen.
Tkokasih

Antworten:


35

Anweisungen verschieben

Eine "defer" -Anweisung ruft eine Funktion auf, deren Ausführung auf den Moment verschoben wird, in dem die umgebende Funktion zurückkehrt.

DeferStmt = Ausdruck "verschieben".

Der Ausdruck muss ein Funktions- oder Methodenaufruf sein. Bei jeder Ausführung der Anweisung "defer" werden die Parameter des Funktionsaufrufs ausgewertet und neu gespeichert, die Funktion wird jedoch nicht aufgerufen. Aufgeschobene Funktionsaufrufe werden in LIFO-Reihenfolge unmittelbar vor der Rückkehr der umgebenden Funktion ausgeführt, jedoch nachdem die Rückgabewerte, falls vorhanden, ausgewertet wurden.


lock(l);
defer unlock(l);  // unlocking happens before surrounding function returns

// prints 3 2 1 0 before surrounding function returns
for i := 0; i <= 3; i++ {
    defer fmt.Print(i);
}

Aktualisieren:

deferist jetzt der idiomatische Weg zu handhaben auch panicin einer Ausnahme wie Art und Weise:

package main

import "fmt"

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i+1)
}

17
Sieht aus wie guter alter RAII (explizit gemacht).
Konrad Rudolph

4
+1 da ich viel über Go gelesen habe, aber ich habe das immer noch nicht gesehen (bis du es mir gezeigt hast)!
u0b34a0f6ae

Clever, obwohl es für mich sinnvoller wäre, wenn aufgeschobene Anweisungen in FIFO-Reihenfolge (von oben nach unten) ausgeführt würden, aber vielleicht
bin

Cool. Erinnert mich an Scrop Guards von D digitalmars.com/d/2.0/exception-safe.html
hasen

4
@ Mike: Wenn Sie mit Blöcken von "try: .. finally:" vergleichen, nistet LIFO auf die gleiche Weise. Bei Paaren zum Öffnen / Schließen von Ressourcen usw. ist eine solche Verschachtelung das einzige, was Sinn macht (Erstes Öffnen wird zuletzt schließen).
u0b34a0f6ae

25

Go-Objektdateien enthalten tatsächlich einen Klartext-Header:

jurily@jurily ~/workspace/go/euler31 $ 6g euler31.go
jurily@jurily ~/workspace/go/euler31 $ cat euler31.6
amd64
  exports automatically generated from
  euler31.go in package "main"
    import

$$  // exports
  package main
    var main.coin [9]int
    func main.howmany (amount int, max int) (? int)
    func main.main ()
    var main.initdone· uint8
    func main.init ()

$$  // local types
  type main.dsigddd_1·1 struct { ? int }

$$

!
<binary segment>

6
Das ist eher ein verstecktes Feature als ein idiomatisches Beispiel
hasen

22

Ich habe ein paar Leute gesehen, die sich über die for-Schleife beschwert haben, nach dem Motto "Warum sollten wir heutzutage etwas sagen müssen i = 0; i < len; i++?".

Ich bin anderer Meinung, ich mag das für Konstrukt. Sie können die lange Version verwenden, wenn Sie möchten, aber das idiomatische Go ist

var a = []int{1, 2, 3}
for i, v := range a {
    fmt.Println(i, v)
}

Das for .. rangeKonstrukt durchläuft alle Elemente und liefert zwei Werte - den Index iund den Wertv .

range funktioniert auch auf Karten und Kanälen.

Dennoch, wenn Sie nicht mögen forin jeder Form, können Sie festlegen each, mapusw. in ein paar Zeilen:

type IntArr []int

// 'each' takes a function argument.
// The function must accept two ints, the index and value,
// and will be called on each element in turn.
func (a IntArr) each(fn func(index, value int)) {
    for i, v := range a {
        fn(i, v)
    }
}

func main() {
    var a = IntArr([]int{2, 0, 0, 9}) // create int slice and cast to IntArr
    var fnPrint = func(i, v int) {
        fmt.Println(i, ":", v)
    } // create a function

    a.each(fnPrint) // call on each element
}

druckt

0 : 2
1 : 0
2 : 0
3 : 9

Ich fange an, Go sehr zu mögen :)


Obwohl rangees nur schön ist, wenn es mit dem gleichen Code wie die for-3-Schleife kompiliert wird.
Thomas Ahle

19

Holen Sie sich Ihren Stackoverflow-Ruf

Dies ist eine Übersetzung dieser Antwort .

package main

import (
    "json"
    "fmt"
    "http"
    "os"
    "strings"
)

func die(message string) {
    fmt.Printf("%s.\n", message);
    os.Exit(1);
}

func main() {
    kinopiko_flair := "https://stackoverflow.com/users/flair/181548.json"
    response, _, err := http.Get(kinopiko_flair)
    if err != nil {
        die(fmt.Sprintf("Error getting %s", kinopiko_flair))
    }

    var nr int
    const buf_size = 0x1000
    buf := make([]byte, buf_size)

    nr, err = response.Body.Read(buf)
    if err != nil && error != os.EOF {
        die(fmt.Sprintf("Error reading response: %s", err.String()))
    }
    if nr >= buf_size { die ("Buffer overrun") }
    response.Body.Close()

    json_text := strings.Split(string(buf), "\000", 2)
    parsed, ok, errtok := json.StringToJson(json_text[0])
    if ! ok {
        die(fmt.Sprintf("Error parsing JSON %s at %s", json_text, errtok))
    }

    fmt.Printf("Your stackoverflow.com reputation is %s\n", parsed.Get ("reputation"))
}

Vielen Dank an Scott Wales für die Hilfe bei .Read ().

Das sieht mit den zwei Saiten und zwei Puffern immer noch ziemlich klobig aus. Wenn also Go-Experten Ratschläge haben, lassen Sie es mich wissen.


Ich bin mir nicht sicher, was mit der Formatierung falsch sein sollte. Ich habe es wiederhergestellt.

5
Die Go-Autoren empfehlen zu gofmtIhrem Code :-)
ℝaphink

Ich kann es nicht kompilieren: $ ../go/src/cmd/6g/6g SO.go SO.go: 34: undefined: json.StringToJson
ℝaphink

@Raphink: Die Sprache hat sich geändert, seit ich das gemacht habe.

Ja, wissen Sie vielleicht, was dem StringToJson am nächsten kommt? Früher wurde ein Builder intern eingerichtet, jetzt muss man seinen eigenen mit einer vordefinierten nativen Struktur versehen?
Macbirdie

19

Hier ist ein schönes Beispiel für iota aus Kinopikos Beitrag :

type ByteSize float64
const (
    _ = iota;   // ignore first value by assigning to blank identifier
    KB ByteSize = 1<<(10*iota)
    MB
    GB
    TB
    PB
    YB
)

// This implicitly repeats to fill in all the values (!)

5
Beachten Sie, dass die Semikolons nicht erforderlich sind.
mk12

18

Sie können Variablen durch parallele Zuordnung austauschen:

x, y = y, x

// or in an array
a[j], a[i] = a[i], a[j]

einfach aber effektiv.


18

Hier ist eine Redewendung von der Seite " Effective Go "

switch {
case '0' <= c && c <= '9':
    return c - '0'
case 'a' <= c && c <= 'f':
    return c - 'a' + 10
case 'A' <= c && c <= 'F':
    return c - 'A' + 10
}
return 0

Die switch-Anweisung schaltet true ein, wenn kein Ausdruck angegeben wird. Das ist also gleichbedeutend mit

if '0' <= c && c <= '9' {
    return c - '0'
} else if 'a' <= c && c <= 'f' {
    return c - 'a' + 10
} else if 'A' <= c && c <= 'F' {
    return c - 'A' + 10
}
return 0

Im Moment sieht die Switch-Version für mich etwas sauberer aus.


6
Whoa, komplett aus VB herausgerissen. ;-) ( Switch True…)
Konrad Rudolph

@Konrad, schlag mich drauf! :) Ich habe dieses Idiom bereits in VB6-Code verwendet und es kann definitiv die Lesbarkeit in bestimmten Situationen verbessern.
Mike Spross

Was ist '<='? Hat es etwas mit '<-' zu tun?
inkaphink

@Raphink: weniger als oder gleich.
Paul Ruane

17

Typschalter :

switch i := x.(type) {
case nil:
    printString("x is nil");
case int:
    printInt(i);  // i is an int
case float:
    printFloat(i);  // i is a float
case func(int) float:
    printFunction(i);  // i is a function
case bool, string:
    printString("type is bool or string");  // i is an interface{}
default:
    printString("don't know the type");
}


14

Benannte Ergebnisparameter

Die Rückgabe- oder Ergebnis- "Parameter" einer Go-Funktion können wie die eingehenden Parameter benannt und als reguläre Variablen verwendet werden. Wenn sie benannt werden, werden sie zu Beginn der Funktion auf die Nullwerte für ihre Typen initialisiert. Wenn die Funktion eine return-Anweisung ohne Argumente ausführt, werden die aktuellen Werte der Ergebnisparameter als zurückgegebene Werte verwendet.

Die Namen sind nicht obligatorisch, können aber den Code kürzer und klarer machen: Sie sind Dokumentation. Wenn wir die Ergebnisse von nextInt benennen, wird klar, welches zurückgegebene int welches ist.

func nextInt(b []byte, pos int) (value, nextPos int) {

Da benannte Ergebnisse initialisiert und an eine schmucklose Rückgabe gebunden sind, können sie sowohl vereinfachen als auch verdeutlichen. Hier ist eine Version von io.ReadFull, die sie gut nutzt:

func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
    for len(buf) > 0 && err == nil {
        var nr int;
        nr, err = r.Read(buf);
        n += nr;
        buf = buf[nr:len(buf)];
    }
    return;
}

1
Ich bin neugierig - hat eine andere Sprache das?
u0b34a0f6ae

1
Matlab hat etwas ähnliches.
Dan Lorenc

pascal verwendet eine ähnliche Syntax für die Rückgabe eines Werts.
nes1983

1
@ nes1983 Für diejenigen, die es nicht wissen, weisen Sie in Pascal den Funktionswert klassisch dem Funktionsnamen zu.
Fuz

FORTRAN hat das so ziemlich.
Hut8

14

Aus James Antills Antwort :

foo := <-ch     // This blocks.
foo, ok := <-ch // This returns immediately.

Auch eine mögliche Gefahr: der subtile Unterschied zwischen den Empfangs- und Sendeoperatoren:

a <- ch // sends ch to channel a
<-ch    // reads from channel ch

3
Der Empfangsoperator selbst ist ab Go 1.0.3 jetzt eine Sperroperation. Die Spezifikation wurde geändert: golang.org/ref/spec#Receive_operator . Bitte versuchen Sie das Blockierungsverhalten (Deadlock) hier: play.golang.org/p/0yurtWW4Q3
Deleplace

13
/* 
 * How many different ways can £2 be made using any number of coins?
 * Now with 100% less semicolons!
 */

package main
import "fmt"


/* This line took me over 10 minutes to figure out.
 *  "[...]" means "figure out the size yourself"
 * If you only specify "[]", it will try to create a slice, which is a reference to an existing array.
 * Also, ":=" doesn't work here.
 */
var coin = [...]int{0, 1, 2, 5, 10, 20, 50, 100, 200}

func howmany(amount int, max int) int {
    if amount == 0 { return 1 }
    if amount < 0 { return 0 }
    if max <= 0 && amount >= 1 { return 0 }

    // recursion works as expected
    return howmany(amount, max-1) + howmany(amount-coin[max], max)
}


func main() {
    fmt.Println(howmany(200, len(coin)-1))
}

4
Ich würde vorschlagen, den Namen der Problemlösungsseite sowie die ID-Nummer zu entfernen. Vielleicht die Frage umformulieren. Um das Problem nicht jemandem zu verderben, der darüber stolpert. Oder versuchen Sie zu betrügen, indem Sie im Internet nach dem Problem suchen.
Mizipzor

1
Für die Aufzeichnung: Dies ist der Algorithmus von algorithmist.com/index.php/Coin_Change. Es ist das erste Google-Ergebnis für "Münzwechsel".
György Andrasek

13

Ich finde es gut, dass Sie Typen, einschließlich Grundelemente wie int, beliebig oft neu definieren und verschiedene Methoden anhängen können. Wie beim Definieren eines RomanNumeral-Typs:

package main

import (
    "fmt"
    "strings"
)

var numText = "zero one two three four five six seven eight nine ten"
var numRoman = "- I II III IV V VI VII IX X"
var aText = strings.Split(numText, " ")
var aRoman = strings.Split(numRoman, " ")

type TextNumber int
type RomanNumber int

func (n TextNumber) String() string {
    return aText[n]
}

func (n RomanNumber) String() string {
    return aRoman[n]
}

func main() {
    var i = 5
    fmt.Println("Number: ", i, TextNumber(i), RomanNumber(i))
}

Welches druckt aus

Number:  5 five V

Der RomanNumber()Aufruf ist im Wesentlichen eine Umwandlung, er definiert den int-Typ als einen spezifischeren Typ von int neu. Und Println()ruft String()hinter die Kulissen.


12

Kanal zurückgeben

Dies ist eine wahre Redewendung, die sehr wichtig ist: wie man Daten in einen Kanal einspeist und ihn danach schließt. Mit diesem können Sie einfache Iteratoren (da der Bereich einen Kanal akzeptiert) oder Filter erstellen.

// return a channel that doubles the values in the input channel
func DoublingIterator(input chan int) chan int {
    outch := make(chan int);
    // start a goroutine to feed the channel (asynchronously)
    go func() {
        for x := range input {
            outch <- 2*x;    
        }
        // close the channel we created and control
        close(outch);
    }();
    return outch;
}

+1. Sie können auch Kanäle durch Kanäle leiten.
György Andrasek

5
Achten Sie jedoch darauf, dass Sie nicht aus einer for x: = range chan {} -Schleife ausbrechen, da sonst die Goroutine und der gesamte Speicher, auf den sie verweist, verloren gehen.
Jeff Allen

3
@ JeffAllen wie wäre es defer close(outch);als erste Aussage der Goroutine?

1
Aufschieben stellt eine Anweisung zur Ausführung in die Warteschlange, wenn die Funktion zurückgegeben wird, unabhängig davon, welcher Rückgabepunkt verwendet wird. Wenn der Kanaleingang jedoch niemals geschlossen wird, verlässt die anonyme Funktion in diesem Beispiel niemals die for-Schleife.
Jeff Allen

11

Zeitüberschreitung für Kanallesungen:

ticker := time.NewTicker(ns);
select {
    case v := <- chan_target:
        do_something_with_v;
    case <- ticker.C:
        handle_timeout;
}

Von Davies Liu gestohlen .


11
for {
    v := <-ch
    if closed(ch) {
        break
    }
    fmt.Println(v)
}

Da der Bereich automatisch nach einem geschlossenen Kanal sucht, können wir Folgendes verkürzen:

for v := range ch {
    fmt.Println(v)
}

9

Es gibt ein Make-System, das Sie in $ GOROOT / src verwenden können

Richten Sie Ihr Makefile mit ein

TARG=foobar           # Name of package to compile
GOFILES=foo.go bar.go # Go sources
CGOFILES=bang.cgo     # Sources to run cgo on
OFILES=a_c_file.$O    # Sources compiled with $Oc
                      # $O is the arch number (6 for x86_64)

include $(GOROOT)/src/Make.$(GOARCH)
include $(GOROOT)/src/Make.pkg

Sie können dann die automatisierten Testtools verwenden, indem Sie make test ausführen, oder das Paket und die freigegebenen Objekte von cgo mit make install zu Ihrem $ GOROOT hinzufügen.


7

Eine andere interessante Sache in Go ist das godoc. Sie können es als Webserver auf Ihrem Computer ausführen

godoc -http=:8080

Dabei ist 8080 die Portnummer, und die gesamte Website unter golang.org ist dann unter verfügbar localhost:8080.


Ist das ein reguläres Programm oder ein Daemon?
György Andrasek

Es ist ein reguläres Programm.
Jeremy

7

Dies ist eine Implementierung eines Stapels. Es zeigt das Hinzufügen von Methoden zu einem Typ.

Ich wollte den Stack-Teil davon in ein Slice verwandeln und die Eigenschaften des Slice verwenden, aber obwohl ich das ohne das typezum Laufen gebracht habe, konnte ich die Syntax zum Definieren eines Slice mit einem nicht sehen type.

package main

import "fmt"
import "os"

const stack_max = 100

type Stack2 struct {
    stack [stack_max]string
    size  int
}

func (s *Stack2) push(pushed_string string) {
    n := s.size
    if n >= stack_max-1 {
        fmt.Print("Oh noes\n")
        os.Exit(1)
    }
    s.size++
    s.stack[n] = pushed_string
}

func (s *Stack2) pop() string {
    n := s.size
    if n == 0 {
        fmt.Print("Underflow\n")
        os.Exit(1)
    }
    top := s.stack[n-1]
    s.size--
    return top
}

func (s *Stack2) print_all() {
    n := s.size
    fmt.Printf("Stack size is %d\n", n)
    for i := 0; i < n; i++ {
        fmt.Printf("%d:\t%s\n", i, s.stack[i])
    }
}

func main() {
    stack := new(Stack2)
    stack.print_all()
    stack.push("boo")
    stack.print_all()
    popped := stack.pop()
    fmt.Printf("Stack top is %s\n", popped)
    stack.print_all()
    stack.push("moo")
    stack.push("zoo")
    stack.print_all()
    popped2 := stack.pop()
    fmt.Printf("Stack top is %s\n", popped2)
    stack.print_all()
}

10
Anstatt zu verwenden fmt.Printf(...); os.Exit();, können Sie verwenden panic(...).
Notnoop

1
Das gibt eine Stapelverfolgung, die ich nicht will.

3
Warum ist es begrenzt? Go ist eine verwaltete Sprache. Ihr Stapel kann so tief sein, wie Sie möchten. Verwenden Sie das neue integrierte append (), das bei Bedarf so etwas wie Cs Realloc ausführt.
Jeff Allen

"Go braucht keine Generika", sagten sie.
cubuspl42

4

C-Code von unterwegs aufrufen

Mit der c-Laufzeit ist es möglich, auf die untere Ebene von go zuzugreifen.

C-Funktionen sind in der Form

void package·function(...)

(Beachten Sie, dass der Punkttrenner ein Unicode-Zeichen ist.) Dabei können die Argumente grundlegende Go-Typen, Slices, Strings usw. sein. Um einen Wertaufruf zurückzugeben

FLUSH(&ret)

(Sie können mehr als einen Wert zurückgeben)

Zum Beispiel, um eine Funktion zu erstellen

package foo
bar( a int32, b string )(c float32 ){
    c = 1.3 + float32(a - int32(len(b))
}

in C verwenden Sie

#include "runtime.h"
void foo·bar(int32 a, String b, float32 c){
    c = 1.3 + a - b.len;
    FLUSH(&c);
}

Beachten Sie, dass Sie die Funktion weiterhin in einer go-Datei deklarieren sollten und dass Sie sich selbst um den Speicher kümmern müssen. Ich bin mir nicht sicher, ob es möglich ist, externe Bibliotheken damit aufzurufen. Es ist möglicherweise besser, cgo zu verwenden.

In $ GOROOT / src / pkg / runtime finden Sie Beispiele für die Laufzeit.

Siehe auch diese Antwort zum Verknüpfen von C ++ - Code mit go.


3
Verwendet es wirklich den "fliegenden Punkt"? Ich wage es nicht zu bearbeiten, aber das scheint ein bisschen unerwartet und radikal.
Entspannen Sie

Ja, Sie müssen mit 6c (oder 8c usw.) kompilieren. Ich glaube nicht, dass gcc Unicode-IDs verarbeitet.
Scott Wales

1
Ich denke, AltGr + -Periodentypen sind gleich · aber mit Unicode bin ich mir nicht sicher. War sehr überrascht zu sehen, dass ich in der Quelle gelesen habe .. warum nicht so etwas wie :: verwenden?
u0b34a0f6ae

Das Zeichen ist MIDDLE DOT U + 00B7. Der Parser wurde möglicherweise so verfälscht, dass er dies als Zeichen ansieht, um eine gültige c-Kennung zu erstellen, von der ich glaube, dass sie :: ausschließen würde.
Scott Wales

4
Das '·' ist nur ein vorübergehender Hack, Rob war sogar überrascht, dass es noch da war, er sagte, es würde durch etwas weniger Eigenartiges ersetzt werden.
Uriel


3

Hast du diesen Vortrag gesehen ? Es zeigt eine Menge cooler Sachen, die du machen kannst (Ende des Gesprächs)


2
Ja, habe ich. Es läuft darauf hinaus: "Da ist noch viel mehr drin, lasst uns zum nächsten Thema übergehen."
György Andrasek

Ja, anscheinend viel zu sagen mit wenig Zeit

3

Ein Stapel, der auf der anderen Antwort basiert, aber ein Slice verwendet, an das keine Größenbeschränkung angehängt ist.

package main

import "fmt"
import "os"

type Stack2 struct {
        // initial storage space for the stack
        stack [10]string
        cur   []string
}

func (s *Stack2) push(pushed_string string) {
        s.cur = append(s.cur, pushed_string)
}

func (s *Stack2) pop() (popped string) {
        if len(s.cur) == 0 {
                fmt.Print("Underflow\n")
                os.Exit(1)
        }
        popped = s.cur[len(s.cur)-1]
        s.cur = s.cur[0 : len(s.cur)-1]
        return
}

func (s *Stack2) print_all() {
        fmt.Printf("Stack size is %d\n", len(s.cur))
        for i, s := range s.cur {
                fmt.Printf("%d:\t%s\n", i, s)
        }
}

func NewStack() (stack *Stack2) {
        stack = new(Stack2)
        // init the slice to an empty slice of the underlying storage
        stack.cur = stack.stack[0:0]
        return
}

func main() {
        stack := NewStack()
        stack.print_all()
        stack.push("boo")
        stack.print_all()
        popped := stack.pop()
        fmt.Printf("Stack top is %s\n", popped)
        stack.print_all()
        stack.push("moo")
        stack.push("zoo")
        stack.print_all()
        popped2 := stack.pop()
        fmt.Printf("Stack top is %s\n", popped2)
        stack.print_all()
}

3
const ever = true

for ever {
    // infinite loop
}

25
Hm. for { /* infinite loop */ }reicht.
u0b34a0f6ae

2
Natürlich. Genau das passiert hier. Ich mag nur das foreverSchlüsselwort. Sogar Qt hat ein Makro dafür.
György Andrasek

6
Aber Go braucht dazu kein Makro oder einen niedlichen Alias ​​von true.
u0b34a0f6ae

@ kaizer.se: Jurily meint, dass for ever(nach dem Deklarieren der Variablen) etwas Süßes ist, das Sie in Go tun können, wenn Sie möchten. Es sieht aus wie Englisch (Modulo the Blank).
Frank

8
es ist etwas süßes, das du auch in C machen kannst .. :-)#define ever (;;)
u0b34a0f6ae

2

Es gibt viele kleine Programme im testHauptverzeichnis. Beispiele:

  • peano.go druckt Fakultäten.
  • hilbert.go hat eine Matrixmultiplikation.
  • iota.go hat Beispiele für die seltsame Iota-Sache.
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.