PHPerに捧ぐGo超入門

Wataru SendoPosted by

この記事は GRIPHONE Advent Calendar 2019 の12日目の記事です。

こんにちは。サーバーサイドエンジニアのsengokです。
今回は(ちょうど自分のような)普段開発にPHPを使用していて、
そろそろGoにもチャレンジしてみようかなと考えてる方向けのGo超入門記事を書いていきたいと思います!

はじめに

この記事は、基本的にA Tour of Goを参考にひとまずこの辺をさらっと覚えておけば簡単なショートコードを書ける!読める!という部分をピックアップして書いています。
特に、

  • 普段PHPで書いてる○○ってGoだとどう書くの?
  • どの概念が近いの?

という観点から学習の走り出しに役立ちそうな項目をGoとPHPの比較表のようにまとめていきたいと思います。

変数 / 定数

プログラムといえばまずは変数宣言から!
最初はGoでの変数 / 定数の宣言の仕方から見ていきましょう!

概要GoPHP備考
変数宣言var a int = 1$a = 1Goでの変数宣言は「var 変数名 型 = 初期値」が基本です。
ただし、変数宣言時に特に初期値を定めなければ型に応じた初期値が入ります。
例えば「int」なら0、「string」なら””(空文字)といった具合です。
関数内でのみ有効な変数宣言 a := 3 Goでは関数内でのみ、変数名 := 初期値 といった暗黙的な型宣言を用いた短い構文で変数宣言ができます。
定数const Hoge = 123 define('HOGE', 123);

// オブジェクト定数
const HOGE = 123;
Goでも定数については型の指定が不要です。
(扱えるのは文字 / 文字列 / boolean / 数字のみです)
他の変数に代入する際は、代入時に変数の型が決定するため、var s = Hoge のように型を書かなくても代入することができます。

演算子

変数 / 定数を扱えるようになったら次は式を書きたくなりますよね!
とはいえ、GoでもPHPで使える基本的な演算子(+, -, &&, || などなど)は変わらず存在します。
ただ、PHPerが普段使うであろうかつ「あれ?もしかしてGoにこれは無いの?」と特に疑問に思いそうな演算子がいくつかあったのでさらっとピックアップしていきます。

概要GoPHP備考
等価演算子 a == b $a === $b aとbが等価であるか。
PHPでは2値の型の相互変換後の値が等価であるか比較する「==」と型まで考慮し厳密に比較する「===」がありました。Goでは、型が変数宣言時に決まっているので「==」のみで比較が十分です。
累乗演算子 $a ** $b aはbの累乗。
Goでは演算子としては用意されていないため、例えばmath.Powなどで代用します。
三項演算子$a ? $b : $c aが真ならb, 偽ならc。
Goでは演算子としては用意されていないため、素直にif-elseを使います。
null合体演算子 $a ?? $b aがnullならばb, aがnullでなければaを採用する。
Goではnull(というかnil)の扱いが難しいので今回は説明を割愛しますが、とりあえずこの演算子はありません。
宇宙船演算子 $a <=> $b aがbより小さければ負、aとbが同値なら0、aがbより大きければ正の値を返す。
Goでは演算子としては用意されていないため、もしも独自のソートを行いたい場合は、sortパッケージのsort.interfaceで定義されているメソッドを実装して比較するのが良さそうです。

Goは静的な型付け言語です。
それゆえにPHPでさらっと使ってたアレ、どこいったの?が多いのでしっかり見ていきましょう!

概要GoPHP備考
基本型bool
string
int, int8 ~ int64
uint, uint8 ~ uint64, uintptr
float32, float64
complex64, complex128
boolean
string
integer
float
Goでは変数宣言時に明示的に型を指定、かつ暗黙的なキャストは行われずコンパイルエラーになってしまうため注意が必要です。
例えばintをfloat32に変換する場合はvar a = float32(1) といったように明示的に宣言する必要があります。
int, uintなど特にbit数が指定されていないものは32bitシステムでは32bit、64bitシステムでは64bitになります。
Structs vertex := struct {
  X int
  Y int
}{
  X: 1,
  Y: 2,
}
fmt.Println(vertex.X)

Goにはいわゆる構造体があります。
PHPではクラスや連想配列で表現することが多いかと思います。
Arraysvar a [2]string
a[0] = "hoge"
a[1] = "fuga"


// これでも ↑ と同じ
a := [2]string{"hoge", "fuga"}


// 別の変数に配列を代入すると値のコピーが行われる
b := a
// 強いて言えば
$a = new SplFixedArray(2);
$a[0] = "hoge"
$a[1] = "fuga"


// これでも ↑ と同じ。
$a = SplFixedArray::fromArray(["hoge", "fuga"])


// 別の変数に配列を代入すると値のコピーが行われる
$a = [1, 2, 3]
$b= $a;
GoでArrayと言うと型を指定された固定長配列になります。
固定長のため、最初に決めた長さを超えて要素を追加することができません。
もしも、PHPでいうArrayのような可変長配列を扱いたい場合は後述のSlicesが近いかと思います。
Slices// 例1. 要素を末尾に後から追加できる
var s = []int{1, 2, 3, 4, 5}
append(s, 6)


// 例2. 宣言済みのArrayからスライスできる
a := [3]int{1, 2, 3}


// 0から1番目を切り出す。sの中身は{1, 2}
s := a[0:2]


// a の中身は{99, 2, 3}、sの中身は{99, 2} になる
s[0] = 99
// 要素を末尾に後から追加できる
$s = [1, 2, 3, 4, 5];
$s[] = 6;


// 例2はPHPの配列の参照渡しに近い
$a = [1, 2, 3];
$b = &$a;


// $aも$bも[1, 2, 3, 4]になる
$b[] = 4;
SlicesではArrayと異なり、長さが可変で後から要素を追加することができます。Slices自体は配列への参照になっています。
そのため、例2のように元になる配列aがある状態から切り出しを行い値を書き換えた場合、配列aの値も書き換わるため少し注意が必要です。
Maps// m の中身は ["first":1, "second":2] になる
var m = make(map[string]int)
m["first"] = 1
m["second"] = 2


// 単純な型なら変数宣言時に初期化できる
var mm = map[string]int{
  "first": 1,
  "second": 2
}
// 要素の追加
mm["third"] = 3
// 要素の取得
a := mm["first"]
// 要素の削除
delete(mm, "first")
// m の中身は ['first' => 1, 'second' => 2] になる
$m = [];
$m['first'] = 1;
$m['second'] = 2;


// PHPでも変数宣言時に初期化できる
$mm = [
  'first' => 1,
  'second' => 2, 
];
// 要素の追加
$mm['third'] = 3;
// 要素の取得
$a = $mm['first'];
// 要素の削除
unset($mm['first']);
配列に値だけでなく、独自にキーを指定する際はMapsを利用します。
PHPでは、よく連想配列と呼ばれたりもしますね。
要素の追加や取得、削除の仕方もほとんど同じです。

制御構文

次は分岐やループといった制御構文です!
いよいよプログラムらしくなってきましたね!

概要GoPHP備考
if – elseif a == 1 {
  // なんかやる
} else if a == 2 {
  // なんかやる
} else {
  // なんかやる
}
if ($a == 1) {
  // なんかやる
} elseif ($a == 2) {
  // なんかやる
} else {
  // なんかやる
}
Goではelse ifのようにelseとifの間にスペースを入れて記述します。
PHPでもelse ifのようにスペースを入れて記述することはできますが、特定条件でパースエラーが起きるので一般的にはelseifと繋げて書くことが多いかと思います。
switch
switch animal {
case "cat":
  // なんかやる
case "dog":
  // なんかやる 
default:
  // なんかやる
}
switch ($animal) {
case "cat":
  // なんかやる
  break;
case "dog":
  // なんかやる 
  break;
default:
  // なんかやる
}
Goでは、breakステートメントを書かなくても該当するcaseのみを実行してswitchステートメントから抜けてくれます。
もしも、フォールスルーを行いたい場合は各caseで
case “cat”:
  // なんかやる
  fallthrough
case “dog”
  // 以下略
のように記述することで実現できます。
for // 普通のforループ
for i := 0; i < 10; i++ {
// なんかやる
}


// 無限ループ
for {
  // なんかやる
}
// 普通のforループ
for ($i = 0; $i < 10; $i++) {
// なんかやる
}


// 無限ループ
for (; ;) {
  // なんかやる
}
for文はPHPとほとんど同じですが、括弧がいらないなど少し短く書けます。
GoでもPHPと同様に初期化、条件式、後処理ステートメントの全てを省略可能です。
whilei : = 1;
for i < 10 {
  i += i;
}
$i = 1;
while($i < 10) {
  $i += $i;
}
Goにはwhile文そのもの自体は存在しません。初期化 / 後処理ステートメントを省略して書くことでPHPのwhileと同じような動作が行えます。
foreach a := [3]string{“hoge”, “fuga”, “piyo”}
for index, value := range a {
  // なんかやる
}


// valueだけ欲しい
for _, value := range a {
  // なんかやる
}


// indexだけ欲しい
for index := range a {
  // なんかやる
}
$a = [‘hoge’, ‘fuga’, ‘piyo’];
foreach($a as $index => $value) {
  // なんかやる
}


// valueだけ欲しい
foreach($a as $value) {
  // なんかやる
}


// valueだけ欲しい
foreach(array_keys($a) as $index) {
  // なんかやる
}
GoでいわゆるPHPのforeachを行うためにはRangeを利用します。
GoでもPHPでもindexだけ欲しい(そんなにある?)、
valueだけ欲しい場面があると思いますが、Goの方がシンプルに書けますね。

type(≠ クラス)

最後はPHPにはないtypeという概念について見ていきます!あと一息!

概要GoPHP備考
type (≠ クラス)type Person struct {
  Name string
  Age int
}
class Person {
  public $name
  public $age
}
まず、Goにはクラスという概念はありません。
Goでは、structなどの既存の型にtypeキーワードを使って名前をつけることで新しい型として扱うことができます。
例えば、PHPの例にあるPersonクラスを定義したくなった場合、GoではstructにPersonという名前を付けて新しい型として扱います。
メソッド type Person struct {
  Name string
  Age int
}


// メソッドを定義
func (p *Person) IncrementAge {

  p.Age++
}

func main() {
  // まだ20歳
  p := Person{"hoge", 20}
  // 呼ぶ!21歳になる!
  p.IncrementAge()
}
class Person
{
  public $name;
  public $age;


  public function __construct(
    string $name,
    int $age
  )
  {
    $this->name = $name;
    $this->age = $age;
  }


  public function incrementAge()
  {
    $this->age++;
  }
}


// まだ20歳
$p = new Person('hoge', 20);
// 呼ぶ!21歳になる!
$p->incrementAget();
Goではtypeに対してメソッドを定義することができます。
どのtypeに対して定義するかをfunc と メソッド名の間で指定します。
メソッドによっては、structの値を書き換えるような処理を行いたい場合があるかと思います。その場合、例のようにtypeを指定する際*Personのように指定します。
このようにGoではtypeにメソッドを定義する際、値渡しか参照渡しかを意識して定義する必要があります。
インターフェース type Animal interface {
  Cry()
}
 
type Cat struct {
  Name string
}
 
func (cat Cat) Cry() {
  fmt.Println("meow")
}
 
func main() {
  cat := Cat{"mike"}
  // CatはCryを実装してるのでtype Animalの変数に入れられる
  var animal Animal = cat
}
interface Animal
{
  public function cry();
}
 
class Cat implements Animal
{
  public $name;

  public function cry()
  {
    echo ('meow');
  }
}
Goではtypeが特定のインターフェースを実装する際に、PHPのように明示的にimplemntsする必要がありません。

型アサーション

最後は型アサーションについて見ていきます!

概要GoPHP備考
型アサーション type Hoge interface {
  HogeMethod()
}
 
type HogeHoge interface {
  HogeHogeMethod()
}
 
type Fuga struct {
}
 
func (f Fuga) HogeMethod() {
  fmt.Println("aiueo")
}
 
func main () {
  a := Fuga{}
  var i interface{} = a
  _, ok := i.(Hoge)
  if (ok) {
    fmt.Println("aはinterface Hogeを実装してるよー")
  } else {
    fmt.Println("aはinterface Hogeを実装してないよー")
  }
}
interface Hoge
{
  public function HogeMethod();
}
 
interface HogeHoge
{
  public function HogeHogeMethod();
}
 
class Fuga implements Hoge
{
  public function HogeMethod()
  {
    echo ('aiueo');
  }
}

$a = new Fuga();
 
if ($a instanceof Hoge) {
  echo ('$aはinterface Hogeを実装してるよー')
} else {
  echo ('$aはinterface Hogeを実装してないよー')
}
Goでもあるtypeが特定のインターフェースを実装したものかどうかを調べることができます。
特定のインタフェースを実装しているどうかは例のような_, ok := i.(Hoge) といった構文で調べます。
例では使用しなかったので「_」の部分を略記してますが、ここには本来type名が入ります。

最後に

この記事では、PHPer向けのGo超入門について書きました。
それでは明日の記事もお楽しみに!