第一級オブジェクトと第一級関数(@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

toioをtoio.jsで動かしてみる

toioを動かすにはいくつかの手段がありますが、最も簡単なのはiPadのアプリをインストールして使うScratchの拡張機能としてtoioが使えるtoio Doを使うこと。toio Doはブラウザ経由でも動きますが、まだベータ版なので保存機能とかが無いようでiPadアプリを使うのが一番確実。

さらには、Node.js用のライブラリにtoio.jsというのがあって、

https://github.com/toio/toio.js

このGitHubにはサンプルプログラムもあって簡単に動作確認できます。動作環境はmacbook Air apple silicon M1/Ventura 13です。

上記リンクにサンプルプログラムの動かし方があるので、そのままやってみました。

いくつかサンプルありますが、動画で動かしているのは、

% yarn example:keyboard-control

になります。

async function main() {
  // start a scanner to find nearest cube
  const cube = await new NearestScanner().start()

  // connect to the cube
  await cube.connect()

上のコードはサンプルコードからの切り出しですが、cubeへの接続は専用の機能NearestScanner()が用意されて、電源の入っている一番近くのcudeに接続されるようになっています。package.jsonにある”@toio/scanner”: “^1.0.0″にNearestScanner()も含まれていることになります。

 

admin

micro:bitもtoioもScratch Linkで接続できると言うことは、

技術情報が公開されていて色々弄れそうなのでtoioを購入、まだ手元には届いていませんが、toioをScratchから使う時にはパソコンとの接続にはScratch Linkを使う(Web Bluetooth API)と言う記述がありました。したがってmicro:bitもコマンドが公開されてるだろうと思って調べてみると、

https://qiita.com/youtoy/items/c98c0996458a21fc1e67

と言う事例がありました。2021年の記事なのでmicro:bit V1向けかどうかは微妙なのでV2では多少変更が必要(少なくともUUIDは別物になる)かもしれませんが。まあ元々ScratchやMakeCodeはどちらもブラウザ上で動作して、ブラウザとmicro:bit間の通信手段はScratch Linkなので中を通るデータは変わってもtoioでも使えるのでしょう。

上記のサンプルはパソコン側での受信なので、パソコンからの送信は、

https://qiita.com/yagshi/items/abfb3717fb978d031b7e

があります。

いずれにしてもbluetoothとのブラウザからの接続はWeb Bluetooth APIを使いますが、ブラウザが限定でChromeがメジャーブラウザです。

過去記事:https://isehara-3lv.sakura.ne.jp/blog/2021/04/01/microbitでbluetooth通信/

C/C++で低レベルでの接続には、

https://tech.microbit.org/software/runtime/

が関連ドキュメントになりそうですがmicro:bit V2はCODALというハードウェアの抽象化手段があるようです。

 

admin

Rustのテキスト

Rustの場合、コミュニティがしっかりしていてドキュメントの整備もきちんとされているので、特別にテキストを購入する必要はなさそうです。

https://doc.rust-jp.rs/book-ja/title-page.html

の15章あたりまで、ダラダラと読んでますが、例は

https://doc.rust-jp.rs/rust-by-example-ja/

こちらを使えば、一通りの理解はできるように思います。

結局のところ、Rustの仕様のかなりの部分はメモリ管理をコンパイラが判断できるようにユーザーが記述するコードで指示することに集約されるだろうと思う。したがって堅牢性と実行速度を要求するOSやアプリケーションにはRustを使うと言う選択になるんだろう。組み込みやIoTなどで小規模な開発では、コードが冗長になりがちなので、Rustである必要性はあるのかと言う感覚はあります。

 

admin

クロージャー(Golang and Rust)

現代の言語ではクロージャー機能を多くの言語で持っていますが、同じ機能(単純な1の加算)をGolangとRustで実装の比較。Rustの説明で出てくる、クロージャーは環境をキャプチャーできる匿名関数という定義はわかりやすい。

<Golang>

package main

import "fmt"

func add(i int) func() int {
	n := i

	clo := func() int {
		n++
		return n
	}

	return clo		// equivalent to specify anonymous function here and maybe it's more popular
}

func main() {
	a := add(1)
	for i := 1; i < 10; i++ {
		fmt.Println(a())
	}
}

<Rust>

fn f1(i: i32) -> Box<dyn FnMut -> i32> {	// FnMut recieves &mut self
	let mut i = i;						// dyn keyword sepcifies a trait pointer in the heap area
	Box::new(move || {				// force to allocate values in the heap area
		i += 1;
		i
	})
 }
 
 fn main() {
	 let mut cup = f1(0);
	 for _ in 1..10 {
		 println!("{}", cup());
	 }
 }

コードの参考は、

https://scrapbox.io/takker/Rustで関数を返す関数を作

FnMutの解説は、

https://qiita.com/hiratasa/items/c1735dc4c7c78b0b55e9

Fnでは変更できないし、FnOnceでは一度しか呼べないのでFnMutの指定になります。

比較してみると、Rustの方が細かな指定が必要ですが、これはメモリ管理に関する指定を明示的に行わなければいけない言語だからでしょう。したがってこれぐらいの例では変わらないけれども、コードは長めになります。比較してGolangは細かな操作はしなくとも使うだけなら簡単ということになるでしょうか。

Box<dyn … >については、

Boxは変数をスタック領域ではなくヒープ領域への割り当て、dynについては以下の通り、

https://doc.rust-lang.org/stable/rust-by-example/trait/dyn.html

“Rust tries to be as explicit as possible whenever it allocates memory on the heap. So if your function returns a pointer-to-trait-on-heap in this way, you need to write the return type with the dynkeyword

 

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

関数(メソッド)ごとに文字列と文字列スライスは意識しないといけない(@Rust)

Rustの文字型にはString(可変長)、文字列スライス(固定長)、char型(1バイト)の三種類がありますが、関数やメソッドによって引数や適用が変わってきます。

以下は文字列を全て小文字に変換する関数to_lowercase()の例ですが、操作対象は文字列スライスでなければ機能しませんし、出力はString型になります。

VScodeだと、型は自動で補完してくれますね。

fn main() {
    let text = "heLLO worLd";
    let result: String = text.to_lowercase();
    let search= "worl";

    if result.contains(search){
        println!("{} and {}", result, search);
    }

以下は関数(メソッド)のAPIの記述です、

何故、操作対象は文字列スライス型でなければならないかですが、おそらく変更がなくて参照するだけだろうからだと思います。処理量の観点でも参照の方が少なくて済むだろうし。

 

admin