Golangのモジュールのアップデート

Golangを 1.21にアップしたら、buildでgo.bug.st/serial関連のエラー出るので、関連部分のソースコードは触っていないのでモジュールを最新にアップデートした

% go list -m all
~~
go.bug.st/serial v1.6.2
~~

前後省略

これは更新後ですが、前の版数は1.5.0

更新履歴は、

https://pkg.go.dev/go.bug.st/serial@v1.6.2?tab=versions

% go get go.bug.st/serial 

で更新してやります、これでめでたくビルドは成功

 

admin

DIライブラリgoogle.wire(@Golang)

DIで依存関係のあるファイルを定義すると、その依存関係を解決したコードを生成するツールです。これだけでは訳がわからなので以下から実例を。

https://www.asobou.co.jp/blog/web/google-wire

はwireのtutorialをベースに記述しています、https://github.com/google/wire

まず何はともあれwireをインストールします、実はwireはコマンド実行で依存関係を記述したコードを作成します。

% go install github.com/google/wire/cmd/wire@latest

インストールするとパスが通っていれば、% wireで実行できるようになります。

//+build wireinject

package main

import "github.com/google/wire"
 
func InitializeEvent() Event {
    wire.Build(NewEvent, NewGreeter, NewMessage)
    return Event{}
}

ここで、NewEvent, NewGreeter, NewMessageが依存関係のある構造体の名前になります。wire.Bildの引数にこれらの構造体の情報を与えると、依存関係を調べて、依存関係を解消するコードを% wire で生成します。(以下)

// Code generated by Wire. DO NOT EDIT.

//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject

package main

// Injectors from wire.go:

func InitializeEvent() Event {
	message := NewMessage()
	greeter := NewGreeter(message)
	event := NewEvent(greeter)
	return event
}

実は生成されたコードは、元の依存関係を記述しているコード(コメント部分)と同じで、Intialize Eventで呼び出しているだけになります。


func main() {
    e := InitializeEvent()
 
    e.Start()
}

/*
func main() {
    message := NewMessage()
    greeter := NewGreeter(message)
    event := NewEvent(greeter)
 
    event.Start()
}
*/

実行するには、

% go buildで実行ファイルを作成すれば、作成されたコードをリンクした実行ファイルが作成されるの、そのまま実行可能です。

このtutorialでは単純なケースなので、それほどメリットは感じられませんが依存関係が複雑になればwireを使うメリットがあるように思います。

 

admin

 

 

 

 

第一級オブジェクトと第一級関数(@Golang)

現代の言語で多くは関数を第一級オブジェクトとして扱えます、第一級オブジェクトとは言語の持つ基本的な操作を制限なしに実行できることというのがWikipediaでの解説(以下引用)

第一級オブジェクトファーストクラスオブジェクトfirst-class object)は、あるプログラミング言語において、たとえば生成、代入、演算、(引数戻り値としての)受け渡しといったその言語における基本的な操作を制限なしに使用できる対象のことである。

で、第一級オブジェクトとして関数が扱えれば、それは第一級関数となります。より具体的には以下の使用例を。以下やってることは冗長なだけですが、関数を変数に代入できることは『その言語における基本的な操作を制限なしに使用できる』の一つに該当します。

実際にやっていることは関数の型とポインタを渡しているだけだろうと思いますが。

package main

import (
	"fmt"
	"math"
)

type calif interface{
	calc(i float64) float64
}

type calst struct{
	c calif
}

func (c calst)calc(i float64) float64 {
	return math.Sqrt(i)
}

func main() {
	ci := calst{}
	cal := ci.calc(1)
	ary := []float64{25, 36, 100, 121, 144}
	for _, ft := range ary {
		cal = ci.calc(ft) // set func(calc) to variable(cal)
		fmt.Println(cal)
	}
}

現代の多くの言語では、関数は第一級オブジェクトですが、Rustではそうなっていないようです。意図した理由はあるはずですが、

https://zenn.dev/hinastory/articles/7857427ea390c5

 

admin

 

 

 

contextの基本的な動作(@Golang)

contextは英語の意味は『文脈』ですが、プログラミングにおいては言語に関わらず使われる用語なので、おそらく他に適切な用語はなさそう。

その意味するところはAPI境界を超えて、スレッド間でも共有可能なオブジェクトを提供するといったところかと思います。contextはあたかも一つのスレッドのような動作をするように思います。

contextが一番使われるシーンはJavaでもありますがHttpRequestのようなネットワーク処理でタイムアウトやキャンセルが発生するようなケースに相性が良いから。

以下はcontextでcancel()処理とtimeout()処理、それに何らかの値(key/value pair)を設定してみたもの。

package main

import(
	"context"
	"fmt"
	"time"
)

func main() {
	ctx0 := context.Background()		// to initialize the context

	ctx1, cancel1 := context.WithCancel(ctx0)	// to generate cancel function "cancel1"

	ctx3 := context.WithValue(ctx1, "key1", 99)

	go func(ctx1 context.Context) {
		select {
		case <-ctx1.Done():
			fmt.Println("th1 was canceled")
		}
	}(ctx1)

	ctx2, _ := context.WithTimeout(ctx0, 2*time.Second)	// to set the context to timeout mode

	go func(ctx2 context.Context) {
		select {
		case <-ctx2.Done():
			fmt.Println("th2 was timeouted", "key value : ", ctx3.Value("key1"))
		}
	}(ctx2)

	cancel1()

	time.Sleep(3*time.Second)
}

最後の3秒待ちは値が2秒だとfunc(ctx2)処理が完了する前にプログラムが終了するのでこの値以上が必要です。

channelを使ってclose()処理をしても同じように処理はできますが、

https://zenn.dev/hsaki/books/golang-context/viewer/done

じゃなぜcontextを使うかというと、channelはgoroutine限定ですがcontextは汎用的に使えるからと言うことになるでしょう。

 

admin

 

interfaceの実装(@Golang)

Golangのinterfaceの実装はimplementsのように明示することなく、interfaceで定義したメソッドを全て実装すればinterfaceとしての性質を持ちます。それゆえDependency Injectionも分離したコードが書けるわけですが、他の言語から入ると戸惑いがあるのも事実。

以下の簡単な例で実装時の違いを見てみます。以下のコードではcase 1 ~ 3のいずれかの行を有効にすればコンパイルはできますが、意味はそれぞれ違います。

case 1 : 最も一般的な記述方法、StatStructがinterfaceを実装と認識するのでmain()からinterfaceのメソッドが使えます。

case 2 : 構造体に要素を持たせた場合、この場合はcase 1と同じですが、要素を使わないなら空の構造体にしとくのが自然。

case 3 : このケースとcase 1/2との違いはこの行を記述するだけでStat()の実装がなくともinterfaceを実装したことになること。コンパイルはできますが実行時にStat()が呼べないのでエラーになります。実はこれはDIの手法そのものですが。

package main 

import "fmt"

type Stater interface {
    Stat()
}

//type StatStruct struct {}     // case 1
//type StatStruct struct {a int}    // case 2
type StatStruct struct {Stater}     // case 3

func (StatStruct) Stat() {
    fmt.Println("It's fine today")
}

func main() {
    st := StatStruct{}
    st.Stat() 
}

現実的にはメソッドの実装ではcase 1を使うことが普通だと思います。さらに言えば構造体無しでもStat()を定義すれば動きますが、それはおそらくGoのデザインパターン(ideom)から外れるように思います。

 

admin

DI(Dependency Injection) — (@Golang)

DIはコードをinterfaceを介することでソースコード間の依存性を少なくして、例えばDBの接続種類の入れ替えとかテストの時にスタブに切り替えるとかをコードの修正を最低限で実行できる方法です。結果としてコードの保守性が向上するということになります。

https://free-engineer.life/golang-dependency-injection/

の記事がわかりやすかったのでこれを事例にしてみます。

実際にはVScodeで動かしてみて、パッケージが全てmainでは気持ちが悪いのでパッケージ名は分離しています。以下パッケージ分離版のコードです。

https://github.com/chateight/golang/tree/master/basic/di

<task_usecase(use case.go)のDI実現のキーコード部分>

// Saveメソッドの実装はTaskRepositoryで行っている
type TaskRepositoryInterface interface {
	Save(Task) (Task, error)
}
// structのフィールドにTaskRepositoryInterfaceを持たせる
// こうすることで、CreateTaskメソッドでDBへの保存処理を呼ぶことができる
type TaskUsecase struct {
	repo TaskRepositoryInterface
}

~~途中省略~~

func (usecase TaskUsecase) CreateTask(title string) (task.Task, error) {
	t := task.Task{Title: title}
	task, err := usecase.repo.Save(t)
	return task, err
}

つまりinterfaceを経由した抽象的な呼び出しにより依存先をinterfaceとすることで、CreateTask中ではinterfaceを呼び出し、結果として実装(ここではsave())と分離がされているので、例えば他の実装との入れ替えが簡単にできるということになります。

Golangの場合には明示的にinterfaceをimplementで明示しなくとも、interfaceで定義した関数のフットプリントと同じ関数が実装されていればimplementされたことになるのでDIの記述のようなケースでも、他の例えばJavaなどの比較すると綺麗にコードが分離できます。Golangはクラシックなオブジェクト指向では無いですが、現代的な抽象化手段を持つ言語です。

 

admin

構造体

GolangやRustでは積極的に構造体を使い、また構造体を効率的に使うための言語仕様も用意されていますが、その背景は必要なデータセットは構造体にまとめることで、読みやすく従ってバグも入りづらいコードを書くことにあるだろうと思います。

同じ機能をGolangとRustで記述してみます。

<Golang>

type Rectangle struct {
  length  float64
  breadth float64
}

func (r Rectangle) area() float64 {
  return r.length * r.breadth
}

<Rust>

struct Rectangle {
    length: f64,
    breadt: f64,
  }
impl  Rectangle{
  fn area(&self) -> f64 {
    return &self.length * &self.breadt
  }
}

Rust呼び出し方、

  fn main() {
    let mut rec = Rectangle{length:0.0,breadt:0.0};

    rec.length = 20.0;
    rec.breadt = 30.0;

    print!("{}", rec.area())
}

Golangでは

(r Rectangle)

を使って構造体と結びつけ、Rustでは

impl Rectangle

でインターフェースの如くimplementでメソッドを定義していますが、上記二種の結果は全く同じ出力をするし、記法にも大差ありません。c++ではこのような記法はないので、現代の言語の特徴と言ってもいいのではないかと思います。

 

admin

Goのアップデート

すでに2月に1.20になっていますが今さら。homebrewでインストールしているので、

% brew upgrade go

で1.19 -> 1.20へ、

==> Running `brew cleanup go`…

で1.19は関連ファイルが削除されます。

$GOROOTを確認すると、

% go env GOROOT
/opt/homebrew/Cellar/go/1.20.3/libexec

1.20にupgradeされました。$GOROOTはこのように自動的に更新されるので、直接手を入れる必要はないようです。以上はcuiモードで有効で、VScodeだと、以下のGo xxをクリックすれば、インストール済みの版数の選択あるいは新規ダウンロードができます。

 

admin

ラズパイ用チャットアプリ再構成

チャット機能は以下の記事の通り独立モジュールとして開発していましたが、

https://isehara-3lv.sakura.ne.jp/blog/2023/04/28/ラズパイにチャット機能を追加/

mifareカードアプリと分ける必要もなくなったので統合しました。ソースコードのリンクは以下になります。

https://github.com/chateight/myfare-copy

修正箇所は、router.goの以下の2行と、myfareのブラウザ画面からのリンクボタン追加とmain.goからの呼び出し(以下のコード切り出し)の追加。

r.Static(“/static”, “./go_chat/view/static”)
r.LoadHTMLGlob(“./go_chat/view/*.html”)
Static/LoadHTMLGlob()のリンクディレクトリ呼び出しは、Run()を起動したmain.goが基準になるのでそこからの相対ディレクトリに変更、関連処理のmain.goからの呼び出しを移動しただけです。
<main.go部分>
func main() {
	// to call card reader function()
	go uidSerial.SerialMain()

	// chat data base create and make table to store chat messages
	sql_db.DbCreate()
	// start chat service
	go chat.Run()
	
	// start myfare card service
	wevServer()
}

 

admin

GolangでHTML escape処理

HTML escapeはほとんどのケースで入口、つまりHTMLでFORM入力された文字列のチェック、でのJavaScriptの仕事です。もしGolangがフロントエンドの役割を果たす時にはhtmlパッケージにある、

func EscapeString(s string) string

関数が使えます。もしunescapeするならば、

func UnescapeString(s string) string

になります。docのリンクは、

https://pkg.go.dev/html

 

admin