Nag's Blog
Avatar

ブロックチェーンやそれ以外に関してツラツラと...

TypeScriptエンジニアが学ぶGo言語

TypeScriptとGo言語のざっくり比較

| 項目 | TypeScript | Go |

| --------- | --------------------------------- | -------------------------------------------------------- |

| 実行環境 | Node.jsなど | ネイティブバイナリ生成 |

| 型 | 静的だが柔軟<br>(型推論・Union型など) | 静的でシンプル<br>(型推論少なめ) |

| クラス | あり<br>OOP(オブジェクト指向プログラミング)中心 | なし<br>(構造体 + interface) |

| 非同期処理 | Promise, async/await | goroutine, channel |

| パッケージ管理 | npm, yarn | go mod |

| null安全 | null, undefined, ?記法 | nilのみ、nilチェックが必要 |

| エラーハンドリング | try-catch | error型+明示的処理 |

| 配列・マップ | Array, Map | slice, map |

| ジェネリクス | v4以降のTypeScriptで豊富<br>(arg: T): T | Go1.18から導入、まだ制限あり<br>`func Sum[T Number](arg T) T {...}` |

Hello, World!

TypeScript

function greet(name: string): string {
    return Hello, ${name}!;
}

Go

import "fmt"
func greet(name string) string {
    return fmt.Sprintf("Hello, %s!", name)
}

ポイント

  • fmt.Sprintfは文字列フォーマット関数。

classに相当する、構造体とインターフェース

TODO: TypescriptでClassを使っていない。クラスに相当するものをGoで使いたい時に面食らう

TypeScript

```ts

interface Person {

name: string;

greet(): string;

}

const p: Person = {

name: "みけ",

greet() {

return こんにちは、${this.name}です。;

}

}

const main = () => {

console.log(p.greet()); // こんにちは、みけです。

}

```

Go

```go

type Person struct {

Name string

}

func (p Person) Greet() string {

return fmt.Sprintf("こんにちは、%sです。", p.Name)

}

func main() {

p := Person{Name: "みけ"}

fmt.Println(c.Greet()) // こんにちは、みけです。

}

```

ポイント

  • structはオブジェクトっぽいやつ
  • メソッド定義は(p Person)←レシーバ(thisに近い)
  • Goの関数名の頭文字が大文字。Goでは小文字はプライベート扱い

非同期処理・並行処理

TypeScript

```ts

async function doSomething() {

}

async function run() {

await doSomething();

console.log("done");

}

```

Go

```go

import (

"fmt"

"time"

"sync"

)

func do1() {

time.Sleep(1 * time.Second)

fmt.Println("2")

}

func do2(wg *sync.WaitGroup) { // アスタリスクはポインタ型で、ポインタ渡しされることを示す

defer wg.Done() // 関数が終了したらWaitGroupを減らす

time.Sleep(3 * time.Second)

fmt.Println("4")

}

func main() {

var wg sync.WaitGroup

fmt.Println("1")

do1()

wg.Add(1)

go do2(&wg) // &を付けてポインタ渡しをする

fmt.Println("3")

wg.Wait() // 全てのゴールーチンが終わるまで待つ

}

// 1 2 3 4 で出力される

```

ポイント

  • Go言語では基本的に同期処理される。goキーワードを関数呼び出しの前に付けることで、その関数はゴールーチンとして並列に実行される。
  • ポインタ渡し(参照渡し)はGo言語では推奨されることが多い。Go言語では基本的にオブジェクトも値渡しされる(大きなオブジェクトでは暗黙的に参照渡しされる)
    • TODO: どれくらい大きなオブジェクトだと参照渡しになる?

参照渡し

TypeScript

```ts

const cat = {

name: "シロ"

}

const changeName = (cat) => {

cat.name = "ポチ"

}

const main = () => {

changeName(cat);

console.log(cat.name); // ポチ

}

```

Go

```go

import (

"fmt"

)

type Cat struct {

Name string

}

func changeNameByValue(cat Cat) {

cat.Name = "ポチ"

}

func changeNameByPointer(cat *Cat) {

cat.Name = "ミケ"

}

func main() {

cat := Cat{Name: "シロ"}

changeNameByValue(cat)

fmt.Println(cat.Name) // シロ

changeNameByPointer(&cat)

fmt.Println(cat.Name) // ミケ

}

```

ポイント

  • アンパサンドでポインタの明示をしている。Go言語ならではの美しさ。

配列とスライス

TypeScript経験者が混乱しやすい部分。TSと同じ感覚で使えるのはスライス。

Goでは配列はあまり使わず、スライスが主流。

[[Go 配列とスライスの違い]]

Go

```go

import "fmt"

func changeArray(arr [3]string) {

arr[0] = "シロ"

}

func changeSlice(slice []string) {

slice[0] = "シロ"

}

func main() {

var catsArray [3]string = [3]string {"クロ", "ミケ", "ハチワレ"}

changeArray(catsArray)

fmt.Println(catsArray) // クロ ミケ ハチワレ

catsSlice := [] string{"クロ", "ミケ", "ハチワレ"}

changeSlice(catsSlice)

fmt.Println(catsSlice) // シロ ミケ ハチワレ ← 参照渡しなので変化する

}

```

ポイント

  • 配列は固定長、スライスは可変長で作成。
  • 配列は関数に渡した時点で、完全にコピーされる。よって、元の配列は変化しない。
  • スライスの実態はポインタ、長さ、容量からなる「小さい構造体」。中身のポインタが共有されるので、変更が反映される。

interface

TypeScriptと比べると、若干クセがある。

[[Go interfaceまとめ]]

TypeScript

```ts

interface Animal {

speak(): string;

}

class Dog implements Animal {

speak() {

return "ワン!";

}

}

```

Go

```go

type Animal interface {

Speak() string

}

type Dog struct {}

type Cat struct {}

func (d Dog) Speak() string {

return "ワン!"

}

func (c Cat) Speak() string {

return "ニャー!"

}

// それぞれの動物に対して、Speak()の呼び出しを1つ書くだけで鳴かすことができる(多態性)

func main() {

animals := []Animal{Dog{}, Cat{}}

for _, a := range animals {

fmt.Println(a.Speak())

}

}

// ワン! ニャー!

```

ポイント

  • Goではimplementsを書かない。明示しなくても、自動的に判定される構造的型システム。
  • 上記のGoのサンプルでは、DogもCatもSpeak()を定義しているので、勝手にAnimalとして扱える。
  • 多態性を再現できる。
  • この書き方はGoの強み。OOPよりもインターフェースによる抽象化に重点を置いた設計になっている。

空インターフェース

```go

var x interface{}

```

これは「どんな型でも代入できる変数」である。

TypeScriptで言うと`any`に近いが、Goでは安全に使うために「型チェック」をする必要がある。

```go

import "fmt"

func printAnything(x interface{}) {

fmt.Println(x)

}

printAnything(42) // int

printAnything("Hello") // string

printAnything(true) // bool

// どれも通る

```

```go

import "fmt"

type Animal interface {

Speak() string

}

type Dog struct {}

type Cat struct {}

func (d Dog) Speak() string {

return "ワン!"

}

func (c Cat) Speak() string {

return "ニャー!"

}

func main() {

speakAny(Dog{})

speakAny(Cat{})

speakAny("これは文字列です")

speakAny(12345)

}

func speakAny(a interface{}) { // voidなので型は書かない

// 型アサーションでaがAnimalか確認

if animal, ok := a.(Animal); ok {

fmt.Println(animal.Speak()) // 型アサーション後の変数を使用

} else {

fmt.Println("これは動物ではありません")

}

}

// ワン!

// ニャー!

// これは動物ではありません

// これは動物ではありません

```