ScalaのZIOを使ってみる
目次
ZIOとは
ZIOは、Scalaでエフェクト(副作用)を扱い、非同期や並列のプログラミングをするためのライブラリ。
ZIO[R, E, A]という型を中心に利用して、エフェクトを記述する。
- A: エフェクトの成功時に返す値の型
- E: エフェクトの失敗時に投げる可能性のあるエラーの型
- R: エフェクトが依存する環境(サービス等)の型
例えば、以下のようなZIOを返す組み込みのサービスがある。
- Console.readLine(prompt: String): ZIO[Any, IOException, String] → 標準入力から文字列を読み取る。成功時は入力された文字列(String)を返し、IOExceptionを投げる可能性がある
- Console.printLine(line: => Any): ZIO[Any, IOException, Unit] → 標準出力に文字列を書き出す。IOExceptionを投げる可能性がある
- Random.nextInt: ZIO[Any, Nothing, Int] → Int型の乱数を発生させて返す。エラーを投げることはない(EがNothing)
ZIOを利用する準備
build.sbtにZIOのライブラリの依存関係を追加する。
libraryDependencies += "dev.zio" %% "zio" % "2.1.21"
Hello, World!!
ZIOでアプリケーションを作成する場合、ZIOAppDefaultを継承してrunメソッドをオーバーライドすれば良い。
runメソッドがエントリーポイントになる。
import zio._ object MyApp extends ZIOAppDefault { def run: ZIO[Any, IOException, Unit] = Console.printLine("Hello, World!!") }
変換(Transforming)と連鎖(Chaining)
ZIOをmapで変換したり、flatMapで連鎖させたりすることができる。以下のプログラムは、2を出力する。
def run: ZIO[Any, IOException, Unit] = ZIO.succeed(1) .map(_ * 2) .flatMap(Console.printLine(_))
特に、flatMapでZIOを連鎖させる場合、for内包表記(for-yield)を利用して記述するのがよい。
def run: ZIO[Any, IOException, Unit] = for { input <- Console.readLine("Please input something: ") _ <- Console.printLine(s"Input value: ${input}") } yield ()
結合(Combining)
ZIOをzip(または<*>演算子)結合して並列で実行し、結果を組として取得することもできる。この場合、左右どちらかがエラーになると全体がエラーになる。
def run: ZIO[Any, IOException, Unit] = for { random <- Random.nextInt <*> Random.nextInt (randomLeft, randomRight) = random _ <- Console.printLine(s"Left-value: ${randomLeft}, Right-value: ${randomRight}") } yield ()
結合するエフェクトの片方がUnitを返す等、値が不要である場合はzipLeft(または<*演算子)またはzipRight(または*>演算子)を使って、どちらかの値のみを取得することもできる。
def run: ZIO[Any, IOException, Unit] = for { input <- Console.printLine("Please input something: ") *> Console.readLine _ <- Console.printLine(s"Input value: ${input}") } yield ()
参考文献
Getting Started with ZIO | ZIO
https://zio.dev/overview/getting-started
Introduction to ZIO | Baeldung on Scala
https://www.baeldung.com/scala/zio-intro
ZIO小史 - k.dev
https://kdotdev.com/kdotdev/zio-history
retrieve a tuple directly in a for comprehension with ZIO? · Issue #2761 · zio/zio
https://github.com/zio/zio/issues/2761
GraphQL APIについて
目次
GraphQLの概要
GraphQLはAPI用のクエリ言語であり、Facebookによって2012年に開発された後2015年にオープンソース化され、2018年にはGraphQL Foundationが設立されてGraphQLはGraphQL Foundationにより管理されるようになりました。
GraphQLでは、APIでクエリ可能な型やフィールドをスキーマとして定義し、またAPIを利用するクライアントは取得するフィールドを指定することができます。そのため、複雑で階層的なデータ構造を持つAPIの設計に適しています。
リクエスト操作の種類
GraphQL APIにリクエストする際の操作は、以下の3種類があります。
- クエリ: データ取得操作
- ミューテーション: データ更新操作
- サブスクリプション: リアルタイム更新通知
RESTful APIとの比較
- GraphQL APIでは、エンドポイントが1つだけになります。
- GraphQL APIは、必要なデータのみを指定して取得することができるため、その点において効率的です。
- RESTful APIではデータ構造を変更する場合はバージョンを変更したりエンドポイントを追加したりする必要がありますが、GraphQL APIではフィールド追加等で対応できる場合が多くなります。そのため、バージョン管理の必要性が少なくなります。
- GraphQL APIはRESTful APIに比べて、一般的に実装が複雑になります。
- GraphQL APIでは、キャッシュの利用が複雑になります。
スキーマの定義
GraphQLのスキーマは、スキーマ定義言語によって定義されます。
例えば、以下のように型を定義します。
type User { id: Int! name: String! age: Int! address: String }
また、クエリやミューテーションを定義できます。
type Query { user(id: Int!): User } type Mutation { createUser(user: User): User }
クエリ・ミューテーションの利用
クエリでは、取得するフィールドを指定してリクエストすることができます。
例えば、以下のリクエストを送信すると
query { user(id: 1) { id name } }
以下のように指定したフィールドだけがレスポンスされます。
{ "id": 1, "name": "Mano Sakuragi" }
ミューテーションでは、データを登録・更新することができます。ここでも、戻り値のフィールドを指定することができます。
例えば、以下のリクエストを送信すると
mutation { createUser(user: { name: "Hiori Kazano", age: 15 }) { id name } }
以下のようにレスポンスが返ってきます。
{ "id": 2, "name": "Hiori Kazano" }
ScalaでGraphQLを実装する場合に利用できるライブラリ
以下のようなライブラリを利用できそうです。
- Sangria
- Caliban
参考文献
Introduction to GraphQL | GraphQL
https://graphql.org/learn/
初心者向けガイド:GraphQLの基本情報
https://apidog.com/jp/blog/basis-of-graphql/
GraphQL とは?をわかりやすく解説
https://www.redhat.com/ja/topics/api/what-is-graphql
GraphQL vs REST: What's the Difference? | IBM
https://www.ibm.com/think/topics/graphql-vs-rest-api
GraphQLスキーマの解説|NestJS でGraphQL開発を試す
https://zenn.dev/keisuke333/books/ce5e3208a9d393/viewer/f5bd3f
[Scala]Sangriaを使ってGraphQL APIを実装する #sangria - Qiita
https://qiita.com/petitviolet/items/e3e87c3f3e740b3c57ba
Scala, Akka HTTP, CalibanでGraphQL APIを実装する
https://zenn.dev/kowaremonoid/articles/4faaec0356877a
AWSで2つのVPC間をピアリング接続する
AWSで、2つのVPC間をピアリング接続して通信できるようにしてみました。
目次
作業の概要
VPC間で通信できるようにするため、以下のような流れで作業をします。
事前準備
VPCを作成する
ピアリング接続を試すために、VPCを2つ作成します。
今回はピアリング接続を試すだけなので、プライベートサブネット1つ・AZ1つで作成します。

各VPCにEC2インスタンスを作成する
通信するWebサーバ(EC2インスタンス)を各VPC上に作成する。

VPCのピアリング接続を作成する
VPCのメニューの[仮想プライベートクラウド]>[ピアリング接続]から、「ピアリング接続を作成」をクリックしてピアリング接続を作成します。
リクエスタ、アクセプタに事前準備で作成したVPCを設定し、「ピアリング接続を作成」をクリックします。

ピアリング接続を作成すると「承諾の保留中」となるため、[アクション]>[リクエストの承諾]をクリックして、ポップアップの[リクエストを承諾]をクリックして承諾します。
以上で、2つのVPC間にピアリング接続が作成されました。
ルートテーブルを編集する
ピアリング接続を作成しただけではVPC間の通信がルーティングされないため、ルートテーブルを編集します。
各VPCのサブネットが関連付けられているルートテーブルに、もう一方のVPCへのルートを追加します。
まず、VPC1のサブネットに関連付けられているルートテーブルを編集します。

ルートテーブルにチェックを入れて詳細を表示して、[ルート]>[ルートを編集]からルートを追加します。

送信先がVPC2のCIDRブロック、ターゲットが作成済みのピアリング接続のルートを追加します。[変更を保存]をクリックして、ルートテーブルを更新します。

VPC2のサブネットに関連付けられているルートテーブルも、同様に編集します。

以上で、2つのVPC間のルートがルートテーブルに追加されました。
セキュリティグループを編集する
ルーティングまでは実施されるようになりましたが、このままだとファイアウォール(セキュリティグループ)で通信が拒否されてしまうため、セキュリティグループを編集します。
今回は、VPC1のEC2→VPC2のEC2 のpingが通るように設定してみます。
VPC2にあるEC2に設定されているセキュリティグループを編集します。

[インバウンドルール]>[インバウンドのルールを編集]からインバウンドルールを追加します。

VPC1からの「すべてのICMP - IPv4」を許可するルールを追加します。[ルールを保存]をクリックして、セキュリティグループを更新します。

以上で、VPC1のEC2→VPC2のEC2 のpingが許可されるようになりました。
動作確認
VPC1のEC2に接続して、実際にVPC2のEC2にpingをしてみます。
EC2 Instance Connectで接続しました。

pingが通りました!
参考文献
VPC ピア機能とは - Amazon Virtual Private Cloud
https://docs.aws.amazon.com/ja_jp/vpc/latest/peering/what-is-vpc-peering.html
AWSのVPCをVPCピアリング接続で相互に通信できるようにする
https://zenn.dev/rescuenow/articles/8e21a751c828c6
【神アプデ】え!? プライベートなEC2にもAWSマネコンから簡単SSHできるように?? #ハンズオン - Qiita
https://qiita.com/minorun365/items/696ba8bf683a403550d0
アバター改変でやってしまったアニメータ関連のミス事例
VRC向けのアバター改変をしている際に、アニメータ関連でミスしてしまった事例を備忘録としてまとめます。
目次
事例
アニメーションレイヤーのWeightが0になっている
初歩的なミスにして、結構やりがち(だと思う)なミス。Weightが0になっているので、当然アニメーションが適用されない。
これをやりがちなのには理由があって、アニメーションレイヤーを新規作成したときのWeightのデフォルト値が0だからです。(デフォルトは1でもいい気がするんだけどね……)

VRC Expression Parametersにアニメーションパラメータを追加していない
VRC特有の問題です。アバターを他人から見たときに、アニメーションが同期されない問題。
アニメーションパラメータはVRC Expression Parametersに設定して"Synced"にチェックを入れると他人にパラメータ値が同期されますが、設定していなかったためにパラメータが同期されず、他人視点ではアニメーションが発動しませんでした。
もともと私は少し勘違いをしていて、VRC Expression ParametersはVRC Expression Menuでパラメータを変更できるようにするための設定だと思っていたのですが、それが理解として正しくありませんでした。


なお、ContactReceiver等に"LocalOnly"というパラメータがありますが、こちらのチェックを外してもパラメータが同期されるわけではありません。詳細は割愛します。
Interruption Sourceの設定が適切でない
この問題は、おそらく私が使用しているアバターの「びしょぬれのしずくさん」の表情アニメータコントローラがやや特殊な作りであるため発生したものだと思います。(他のアバターについてはあまり知識がないので、もし一般的な作りだったらすみません)
しずくさんの表情アニメータコントローラは、以下のように表情が切り替わるときにデフォルト表情を経由するようになっています。本来、ステートのTransision設定でInterruption SourceがNext Stateになっているため、途中のステート(デフォルト表情)がスキップされて変更先の表情に直接変わるような動きになっています。それが、Interruption SourceがNoneになっていたことで、表情の切替時に一瞬だけデフォルト表情が表示されるようになってしまっていました。

おわり
以上、アニメータ関連のミス実例3つでした。ただの備忘録ですが、誰かのお役に立てば幸いです。
参考文献
知識0だけどExメニューでアクセとか武器とかをオンオフしたい!!(初心者向けExpression解説1)[VRChat]|Ran_kotonoha
https://note.com/ran_kotonoha/n/nd1fc7f1d84ea#9fe187ed-1547-45ba-b9d3-f3b695817597
【VRChat】Contactsで物を出し入れする【Avatar Dynamics】|風庭ゆい
https://note.com/yui0471/n/n54beffc6df97
VRChat上のアバター間データ同期の概念|おにく/Oniku@VRC
https://note.com/onikuvrc/n/n4fc95cf23dab
【初心者Unity】アニメーションの遷移を検証③【遷移の割り込み】 | TECH PROjin
https://tech.pjin.jp/blog/2021/08/31/unity-transition_3/
ScalaTestのウォークスルー
ScalaTestでどのようにテストを記述するのかを学習したため、メモ的に記事に内容を残します。
目次
テストの実行(sbt)
testタスク
プロジェクトに含まれるすべてのテストを実施するには、sbtのtestタスクを実行します。
testOnlyタスク
特定のテストクラスのみテストを実施するには、testOnlyタスクを実行します。
"testOnly {クラス名}"の形式で実行すると、指定されたクラスのテストのみが実行されます。また、クラス名にはワイルドカードも使用できます。
テストの記法
ScalaTestでは、いくつかの記法でテストを記述することができます。
記法はテストの宣言の見た目にのみ影響を与え、テストの内容には影響を与えません。そのため、その場で最適な記法を選ぶことができます。
自分で記法を選択するのが面倒な場合、ScalaTestの公式DocではFlatSpecが推奨されています。
以下では一部の記法を紹介し、それ以降ではWordSpecスタイルを採用します。
FunSuiteスタイル
FunSuiteスタイルは非常にシンプルな記法で、xUnit(例えばJUnit)に近い記法です。
import org.scalatest.funsuite.AnyFunSuite class FunSuiteTest extends AnyFunSuite { test("A negative number should be less than 0") { assert(-1 < 0) } }
FlatSpecスタイル
FlatSpecスタイルはFunSuiteに近い記法ですが、"X should Y"や"X must Y"といった形式で記述します。
FunSuiteスタイルを、やや自然言語のように記述できるようにしたといった感じでしょうか。
import org.scalatest.flatspec.AnyFlatSpec class FlatSpecTest extends AnyFlatSpec { "A negative number" should "be less than 0" in { assert(-1 < 0) } }
FunSpecスタイル
FunSuiteスタイルを入れ子構造にできるようにしたような記法です。
RubyのRSpecと似た記法のようで、describeとitによりテストを入れ子で記述します。
import org.scalatest.funspec.AnyFunSpec class FunSpecTest extends AnyFunSpec { describe("A number") { describe("when negative") { it("should be less than 0") { assert(-1 < 0) } } } }
WordSpecスタイル
こちらはFlatSpecスタイルを入れ子構造にしたような記法です。
Scalaの別のテストフレームワークであるspecs/specs2と似た記法です。
import org.scalatest.wordspec.AnyWordSpec class WordSpecTest extends AnyWordSpec { "A number" when { "negative" should { "be less than 0" in { assert(-1 < 0) } } } }
アサーション(Assertions)
アサーションを使用してテストを記述することができます。
以下で、一部のアサーションを紹介します。
assert
assertは記述した条件がtrueの場合に成功し、falseの場合に失敗します。
"A Set" when { "empty" should { "have size 0" in { assert(Set.empty.size == 0) } } }
assertResult
あるコードの結果が特定の値になることを確認したい場合、assertResultを利用できます。
"A Set" when { "singleton" should { "have size 1" in { assertResult(1) { Set("test").size } } } }
assertThrows
あるコードが特定の例外をスローすることを確認したい場合、assertThrowsを利用できます。
"A Set" when { "empty" should { "produce NoSuchElementException when head is invoked" in { assertThrows[NoSuchElementException] { Set.empty.head } } } }
intercept
assertThrowsと同様に例外をスローすることを確認できますが、テストを中断せず例外の内容をさらにテストできます。
"A Set" when { "empty" should { "produce NoSuchElementException including 'empty' in message when head is invoked" in { val caught = intercept[NoSuchElementException] { Set.empty.head } assert(caught.getMessage.contains("empty")) } } }
succeed/fail
テストを強制的に成功/失敗させます。
"A test" should { "succeed" in { succeed } "fail" in { fail("Always fails.") } }
マッチャー(Matchers)
ScalaTestには、アサーションを"should"や"must"といった単語を使用して記述するためのDSL(ドメイン固有言語)が定義されています。 これをマッチャー(Matchers)と言います。
これらは、should.Matchersまたはmust.Matchersをミックスインすることで使用できます。
以下では、一部のマッチャーを紹介します(Shouldマッチャーを使用します)
等価性
equal, not equalを用いると、等価性をチェックできます。
また、be, not beも用いることができますが、こちらは等価性のカスタマイズができません(デフォルトの等価性チェック)
"A Set" when { "empty" should { "have size 0" in { Set.empty.size should equal (0) } "have not size 1" in { Set.empty.size should not equal (1) } } }
より大きい・小さい
<, > , <=, >= を利用すると、より大きい・小さいや以上・以下の比較ができます。
"A number" when { "negative number" should { "less than 0" in { -1 should be < 0 } } "positive number" should { "greater than 0" in { 1 should be > 0 } } "0" should { "0 or less" in { 0 should be <= 0 } } }
ある数から一定の範囲内
ある数nから一定の範囲dの中に数値が入っているかを、"+-"を利用してチェックできます。小数も可能。
"A number" when { "5" should { "in range 6 +- 2" in { 5 should equal (6 +- 2) } } }
サイズ・長さ
have size, have lengthを用いると、サイズや長さのチェックができます。
"A Set" when { "empty" should { "have size 0" in { Set.empty[Number] should have size 0 } } } "'ABC'" should { "have length 3" in { "ABC" should have length 3 } }
Booleanプロパティ
shouldBeのあとにプロパティ名のシンボルを記述すると、Booleanプロパティの真偽値をチェックできます。
"A Set" when { "empty" should { "be empty" in { Set.empty[Number] shouldBe Symbol("isEmpty") } } }
参考文献
ScalaTest User Guide
https://www.scalatest.org/user_guide
ScalaTest入門 - Qiita
https://qiita.com/verdoyant/items/e8d81e80268b714fdfbc
Scalaのユニットテスト入門 - seratch's weblog in Japanese
https://seratch.hatenablog.jp/entry/20110807/1312726957
【STEP4】テスト : Scalaでコードを書く - Qiita
https://qiita.com/yukinagae/items/038f75e9a1bf17978886
Scalatest: 特定のテストケースだけ実行したい - Qiita
https://qiita.com/suin/items/0294a53d6babd69f29a9