DMM英会話を始めて総英会話時間が1,000分になりました

どうも、お久しぶりです。

気がつくともう11月に突入し、2022年も残りわずかとなってきましたね。早すぎる!

ほとんどブログも書かずに何に熱中してたか・時間を使っていたかというと、最近は"英語力"を伸ばそうと毎日頑張っている次第です。

というのも、Duolingo English Testのスコア投稿にも書いたように、会社でプロダクト開発を行うにあたり、ニュージーランドチームや日本のビジネス・モバイルアプリチームと要求・仕様を整理・検討し開発までの流れのコミュニケーションを英語・日本語を使って行っています。

この仕事の中で、Github上のIssueでの議論や毎週行われる会議ももちろん英語です。

日本語でも難しい仕様に関する議論を英語で行うには、あまりにもスピーキング能力が足りないので、正確に伝わらない・会議を開いてもらっているがその場ですぐに意見を発言したり反論ができず、ただただ時間が無駄になっている感覚がありました。

今すぐ英語学習をしてもすぐには効果が出ないのは理解しつつ、やらないよりはやって少しでも話せるようになろうと思いました。

ということで、とにかくインプットの時間を増やして、それをアウトプットしようとDMM英会話を9/22から始めてみました。

感想

DMM英会話を始めて良かったなと思います。

今まではDuolingoやスピークバディ、英文法の勉強のみしていただけなので、少しのインプットはあっても書く・話すというアウトプットする機会が少なかったのですが、それをDMM英会話を使うことで改善できたのは良かったです。

また、レッスンで何回も英語を話すことで間違うことを恐れずに話せるようになったのも良かったです。

英会話のスケジュールに関してですが、25分~/1日を週6で回しています。

契約当初は25分 * 3回/1日できるプランを契約していたのですが、平日仕事終わってから3回やるのはさすがに続かなかったので今のようなスケジュールになっています。

2022/11/05時点で1,000分(回数でいうと40回)に到達したので、ひとまず継続できていることが嬉しい!

課題

英会話を始めて実際に仕事の会議でスッと言葉が出てきやすくなったものの、まだまだ正確に伝えたり複雑だったり難しい文になると途端に話せません。

DMM英会話のレッスンを受けていても同じで、教材の中にディスカッションをするExerciseがあったりするのですが、言いたいことがあるんだけど伝えられないことがあったりします。

そういうのをメモしておいて、あとでどう伝えるべきだったかを思い出してライティングするなどして練習しています。

また、英文法の学び直しをしつつ、「日本語(思考) → 英語(変換) → 発声」ではなく「英語(思考)→ 発声」ができるように、とにかく声を出して読んで口に英語を覚えさせようとしています。

この英語学習で合っているのか分からないですが、とにかく先生には英文を読めと言われたので、言われた通りにやります!

今後

ニュージーランドのメンバーと技術的な議論をスラスラとできることが目標なのですが、道のりが長いのでマイルストーンをいくつか切る必要があると思っています。

なので、DMM英会話でいうと次のゴールドランク(総英会話時間が3,000分/120レッスン)を最短で目指して頑張ろうと思います!

では。

紹介コード

もしよかったら、紹介コードを使ってください!

この紹介コードを使うことで、お互いが「プラスレッスンチケット」が3枚付与されるのでお得です!

eikaiwa.dmm.com

2回目のDuolingo English Testを受けてきました

どうも、お久しぶりです。

早くも9月末になり、会社では評価面談の時期で個人的には憂鬱な気分になります。

それはさておき、日々Duolingoアプリを使って勉強したり、スピークバディというアプリを使って話す練習をしたり、英文読解をしたり少しずつではありますが英語を勉強していました。

なので、定期的に結果を計測しておこうということで数日前に2回目のDuolingo English Testを受けてきました。

Duolingo English Testがどんなものかは以下の記事に記載してるので、興味があれば呼んでもらえると嬉しいです。

blog.ryskit.com

やっぱり受けてみて、いちいち会場までテストを受けに行かなくて済むのが本当に最高ですね!

思い立ったらすぐに試験料払って受験できる体験が毎回最高すぎます!!

結果

certs.duolingo.com

前回が総合スコアが90で、今回の総合スコアは85でした!

いや、スコア落ちとるやないかーーーーい!!!

正直、このスコア見て心の中で自分に突っ込んでしまいました。

ただ、細かく見ると以下のような感じでした。

  • Literacy(受験者の読んで書く能力)は前回より5だけ下がった
  • Comprehension(受験者の読んで聞く能力)は前回と同じ
  • Conversation(受験者の聞いて話す能力)は前回より5だけ上がった
  • Production(受験者の書いて話す能力)は前回より20上がった

ConversationやProductionは前回低すぎて悔しかった領域だったので、少しだけですが上がっていてよかったです。

ただ、Literacyは前回よりも下がっていたのでちゃんと読解練習できてないんだなというのは分かりました。(やり方の見直しが必要)

感想

話す・書く能力がもともと低く、その部分を鍛えるための勉強はしていたつもりだったのでそこは少しですが結果が出ていて良かったです。

ただ、会社の会議でNZチームのメンバーと英語で会話をする必要があるのですが、正直毎週準備をしていてもイレギュラーなことを聞かれてしまうとアワアワして上手く話せない状態です。

これでいかんと今週からDMM英会話も始めてみました。これが数カ月後のDuoligo English Testに効果があるのか個人的にも楽しみです!

とにかく、結果は受け止めて全体的に英語力の伸ばせるように日々学習を継続しようと思います。以上!

Foldable and Traverse

今日も今日とてScala with Catsを読む。

この章ではコレクションに対するイテレーションの2つの型クラスについて見ていく。

  • Foldable
    • foldLeftfoldRightの操作を抽象化したもの
  • Traverse
    • Applicativeを使ってfoldingよりも少ない手間でイテレートを行う高度な抽象化をしたもの

Foldable

CatsのFoldablefoldLeftfoldRightを型クラスに抽象化したもの。

Foldableのインスタンスはこの2つのメソッドが定義し、多くの派生メソッドを継承している。

import cats.Foldable

object Playground1 extends App {

  val ints = List(1, 2, 3)
  println(Foldable[List].foldLeft(ints, 0)(_ + _))
}

Folding Right

FoldablefoldRightの定義はfoldLeftと違い、Evalモナドが使われている。

def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]: Eval[B]

Evalを使うということは、常にスタックセーフにfoldingできるということ。

Folding with Monoids

FoldablefoldLeftの上に定義された多くの便利メソッドを提供してくれる。

それらの多くは標準ライブラリにある馴染みのあるメソッドを模倣したもの:find, exists, forall, toList, isEmpty, nonEmpty ...

これらに加えて、CatsはMonoidを使った2つのメソッドを提供する。

  • combineAlll(alias: fold)
    • Monoidを使って連続したすべての要素を結合する
  • foldMap
    • ユーザーが与えた関数をシーケンスにマッピングし、その結果をMonoidを使って結合する
import cats.instances.int._
import cats.instances.string._

Foldable[List].combineAll(List(1, 2, 3))
// 6

Foldable[List].foldMap(List(1, 2, 3))(_.toString)
// "123"

Traverse

Traverse型クラスはApplicativeを活用して、より便利で法則性のある反復処理のパターンを提供する上位のツールです。

CatsのTraverseの定義

trait Traverse[F[_]] extends Functor[F] with Foldable[F] with UnorderedTraverse[F] { self =>
  
  def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]]

  def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]] =
    traverse(fga)(ga => ga)
}

CatsはList,Vector,Stream,Option,Either, その他の型のためのTraverseのインスタンスを提供する。

import cats.Traverse
import cats.instances.future._
import cats.instances.list._

import scala.concurrent.{Await, Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.DurationInt

object Playground2 extends App {

  val hostnames = List("google.com", "facebook.com", "instagram.com")

  def getUptime(hostname: String): Future[Int] =
    Future(hostname.length * 60)

  val totalUptime: Future[List[Int]] =
    Traverse[List].traverse(hostnames)(getUptime)

  println(Await.result(totalUptime, 1.second))

  val numbers = List(Future(1), Future(2), Future(3))

  val numbers2: Future[List[Int]] =
    Traverse[List].sequence(numbers)

  println(Await.result(numbers2, 1.second))
}

Semigroupal and Applicative

今日も今日とてScala with Catsを読んでいく。

Semigroupal and Applicative

  • Semigroupal
    • コンテキストのペアを構成する概念を内包する
    • CatsはSemigroupalFunctorを利用して、複数の引数を持つ関数のシーケンスを可能にするcats.syntax.applyモジュールを提供する
  • Parallel
    • Parallelはモナドインスタンスを持つ型をSemigroupalインスタンスと関連する型に変換する
  • Applicative
    • ApplicativeSemigroupalFunctorを継承する
    • コンテキスト内のパラメータに関数を適用する方法を提供する

Semigroupal

cats.Semigroupalはコンテキストを組み合わせることができる型クラスである。

もし、F[A]F[B]という2つの型のオブジェクトがあったら、Semigroupa[F]F[(A, B)]のような形に組み合わせることできる。

trait Semigroupal[F[_]] {
  def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]
}

Optionを例として以下の実行結果を見てみると分かるように、両方の値がSomeの場合はタプルで値を返すが、一方がNoneの場合は結果はNoneとなる。

println(Semigroupal[Option].product(Some("abc"), Some(123)))
// Some((abc,123))
println(Semigroupal[Option].product(Some("abc"), None))
// None

Semigroupal Laws

Semigroupalの法則は一つで、productメソッドが結合法則を満たしていることである。

product(a, product(b, c) == product(product(a, b), c)

Apply Syntax

CatsはSemigroupalのメソッドショートハンドであるtupledmapNなどの便利なapply syntaxを提供している。

Semigroupal Applied to Different Types

Semigroupalはいつも期待するような振る舞いはしない。特にMonadインスタンスも持つ型だ。

Future

import cats.syntax.apply._

import scala.concurrent.{Await, Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.DurationInt


case class Cat(name: String, yearOfBirth: Int, favoriteFoods: List[String])

val futureCat =  (Future("Garfield"), Future(1978), Future(List("Lasagne"))).mapN(Cat)

println(Await.result(futureCat, 1.second))
// Cat(Garfield,1978,List(Lasagne))

List

SemigroupalのListを組み合わせた場合、いくつか期待しない結果になる。

println(Semigroupal[List].product(List(1, 2), List(3, 4)))
// List((1,3), (1,4), (2,3), (2,4))

Either

Eitherにproductメソッドを適用するとエラーが累積されることを期待するが、結果を見てみると分かるように最初のエラーのみが結果として返されている。

type ErrorOr[A] = Either[Vector[String], A]

println(
  Semigroupal[ErrorOr]
    .product(Left(Vector("Error 1")), Left(Vector("Error 2")))
)
// Left(Vector(Error 1))

Semigroupal Applied to Monads

ListやEitherに対するproductメソッドの結果が期待したものと違っている理由は、それらがモナドであるから。

モナドの場合は、productメソッドの実装は次のようになる。

import cats.Monad
import cats.syntax.functor._
import cats.syntax.flatMap._

def product[F[_]: MOnad, A, B](fa: F[A], fb: F[B]): F[(A, B)] =
  fa.flatMap(a => fb.map(b => (a, b)))

productメソッドの実装によって異なるセマンティクスをもつのはとても奇妙なことだ。

そこで、Catsのモナド(Semigroupalを拡張したもの)は上記のような標準実装を提供している。

Parallel

Paralell型とそれらに関連する構文によって、ある種のモナドの代替的なセマンティクスにアクセスすることができる。

import cats.Semigroupal
import cats.syntax.parallel._
import cats.syntax.apply._

type ErrorOr[A] = Either[Vector[String], A]

val error1: ErrorOr[Int] = Left(Vector("Error 1"))
val error2: ErrorOr[Int] = Left(Vector("Error 2"))

println(Semigroupal[ErrorOr].product(error1, error2))
// Left(Vector(Error 1))

println((error1, error2).tupled)
// Left(Vector(Error 1))

println((error1, error2).parTupled)
// Left(Vector(Error 1, Error 2))

parTupledを使うと両方のエラーが返却されるのが分かる。

なぜこのような振る舞いになるのかParallelの定義を見てみる。

trait Parallel[M[_]] {
  type F[_]

  def applicative: Applicative[F]
  def monad: Monad[M]
  def parallel: ~>[M, F]
  • M型のモナドインスタンスがある
  • 関連するFの型コンストラクタがあり、それはApplicativeインスタンスを持っている
  • MからFに変換できる

※ 型コンストラクタ(type-constructor)は、型を引数に取って新しい型を作るもの

~>というのはFunctionKのエイリアスで、~>[M, F]はMからFに変換するということを表す。

FuntionK M ~> F は、M[A] 型の値から F[A] 型の値への関数である。

import cats.arrow.FunctionK

object optionToList extends FunctionK[Option, List] {
  def apply[A](fa: Option[A]): List[A] =
    fa match {
      case None => List.empty[A]
      case Some(a) => List(a)
   }
}

optionToList(Some(1))
// List(1)

optionToList(None)
// List()

Apply and Applicative

Catsは2つの型クラスを用いてApplicativeをモデル化する。

1つ目はcats.Applyで、これはSemigroupalFunctorを継承しており、コンテキスト内の関数にパラメータを適用するapメソッドを追加したもの。

2つ目は、cats.Applicativeで、これはApplyを継承し、pureメソッドを追加したもの。

trait Apply[F[_]] extends Semigroupal[F] with Functor[F] {
  def ap[A, B](ff: F[A => B](fa: F[A]): F[B]

  def product[A, B](fa: F[F], fb: F[B]): F[(A, B)] =
    ap(map(fa)(a => (b: B) => (a, b)))(fb)
}

trait Applicative[F[_]] extends Apply[F] {
  def pure[A](a: A): F[A]
}

モナドの型クラスの階層

Monda Transformers

引き続き、Scala with Catsを読んでいく。

Monad Transformers

Monadはネスト化されたfor-comprehensionsによってコードを肥大化させる可能性がある。

Exercise: Composing Monads

M1, M2というモナドを合成したComposed[A]という型のflatMapを実装できるかという問題。

import cats.Monad
import cats.syntax.applicative._

object Exercise5_1 extends App {

  def compose[M1[_]: Monad, M2[_]: Monad] = {
    type Composed[A] = M1[M2[A]]

    new Monad[Composed] {
      def pure[A](a: A): Composed[A] =
        a.pure[M2].pure[M1]

      def flatMap[A, B](fa: Composed[A])(f: A => Composed[B]): Composed[B] = ???
    }
  }
}

M1, M2について知らずにflatMapの一般的な定義を書くことは不可能である。

しかし、どちらかのモナドについて知っていれば、上記のコードを書くことができる。

たとえば、M2がOptionだとすると、flatMapの定義はこうなる。

def flatMap[A, B](fa: Composed[A])(f: A => Composed[B]): Composed[B] =
  fa.flatMap(_.fold[Composed[B]](None.pure[M1])(f))

上記の定義で出てくるNoneというのはOption固有の概念で、一般的なモナドには出てこない。

Optionを他のモナドと合成するためにより詳細な情報が必要になる。

他のモナドについても同様で、flatMapメソッドを書くのに役に立つ。これはモナドトランスフォーマーの背景にあるアイデアである。

Catsは様々なモナドのためにトランスフォーマーを定義し、それぞれが他のモナドと合成するために必要な追加の情報を提供している。

A Transformative Example

Catsではたくさんのモナドトランスフォーマーを提供しており、それらの名前のサフィックスにはTという名前がつけられている。(e.g. EitherT)

EitherTはEitherと他のモナドを合成し、OptionTはOptionと他のモナドを合成する。

Monad Tranformers in Cats

各モナドトランスフォーマーはデータ型でcats.dataに定義されており、モナドのスタックをラップして新しいモナドを生成することが可能。

Summary

モナドトランスフォーマーの型シグネチャは内から外に向かって書かれている。

例えば、EitherT[Option, String, A]というシグネチャの場合はOption[Either[String, A]のラッパーとなる。

参考資料

MonadTransformer とは何か · GitHub

everpeace.hatenadiary.org

Monads

Scala with Catsを読んでいく

What is a Monad?

ざっくり言うと、MonadはコンストラクタとflatMapメソッドを持つもの。

Option[A] flatMap (A => Option[B]) => Option[B]

すべてのMonadFunctorでもある。

flatMapmapメソッドを持っていれば、for-comprehension(For式)を使うことができる。

Monad Laws

  • Left identity
    • pure(a).flatMap(func) == func(a)
  • Right identity
    • m.flatMap(pure) == m
  • Associativity
    • m.flatMap(f).flatMap(g) == m.flatMap(x => f(x).flatMap(g))

ここらへんの圏論用語がいまいち分からない。

  • identity ... 恒等射
  • associativity ... 結合律

Monads in Cats

cats.Monadは2つの型クラスを継承している。

  • FlatMap type class
    • flatMapメソッドを提供している
  • Applicative
    • pureメソッドを提供している
    • ApplicativeはFunctorを継承しているため、すべてのMonadでmapメソッドを使うことができる

Error Handling

プログラムで発生する可能性のあるエラーを表す代数的データ型を用いたアプローチ

sealed trait LoginError extends Product with Serializeable

final case class UserNotFound(username: String) extends LoginError

final case class PasswordIncorrect(username: String) extends LoginError

case object UnexpectedError extends LoginError

case class User(username: String, password: String)

type LoginResult = Either[LoginError, User]

def handleError(error: LoginError): Unit =
  error match {
    case UserNotFound(u) => println(s"User not found: $u")
    case PasswordIncorrect(u) => println(s"Password Incorrect: $u")
    case UnexpectedError => println(s"Unexpected error")
  }

Aside: Error Handling and MonadError

CatsはMonadErrorと呼ばれるエラーハンドリングのために使われるEitherのような抽象化したデータ型を提供する。

package cats

trait MonadError[F[_], E] extends ApplicativeError[F, E] with Monad[F] {

   def raiseError[A](e: E): F[A]

  def handleErrorWith[A](fa: F[A])(f: E => F[A]): F[A]

  def handleError[A](fa: F[A])(f: E => A): F[A]

  def ensure[A](fa: F[A])(f: A => Boolean): F[A]
}
  • F[_] はMonad型
  • EはFに含まれるエラー型

MonadErrorの重要なメソッドは以下の2つ

  • raiseError
  • handleError

raiseErrorはMonadでいうpureメソッドに近い。

handleErrorWithraiseErrorを補足する。

The Eval Monad

cats.Eval は、評価の様々なモデルを抽象化するためのMonadである。

典型的な2つはeagerlazyでそれぞれcall-by-valuecall-by-nameとも呼ばれる。

Evalはまた結果をメモすることができ、call-by-needで評価もできる。

ja.wikipedia.org

また、Evalはスタックセーフなのでとても深い再帰処理でもスタックを開放することなく使用できる

評価時の挙動

call-by-value evaluation

val x = {
  println("Computing X")
  math.random
}
// Computing X
// val x: Double = 0.0056134621561709785

x // first access
// val res0: Double = 0.0056134621561709785 // first access

x // second access
// val res1: Double = 0.0056134621561709785
  • 定義した時点(eager)で計算が評価される
  • 計算は一度だけ評価されメモ化される

call-by-name evaluation

def y = {
  println("Computation Y")
  math.random
}
// def y: Double

y // first access
// Computation Y
// val res0: Double = 0.6768399768604834

y // second access
// Computation Y
// val res1: Double = 0.1400800525943975
  • 計算は使用した時点(lazy)で評価される
  • 計算は毎回使用されるたびに評価される(メモ化されない)

call-by-need evaluation

lazy val z = {
  println("Computation Z")
  math.random
 }
// lazy val z: Double

z // first access
// Computation Z
// val res0: Double = 0.5971338589578261

z // second access
val res5: Double = 0.5971338589578261
  • 定義時点では評価されず(not eager)、使用した時点で評価される(lazy)
  • 一度評価されると結果はキャッシュされる(memoized)

Eval's Models of Evaluation

Evalには3つのサブタイプがあり、上記で記載した評価時の挙動と一致している。

  • Now(call-by-value)
  • Always(call-by-name)
  • Later(call-by-need)
Scala Cats Properties
val Now eager, memoized
def Always lazy, not memoized
lazy Later lazy, memoized

The Writer Monad

cats.data.Writerは計算と一緒にログを運ぶことができるMonadである。

Writer[W, A]は2つの型を運ぶことができる。

Wはログの型でAが結果の型を表す。

The Reader Monad

cats.data.Readerは入力に依存する操作を連続させることができるMonadである。

Readerのインスタンスは引数一つの関数をラップし、それらを合成するための便利なメソッドを提供する。

import cats.data.Reader

final case class Cat(name: String, favoriteFood: String)

val catName: Reader[Cat, String] = Reader(cat => cat.name)

catName.run(Cat("Steve", "tuna"))
// res1: cats.ppackage.Id[String] = "Steve"

The State Monad

cats.data.Stateは計算の一部として追加のステートを渡すことができる。

アトミックな状態操作を表すStateを定義し、mapやflatMapを使ってそれらを繋ぎ合わせる。

State[S, A]のインスタンスは S => (S, A)の型を持つ関数を表す。

SがStateの型で、Aが結果の型。

import cats.data.State

val a = State[Int, String] { state =>
  (state, s"The state is $state")
}

val (state, result) = a.run(10).value
// state: Int = 10
// result: String = "The state is 10"

val justTheState = a.runS(10).value
// justTheState: Int = 10

val justTheResult = a.runA(10).value
// justTheResult: String = "The state is 10"

Stateの特徴は2つ

  • inputの状態をoutputの状態に変化させる
  • 結果を計算する

Stateは3つのメソッドを提供する

  • run
  • runS
  • runA

Define Custom Monads

以下の3つのメソッドを実装すればカスタムタイプのMonadを定義できる。

  • flatMap
  • pure
  • tailRecM

tailRecMメソッドはCatsで使われる最適化で、flatMapをネストして呼び出したことで消費されるスタックのスペースを制限するために使われる。

Monadに関する参考情報

MonadTransformer とは何か · GitHub

合成できるモナド、モナドが合成できる時 - Milestones to EVERPEACE 〜alius via〜

Functors

引き続き、Scala with Catsを読んでいく。

Functors

Functorとはざっくり言うと、map メソッドを持ったものと言える。

通常、Listsの反復処理でmapメソッドに出会うが、Functorを理解するには別の視点で考える必要がある。

Listsを走査する」と考えるのではなく、「すべての値を一度に変換する」と考えるべきである。

ある関数を適用するとすべての値に適用され値は変化するがListsの構造は保ったままである。

List(1, 2, 3).map(n => n + 1)
// List(2, 3, 4)

Future

Futureは非同期計算をキューイングしてシーケンス化し、前の計算が完了した時点で適用するFunctorである。

Futureは内部の状態については保証せず、Futureでラップされた計算は、ongoing(進行中), complete(完了), rejected(拒否)で表される。

もし、Futureが完了した場合は、マッピングされたカンスは即座に呼ばれる。もしそうでない場合は、下位のスレッドプールが関数の呼び出しをキューに入れて後で呼び出される。

Futureにおいて、「関数がいつ呼び出されるかは分からない」が、「関数がどの順序で呼び出される」かは分かる。

Note: Futureはreferentially transparent(参照透過性)でない 。

Futureは常に計算結果をキャッシュするため、これらの動作を調整することはできない。

Definition of a Functor

Functorの型は F[A]で、map操作は (A => B) => F[B]

Functor Laws

Functorは、多くの小さな操作を一つずつ順番に行う場合でも、それらを組み合わせて大きな関数にしてからマッピングする場合でも同じセマンティクスを保証する。

Identity: identity関数でmapを呼び出しても、何もしていない場合と同じである

fa.map(a => a) == fa

Composition: fgという2つの関数をマッピングすることはfという関数をマッピングしてからgという関数をマッピングするのと同じである

fa.map(g(f(_))) == fa.map(f).map(g)

Functors in Cats

Functorの型クラスは cats.Functorである。

Functorはliftというメソッドを提供していて、これは A => B という型の関数をFunctor上で操作し、F[A] => F[B]という型に変換する関数。

val func = (x: Int) => x + 1
val liftedFunc = Functor[Option].lift(func)
liftedFunc(Option(1))
// Some(2)

Exercise: Branching out with Functors

2分木型のFunctorを書け、という問題。

以下は、2分木の定義。

sealed trait Tree[+A]
final case class Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A]
final case class Leaf[A](value: A) extends Tree[A]

Functorの実装はすぐ書けた。

  
  implicit def binaryTreeFunctor: Functor[Tree] = new Functor[Tree] {
    override def map[A, B](fa: Tree[A])(f: A => B): Tree[B] =
      fa match {
        case Branch(left, right) => Branch(map(left)(f), map(right)(f))
        case Leaf(value) => Leaf(f(value))
      }
  }

これを実行してみようと以下のように書いてみると、Cannot resolve symbol map だと怒られた。

  println(Branch(Leaf(1), Branch(Leaf(2), Leaf(3))).map(_ * 2))

Cats.Functorの定義を見てみると以下のようになっており、Invariant[F]を継承している。

trait Functor[F[_]] extends Invariant[F] { self =>
  def map[A, B](fa: F[A])(f: A => B): F[B]
  ...
}

ということは、変位指定は不変なので、Tree型を継承したBranch型やLeaf型は渡せない。

なので、Treeオブジェクトを作成して、Branch型とLeaf型のコンストラクターを作成する。

object Tree {
  def branch[A](left: Tree[A], right: Tree[A]): Tree[A] =
    Branch(left, right)

  def leaf[A](value: A): Tree[A] = Leaf(value)
}

これらのコンストラクターの返り値は Tree[A] なので、怒られずに実行できるはず。

println(Tree.branch(Tree.leaf(1), Tree.branch(Tree.leaf(2), Tree.leaf(3))).map(_ * 2))
// Branch(Leaf(2),Branch(Leaf(4),Leaf(6)))

実行できた。

Contravariant Functors and the contramap Method

Contravariant Functorcontramapというメソッドを提供する。

このcontramapメソッドは変換を表すデータ型にのみ意味がある。

F[B] contramap (A => B) => F[A]

Invariant functors and the imap method

Invariant Functorimapメソッドを実装しており、これはざっくり言うとmapcontramapの組み合わせと同じ。

F[A] imap (A => B, B => A) => F[B]

Monoids and Semigroups

お久しぶりです。一週間ちょっと前にコロナに感染してから常に身体が怠くて頭も重いから仕事でもプライベートでも何もやる気になりません。

ただ、このままずっとベッドの上で寝ていても良くないので、Scala with Catsを読み始めました。

www.scalawithcats.com

備忘録としてまとめておきます。

Monoid

型AのMonoidは以下の特性?を持ちます。

  • (A, A) => A の組み合わせの操作: combine
  • Aの空要素: empty

Scalaのコードで表すと以下の通り。

trait Monoid[A] {
  def combile(x: A, y: A): A
  def empty: A
}

Monoidにはいくつかの法則があります。

  • combineAssociative でなければならない
  • emptyIdentity Element でなければならない

Associative / Identity Element とは、具体的には以下のような法則です。

def associativeLaw[A](x: A, y: A, z: A)(implicit m: Monoid[A]): Boolean = {
  m.combine(x, m.combine(y, z)) == m.combine(m.combine(x, y), z)
}

def identityLaw[A](x: A)(implicit m: Monoid[A]): Boolean = {
  (m.combine(x, m.empty) == x) && (m.combine(m.empty, x) == x)
}

Semigroup

Semigroup(圏論で半群と呼ぶらしい)は、Monoidの結合部分(combine)のことを指します。

CatsのMonoidは以下のように定義されています。

trait Semigroup[A] {
  def combine(x: A, y: A): A
}

trait Monoid[A] extends Semigroup[A] {
  def empty: A
}

Duolingo English Testを受けてみた

会社で英語を使う必要が出てきたり、もっと洋書をスラスラ読みたい、英語圏で働くことも視野に入れられるようにしたいというモチベーションが出てきたので、本腰入れて英語学習をし始めました。

そこでまずは自分の実力はどれぐらいなのかを測定する必要があると思い、今回初めて「Duolingo English Test」を受けてみました。

このテストは語学学習サービスを開発しているDuolingoが開発したもので、多くの海外の大学やカレッジ、高校の入学選考書類として受け付けられるほどしっかりとしたものなので、気になっている人がいればぜひ試してもらいたいです!

englishtest.duolingo.com

何が良い?

お手軽

とにかくオンラインで手軽にいつでもテストを受けられるのが良いです!

TOEICだと申し込みの受付日程が決まっているし、実際に受験会場に足を運ばないといけないのがかなり億劫です。

でも、Duolingo English Testならオンラインでチケットを買って、いつでもテストを受けられるのですごく楽なのです!

テストの所要時間が短い

Duolingo English Testなら約60分程度で終わります。

これなら平日でも休日でもサクッと受けることができるので、休日が半日テストで潰れるなんてことがないのが嬉しいところです!

実践的な英語力を測れる

Reading, Listening, Writing, Speakingすべてが求められる問題になっています。

画像について英単語50語以上で説明、あることに対してのあなたは賛成するか反対するかの意見をスピーキングする等、個人的にこういうテストはあまり受けたことがなかったので新鮮でした!

感想

スコアは90でギリギリ中上級なんですが、見てもらうと分かるようにProduction(書いて話す能力:発信力)が著しく低いです。

今後は英語を書く・話すことを重点的に勉強しつつ、スコアを120ぐらいまで伸ばせるように毎日勉強していこうと思います。

certs.duolingo.com

blog.duolingo.com

転職して3ヶ月経った

2022年1月に今いる会社に転職して、もう3ヶ月経った。

今いる部署は技術研究をもとにプロダクトを開発することが多いので、暇なときは暇で忙しいときは忙しいらしい。

が、最初の2週間ぐらいは様子見で細かいタスクを振ってもらったが、それ以降すぐに既存アプリケーションのリアーキテクトプロジェクトに参加させてもらえたり、現在はそれと並行で新しいプロジェクトの管理画面を全部任せるね!って言ってもらえたりでやることが多いのでめちゃめちゃ楽しい!

既存アプリケーションに関しては、性能の事情から部分的にRustを使ってみようか!ということになり、CQRSアーキテクチャパターンで作られたアプリケーションのReadModelUpdater部分をRustで実装するタスクを任せてもらった。

Rustに関してはチュートリアルを軽くやったことしかなかったので、最初はとにかく書いてもコンパイルが通らない、どうやって書けばコンパイルが通るのか本当に分からずで苦労した(今もすんなりコンパイルは通せないから何回も書き直したりするけど、前よりマシだと思う)

また、非同期でメッセージの処理をしたり、Graceful Shutdownの実装のためにtokioを使ってごにょごにょやっていて、これも型の問題でいろいろ苦労した。

あと、そもそものモデリング力不足や実装力の問題で丁寧にレビューもらったりした(上長には感謝しかない🙏)

前職でコードを書いたり本読んで学んでいたつもりだったけど、環境に甘えすぎていた部分があったなと反省している。

そのあとは、Rust製のGraphQLサーバーを一部実装・修正をした。

そもそもRESTful APIしか触ってこなかったので、またここでも学び必須モードになり、ひたすらRustのライブラリであるasync-graphqlを調べたり、そもそもGraphQLってどんなもんなんだというのを調べたりしながら、実装・修正をしていた。

未だにGraphQLに関しては理解は浅いと思うし、エラーハンドリングとかのベストプラクティスってどうなのよ!っていうのはある。

新しいプロジェクトがそろそろ始まるよって段階で、上長とプロジェクト全体像のブレストしたりイベントストーミングしたりドメインイベントの発生源探したり、こういうモデルが必要だよねみたいな話をしたりして、このような取り組みはかなり意味のある時間だし、時間取って付き合ってもらってありがたいなと感謝している。

戦術的DDDで書かれたプロジェクトの機能追加、修正、保守といったことは経験あるけど、一からドメインイベント探してモデリングするみたいな経験は今まで正直なかったので、本当に学びしかないなと思ってやっていた。

ちなみに、新しいプロジェクトで書く言語はGoなのでこれがまた楽しみである!正直、チームで使う言語に強いこだわりはなくて保守・運用が楽になったり、いまチームが抱えている問題を解決できる言語であれば正直何でも良い。それよりも、新しい言語や手法を学ぶコストが〜と言われて選択肢を狭められる方が嫌だから。

とにかくいろいろ雑に書いたけど、新しい環境を楽しめていると思う。そして、チームメンバーも良い人が多いし、やっていき・のっていき精神を持つ人が多いからこういうことを取り入れよう!学ぼう!と言いやすいチームだなと思う。

今の環境は自分があまり触れてこなかった技術に触れる機会が多すぎるので毎日学ぶことが多い。というか学ばないとついていけないんじゃないか?という不安は常にあるけど、それを乗り越えてチームにより貢献できる人になりたいとは思っている。あと大事なことだけど、ちゃんと給料上げてもらいたいから学んでそれを仕事につなげて結果を出していきたいところ(お金はすべてを癒すのさ)

自分もいい歳なので、チームを技術面でもっと引っ張っていけるように技術を研鑽して日々を積み重ねていきたい所存です。