logo

JavaScript 개발자를 위한 Golang - 1부(번역)

Golang for JavaScript developers - Part 1

javascript, go, translation


출처

Golang for JavaScript developers - Part 1

시작

만약 당신이 다른 프로그래밍 언어를 배우는 것에 대해 생각하고 있는 Javascript 개발자라면, Golang은 훌륭한 선택이다. 단순하며 많은 모멘텀과 좋은 성능 그리고 JavaScript와 일부 유사하다.

Edit: 어떤 사람이 댓글에서 JS 개발자가 가능한 모든 옵션 중에서 왜 Go를 선택해야 하는지 물었다. 내 생각에, JS는 완벽한 언어가 아니기 때문에 다른 언어를 배우면 JS 개발자가 JS를 더 실용적으로 사용할 수 있게 큰 도움이 될 것이고 기본적인 프로그래밍 개념에 대한 그녀/그의 지식을 강화하는 데도 도움이 될 것이다. 물론 Rust, Go, Haskel, Kotlin 등과 같은 많은 옵션들이 있지만, 나는 Go가 사용 가능한 모든 옵션 중에서 가장 단순하고 널리 채택되어 시작하기에 좋은 것이라고 생각한다. 나의 두번째 선택은 Kotlin이나 Rust가 될 것이다.

이 포스트는 언어를 비교하는 것이 아니고 그들이 매우 유사하다고 말하고 있다. 이것은 JavaScript 개발자가 Golang을 빨리 파악할 수 있는 가이드이다. Go에는 우리가 다룰 자바스크립트와 완전히 다른 많은 측면들이 있다.

더 유사한 것들

Go에는 JavaScript의 개념과 매우 유사한 것들이 많이 있다. 대부분은 같지 않으나 비슷하다. 먼저 우리는 그것들을 알아보는 것에 힘쓰자. 이 시리즈의 1부에선, 우리는 그것들이 어떻게 비슷한 지 살펴보고 주요 차이점도 주목할 것이다.

Functions

JS와 Go에서 가장 유사한 특징은 functions이다.

유사점

  • Functions는 일급 객체이다.
  • Functions는 변수에 할당할 수 있다.
  • Functions는 인수로 다른 functions을 전달할 수 있고, functions으로 반환할 수 있다.
  • Functions는 중첩 될 수 있다.
  • Functions는 커리 될 수 있다. (일부 기능)
  • Functions는 주변 컨텍스트를 기억해서 클로저를 만들 수 있다.
  • Functions는 이름을 지정하거나 익명으로 지정할 수 있다. 익명 functions를 즉시 호출 할 수 있다.(IIFE)

Javascript

//`this`에 접근 할 수 있는 보통 function
function standardFunction(arg1, arg2) {
    return `${arg1}:${arg2}`;
}

// 변수에 할당 된 function
const assignedFunction1 = standardFunction;

// 변수에 할당 된 화살표 function
const assignedArrowFunction = (arg1, arg2) => {
    return `${arg1}:${arg2}`;
};

// function를 인수로 받아들이고 function를 반환하는 고차 function
function functionAsArgumentAndReturn(addFn, arg1, arg2) {
    const out = addFn(arg1, arg2);
    // 클로저를 반환
    return function(numArg) {
        return out + numArg;
    };
}

const out = functionAsArgumentAndReturn(
    (a, b) => {
        return a + b;
    },
    5,
    10
)(10);
// returns 25

// 중첩 functions
function nested() {
    console.log("outer fn");
    function nested2() {
        console.log("inner fn");
        const arrow = () => {
            console.log("inner arrow");
        };
        arrow();
    }
    nested2();
}

nested(); // prints:
// outer fn
// inner fn
// inner arrow

// 이것은 function을 반환하는 고차 function이다.
function add(x) {
    // function은 클로저로 반환된다.
    // 변수 x는 이 방법의 외부 스코프에서 얻어지고 클로저에 기억된다.
    return y => x + y;
}

// 더 많은 커링을 만들기 위해 add 메소드를 사용하고 있다.
var add10 = add(10);
var add20 = add(20);
var add30 = add(30);

console.log(add10(5)); // 15
console.log(add20(5)); // 25
console.log(add30(5)); // 35

// 즉시 호출 된 익명 function (IIFE)
(function() {
    console.log("anonymous fn");
})();
// prints: anonymous fn

Go

// 보통 function, 이 function은 중첩 될 수 없다
func standardFunction(arg1 string, arg2 string) string {
    return fmt.Sprintf("%s:%s", arg1, arg2)
}

func main() {

  // 변수에 할당 된 function
  var assignedFunction1 = standardFunction

  // 변수에 할당되고 중첩 된 익명 function
  var assignedFunction2 = func(arg1 string, arg2 string) string {
      return fmt.Sprintf("%s:%s", arg1, arg2)
  }

  // function을 인수로 받아들이고 function를 반환하는 고차 function
  var functionAsArgumentAndReturn = func(addFn func(int, int) int, arg1 int, arg2 int) func(int) int {
      var out = addFn(arg1, arg2)
      // 클로저를 반환
      return func(numArg int) int {
          return out + numArg
      }
  }

  var out = functionAsArgumentAndReturn(
      func(a, b int) int {
          return a + b
      },
      5,
      10,
  )(10)
  fmt.Println(out) // prints 25

  // 중첩 된 익명 functions
  var nested = func() {
      fmt.Println("outer fn")
      var nested2 = func() {
          fmt.Println("inner fn")
          var nested3 = func() {
              fmt.Println("inner arrow")
          }
          nested3()
      }
      nested2()
  }

  nested() // prints:
  // outer fn
  // inner fn
  // inner arrow

  // function를 반환하는 고차 function
  var add = func(x int) func(y int) int {
      // function은 클로저로 반환된다.
      // 변수 x는 이 방법의 외부 스코프에서 얻어지고 클로저에 기억된다.
      return func(y int) int {
          return x + y
      }
  }

  // 더 많은 커링을 만들기 위해 add 메소드를 사용하고 있다.
  var add10 = add(10)
  var add20 = add(20)
  var add30 = add(30)

  fmt.Println(add10(5)) // 15
  fmt.Println(add20(5)) // 25
  fmt.Println(add30(5)) // 35

  // 즉시 호출 된 익명 function (IIFE)
  (func() {
      fmt.Println("anonymous fn")
  })()
  // prints: anonymous fn

  assignedFunction1("a", "b")
  assignedFunction2("a", "b")
}

차이점

  • JavaScript Functions에는 두가지 형태가 있다; 정규 functions, 그리고 화살표 functions 반면에 Go 에는 보통 functions 와 인터페이스 functions이 있다. 보통 Go functions는 this를 가지지 않는다. 따라서 화살표 functions과 더 유사한 반면에 인터페이스 functions는 this와 비슷한 것이 있다. 이런 이유로 JavaScript의 보통 functions에 가깝다. Go에는 글로벌 this 라는 개념이 없다.

Javascript

function normalFnOutsideClass() {
    console.log(`여전히 글로벌 this에 접근할 수 있다: ${this}`);
}

const arrowFnOutsideClass = () => {
    console.log(`this를 가지고 있지 않다`);
};

class SomeClass {
    name = "Foo";
    normalFnInsideClass = function() {
        console.log(`this로 class에 접근할 수 있다: ${this.name}`);
    };
    arrowFnInsideClass = () => {
        console.log(`this로 class 참조에 접근할 수 있다: ${this.name}`);
    };
}

new SomeClass().normalFnInsideClass();
new SomeClass().arrowFnInsideClass();

Go

type SomeStruct struct {
    name string
}

func (this *SomeStruct) normalFnInsideStruct() {
    // 변수 이름을 this 또는 다른것으로 지정할 수 있다
    fmt.Printf("this로 struct 참조에 접근할 수 있다\n: %s", this.name)
}

func main() {

    var normalFnOutsideStruct = func() {
        fmt.Println("내 스코프의 변수에 접근할 수 있다")
    }
    normalFnOutsideStruct()

    var structVal = SomeStruct{"Foo"}
    structVal.normalFnInsideStruct()
}
  • JavaScript functions는 다른 값 유형과 동일하므로 Go에서 불가능한 추가 속성을 보유 할 수도 있다.
  • Go functions는 암시적으로 명명된 반환값(implicit named returns)을 가질 수 있다.
  • Go에는 익명 functions만 중첩 될 수 있다.
  • Go functions는 여러 값을 반환할 수 있지만 JavaScript에서는 한 값만 반환할 수 있다. 그러나 JS에서 구조 분해(destructuring)를 사용해서 문제를 해결할 수 있으니 양쪽 모두에서 비슷한 모양의 functions를 만들 수 있다.

Javascript

function holdMyBeer() {
    return ["John", 2];
}

let [a, b] = holdMyBeer();
console.log(`Hey ${a}, hold my ${b} beer\n`);

Go

func holdMyBeer() (string, int64) {
    return "John", 2
}

func main() {
    a, b := holdMyBeer()
    fmt.Printf("Hey %s, hold my %d beer\n", a, b)
}

스코프

스코프는 변수가 유효한 컨텍스트로 변수를 사용할 수 있는 위치를 결정하며 JS와 Go 둘 모두 많은 유사점을 가지고 있다.

유사점

  • 둘 다 function 스코프를 가지며 Functions는 주변 스코프를 기억할 수 있다.
  • 둘 다 block 스코프를 가지고 있다.
  • 둘 다 글로벌 스코프를 가지고 있다.

차이점

  • Go에는 JavaScript에서 까다로운 개념인 this라는 개념이 없다. 내 생각에 이것은 Go에서 일을 훨씬 더 간단하게 만든다.
  • Go에서 같은 스코프의 변수는 다시 선언할 수 없다. Go의 var는 JS에서 let 키워드에 더 가깝다.

흐름 제어(Flow control)

Golang의 흐름 제어는 JavaScript와 매우 유사하지만 많은 면에서 더 단순하다.

유사점

  • for 루프는 둘 다 매우 유사하다.
  • while 루프는 매우 유사하지만 Go는 동일한 for 키워드를 사용한다.
  • forEach도 기능면에서 비슷하지만 구문은 상당히 다르다.
  • 루프에서 break/continue를 할 수 있다. labels을 사용하여 그렇게 할 수도 있다.
  • if/else 문법은 상당히 비슷하고. Go 버전이 좀 더 강력하다.

JavaScript

// For loop
for (let i = 0; i < 10; i++) {
    console.log(i);
}

// While loop
let i = 0;
while (i < 10) {
    console.log(i);
    i++;
}

// Do while

let j = 0;
do {
    j += 1;
    console.log(j);
} while (j < 5);

// ForEach loop
["John", "Sam", "Ram", "Sabi", "Deepu"].forEach((v, i) => {
    console.log(`${v} at index ${i}`);
});

// for of loop
for (let i of ["John", "Sam", "Ram", "Sabi", "Deepu"]) {
    console.log(i);
}

// For in loop
const obj = {
    a: "aVal",
    b: "bVal"
};

for (let i in obj) {
    console.log(obj[i]);
}

Go

func main() {
    // For loop
    for i := 0; i < 10; i++ {
        fmt.Println(i)
    }

    // While loop
    i := 0
    for i < 10 {
        fmt.Println(i)
        i++
    }

    // Do while
    j := 0
    for {
        j += 1
        fmt.Println(j)
        if j == 5 {
            break
        }
    }

    // ForEach and for of loop
    for i, v := range []string{"John", "Sam", "Ram", "Sabi", "Deepu"} {
        fmt.Printf("%v at index %d\n", v, i)
    }

    // For in loop
    var obj = map[string]string{
        "a": "aVal",
        "b": "bVal",
    }

    for i, v := range obj {
        fmt.Printf("%v at index %s\n", v, i)
    }
}

차이점

  • Go에는 삼항 연산자(ternary operator)가 없다.
  • switch 구문은 비슷하지만 Go는 기본적으로 중단되고 JS는 기본적으로 통과한다.
  • Go에서는 그 기능에 대해 fallthrough 키워드를 사용할 수 있고, JS에서는 break 키워드를 사용할 수 있다.
  • JS에는 while, forEach, for in & for of 루프와 같은 더 많은 반복 방법이 있으며, Go에서는 사용할 수 없으나 대부분 for 구문을 사용하여 달성 할 수 있다.
  • if/else는 Go에서 초기화 할당을 가질 수 있다. 아래 코드에서 val에 대한 할당은 ifelse블록 내에서만 스코프를 가지며 그 외부에는 적용되지 않는다. JS에서는 불가능하다.

Go

if val := getVal(); val < 10 {
    return val
} else {
    return val + 1
}

메모리 관리

메모리 관리도 JS와 Go의 세부 사항을 제외하고는 매우 유사하다.

유사점

  • 둘 다 런타임에 가비지 컬렉션이 된다.
  • 둘 다 힙 및 스택 메모리를 가지며 이는 둘 다 동일하다.

차이점

  • Go는 사용자의 메모리 관리를 추상화하는 동안 사용자에게 노출되는 포인터를 가지고 있는 반면에 JavaScript 포인터는 완전히 추상화되어있고 값과 참조로만 작업한다.
  • Go는 대기시간에 초첨을 맞춘 concurrent tricolor mark-and-sweep 알고리즘을 사용하지만 JS 엔진은 일반적으로 Mark-Sweep이 널리 사용되는 다른 알고리즘을 구현한다. 예를 들어 V8 엔진은 Mark-Sweep과 Scavenge 알고리즘 둘다 사용한다.

Misc

  • 주석은 /// * * /로 동일하다.
  • JS와 Go 모두 다른 모듈 가져 오기를 지원하지만 동작은 같지 않다.
  • SetTimeout은 둘 다 비슷하다. setTimeout(somefunction, 3*1000) vs time.AfterFunc(3*time.Second, somefunction).
  • 둘 다 스프레드 연산자가 있다 console.log (... array) vs fmt.Println (array ...). Go 스프레드는 인터페이스 arrays/slices에서만 작동한다.
  • 둘 다 메소드 인수에 나머지 연산자가 있다 ...nums vs nums ...int.

결론

1부에서 우리는 두 언어에서 비슷한 개념을 살펴보았다. 이 시리즈의 다음 편에는 JS와 Go의 더 다른 점을 알아볼 것이다. 다음 편에는 이것보다 다른 것들이 더 있지만 몇 가지 차이점들은 미묘해서 JavaScript 개발자는 이해하기 쉬울 것이다.

다음 편에서 살펴볼 것:

  • Types & Variables
  • Error handling
  • Mutability
  • Composition instead of inheritance
  • Concurrency
  • Compilation
  • Paradigm

참조:

마치며

부족한 영어 실력이나마 매끄럽게 번역을 하기 위해 직역 및 의역을 다수 사용했다.

(문장의 해석이 오역이거나 수정이 필요한 점이 있다면 언제든 알려주시면 반영하겠습니다.)

  • function은 함수라 표기하지 않고 영문 그대로 표기했다.
  • 같거나 비슷한 뜻으로 쓰인 것 같으나 구분을 위해 normal function은 보통 function, regular function은 정규 function으로 번역했다.