今川館

都内勤務の地味OLです

Goのtype宣言はすごい

Goのtype宣言を使うと、特定の型を元にして別の型を定義できる。

一番単純な例: 基本データ型から別の名前の型を作る

type Foo int
var x Foo
x = 18
fmt.Printf("%T\n", x)  // => main.Foo

これはわかる。intからFooという別の型を作るだけである。

関数に型名を付与する使い方

ところが、このGoのtypeってやつは関数にも利用できることに驚いた。

type NumFunc func(xs []int) int

これは全然okで、構文エラーにはならない。

つまり、

// intのスライスを引数に取って合計値を「文字列で」返す
func NumString(xs []int) string {
	acc := 0
	for i := 0; i < len(xs); i++ {
		acc += xs[i]
	}
	return strconv.Itoa(acc)
}

// intのスライスを引数に取って合計値を返す
func Sum(xs []int) int {
	acc := 0
	for i := 0; i < len(xs); i++ {
		acc += xs[i]
	}
	return acc
}

type NumFunc func(xs []int) int

var fn NumFunc

fn = Sum // これはok

// fn = NumString // これはコンパイルエラー
// cannot use NumString (type func([]int) string) as type NumFunc in assignment

このように、関数を任意の型として宣言し、所定のシグネチャを守る関数しか代入できなくさせられる。

動くコードはhttps://play.golang.org/p/Em_52ORgYqこちら

関数のファクトリを簡潔に記述する

そもそもこの話に興味を持ったのは、Pythonのデコレータに相当することをGoでやる方法を考え始めたときだった。

Pythonのデコレータは関数のファクトリだから同じことをすれば良いのだが、最初以下のような安直なやり方でしか書けないと思っていた。

package main

import "fmt"

func main() {
	var foo = func(x int) int {
		return x + 5
	}

	var deco = func(fn func(int) int) func(int) int {
		return func(x int) int {
			return fn(x)
		}
	}

	var fn = deco(foo)
	fmt.Println(fn(100)) // => 105
}

とにかくdecoの定義の部分がひどい。

「func(int) int」という記載が3回も並ぶと実に読みにくい・・

なので、この重複の多い部分をtypeでくくり出す。

package main

import "fmt"

func main() {
	type F func(x int) int

	deco := func(fn F) F {
		return func(x int) int {
			return fn(x)
		}
	}

	var fn = deco(func(x int) int { return x + 5 })
	fmt.Println(fn(100)) // => 105
}


すると、decoの記載がだいぶスッキリするじゃないか。というところでこのtypeってやつはすごいと思ったのであった。

参考リンク

Go for Pythonistas