ひらめの日常

日常のメモをつらつらと

【Scala】Monocleを使ってみたメモ

何の記事か

こちらの『Scala Design Patterns』の11章前半です。

Lensパターンの実装方法の一つとして scalaz.Lens が紹介されていましたが、他に紹介されていた Monocle を使って書き換えてみようというメモです。

Lensパターンとは

Lensパターンは、ネストが深くなっているオブジェクトの一部だけプロパティを書き換えたい、その一方でイミュータブルなオブジェクトを使用したいときなどに使われます。例えば以下のようなオブジェクトで、User から user.company.address.city.country.code を書き換えようとすると大量のコードを書く必要が出てきます。

classDiagram
  class User {
    +name: String
    +company: Company
    +address: Address
  }
  class Company {
    +name: String
    +address: Address
  }
  class Address {
    +number: Int
    +street: String
    +city: City
  }
  class City {
    +name: String
    +country: Country
  }
  class Country {
    +name: String
    +code: String
  }
  
  User *-- Company
  User *-- Address
  Company *-- Address
  Address *-- City
  City *-- Country

例えばこんな感じにプロパティを一つ書き換えただけなのに、 Country を含むオブジェクトを全てcopyして作り直す必要があります。

val userFixed = user.copy(
  company = user.company.copy(
    address  = user.company.address.copy(
      city = user.company.address.city.copy(
        country = user.company.address.city.country.copy(
          code = user.company.address.city.country.code.toUpperCase
        )
      )
    )
  )
)

そこで役に立つのがLensパターンのようです。変更したいプロパティにレンズを通してフォーカスするイメージらしいのですが、具体的なLensパターンの実装自体は未調査です。

scalaz.Lens

本の中でLensパターンを実現しているライブラリとしてまず紹介されているのは、scalaz.Lens です。Lens[A, B]LensFamily[A, A, B, B]エイリアスとなっていました。 LensFamily[A1, A2, B1, B2] は更新前後で型が変わる場合に対応しているのですが、変わらない場合は基本的に Lens[A, B] を使うのが良さそうです。

/**
 * A Lens Family, offering a purely functional means to access and retrieve
 * a field transitioning from type `B1` to type `B2` in a record simultaneously
 * transitioning from type `A1` to type `A2`.  [[scalaz.Lens]] is a convenient
 * alias for when `A1 === A2`, and `B1 === B2`.
 *
 * The term ''field'' should not be interpreted restrictively to mean a member of a class. For example, a lens
 * family can address membership of a `Set`.
 *
 * @see [[scalaz.PLens]]
 *
 * @tparam A1 The initial type of the record
 * @tparam A2 The final type of the record
 * @tparam B1 The initial type of the field
 * @tparam B2 The final type of the field
 */
sealed abstract class LensFamily[A1, A2, B1, B2]
type Lens[A, B] = LensFamily[A, A, B, B]

Lensのgetterとsetterを Lens.lensu(setter, getter) で定義してあげます。lensu の関数定義は以下の通りです。

def lensu[A, B](set: (A, B) => A, get: A => B): Lens[A, B] 

使用する側ではUser -> Company のように、クラス -> クラスのプロパティ という感じで順にアクセスする方法を定義します。

object User {
  val userCompany: Lens[User, Company] = Lens.lensu[User, Company](
    (u, company) => u.copy(company = company),
    _.company
  )

  val userAddress: Lens[User, Address] = Lens.lensu[User, Address](
    (u, address) => u.copy(address = address),
    _.address
  )

  val companyAddress: Lens[Company, Address] = Lens.lensu[Company, Address](
    (c, address) => c.copy(address = address),
    _.address
  )

  val addressCity: Lens[Address, City] = Lens.lensu[Address, City](
    (a, city) => a.copy(city = city),
    _.city
  )

  val cityCountry: Lens[City, Country] = Lens.lensu[City, Country](
    (c, country) => c.copy(country = country),
    _.country
  )

  val countryCode: Lens[Country, String] = Lens.lensu[Country, String](
    (c, code) => c.copy(code = code),
    _.code
  )
}

そして、andThenエイリアスである >=> を使って繋げてます。こうすることで、User から Country.code までたどることができます。

  val userCompanyCountryCode: Lens[User, String] = userCompany >=> companyAddress >=> addressCity >=> cityCountry >=> countryCode

// こっちは compose を使ったもの。表記が逆になるだけでやってることは同じ。
  val userCompanyCountryCodeCompose: Lens[User, String] = countryCode <=< cityCountry <=< addressCity <=< companyAddress <=< userCompany

Monocle

本の中で具体的に紹介されていたのは scalaz.Lens でしたが、より便利なライブラリとしてお勧めされていた Monocle · Access and transform immutable data を使って書き換えてみようと思います。

まず、Monocleはサイトを見ると Scala 2.13 以降に対応しているようなので、サンプルプロジェクトのScalaのバージョンを2.12から上げます。ScalaTestやMockitoなどのプロジェクトに含まれている他のライブラリのバージョンも、Scalaのバージョンに追従して上げる必要がありました。

scalaVersion := "2.13.8"

libraryDependencies ++=  {
  Seq(
    ...
    "dev.optics" %% "monocle-core"  % "3.1.0",
    "dev.optics" %% "monocle-macro" % "3.1.0",
    ...
  )
}

基本的にはサイトに書いてある通りで、以下のような流れになります。

  • foo.focusfoo オブジェクトの持っているプロパティ内部に注目
  • focus の中で指定したプロパティを、 modify によって変更
  • 変更後のオブジェクトを取得

簡単ですね。

  val ivanFixed = ivan.focus(_.company.address.city.country.code).modify(_.toUpperCase)

暗黙的に AppliedFocusOps に変換しているらしいのですが、中身はマクロで書かれていて理解はまだできませんでした。

出力結果を見ると確かに UK と大文字になっていることが確認できます。

User(Ivan,Company(Castle Builders,Address(1,Buckingham Palace Road,City(London,Country(United Kingdom,uk)))),Address(1,Geneva Lake,City(geneva,Country(Switzerland,CH))))
Capitalize UK code...
User(Ivan,Company(Castle Builders,Address(1,Buckingham Palace Road,City(London,Country(United Kingdom,UK)))),Address(1,Geneva Lake,City(geneva,Country(Switzerland,CH))))

【Scala】Optionモナドをcats.Monadを使って書いてみる

何の記事か

Scala Design Patterns で例として挙げられていた Optionモナドを、 cats.Monad を用いて書き換えてみます。以前 Scala Design Patterns のモナドに関する章を噛み砕いで追いました。

hiramekun.hatenablog.com

では cats の Monad を使うとどのように書けるのだろうか?と思い、色々調べながら実験した内容をメモしました。

cats.Monad

cats.Monad の定義は以下の通りです。

trait Monad[F[_]] extends FlatMap[F] with Applicative[F]
  • FlatMap[F] は文字通り flatMap を持つもの
  • Applicative[F]appure を持ち、Functor[F] を継承している。pure以前のブログでまとめたところunit に相当する部分だと理解
  • Functor[F]map を提供していることを以前のブログでもまとめている

これを使って Scala Design Patterns にあるように Option モナドを書き換えてみようと思います。

まずはcats公式のサンプルを見てみます。すると、すでに Applicative[Option] が存在する場合の例が載っていました。

If Applicative is already present and flatten is well-behaved, extending the Applicative to a Monad is trivial.

import cats._

implicit def optionMonad(implicit app: Applicative[Option]) =
  new Monad[Option] {
    // Define flatMap using Option's flatten method
    override def flatMap[A, B](fa: Option[A])(f: A => Option[B]): Option[B] =
      app.map(fa)(f).flatten
    // Reuse this definition from Applicative.
    override def pure[A](a: A): Option[A] = app.pure(a)

    @annotation.tailrec
    def tailRecM[A, B](init: A)(fn: A => Option[Either[A, B]]): Option[B] =
      fn(init) match {
        case None => None
        case Some(Right(b)) => Some(b)
        case Some(Left(a)) => tailRecM(a)(fn)
      }
  }

tailRecM は初めて見る関数ですが、これは cats の主張として「Monad再帰的に処理する場面は多々あり、末尾再帰にしないとスタックオーバーフローが発生するので、Monadを定義する際に強制的に末尾再帰を定義させる」とのことです。

In addition to requiring flatMap and pure, Cats has chosen to require tailRecM which encodes stack safe monadic recursion, as described in Stack Safety for Free by Phil Freeman. Because monadic recursion is so common in functional programming but is not stack safe on the JVM, Cats has chosen to require this method of all monad implementations as opposed to just a subset.

例を見ると、catsのMonadは継承するものではなく引数としてimplicitに与えるイメージのようですね。(なぜ継承ではなく移譲になっているのかは、もう少し理解を深める必要がありそうです。)

書き換えてみる

さて、Applicative の実装は今回のやりたいことから外れるので、Applicative を利用しているところを書き換えていこうと思います。

  • flatMap は値が Some だったらその中身に対して受け取った関数を実行すれば良い。
  • pure は引数を受け取るコンストラクタなので、Some を返せば良い

以上を踏まえると、今回作る MyOption は次のように定義することができます。

implicit def optionMonad: Monad[MyOption] =
  new Monad[MyOption] {
    override def pure[A](x: A): MyOption[A] = MySome(x)
                                                                                                 
    override def flatMap[A, B](fa: MyOption[A])(f: A => MyOption[B]): MyOption[B] = fa match {
      case MySome(value) => f(value)
      case MyNone() => MyNone()
    }
                                                                                                 
    @tailrec
    override def tailRecM[A, B](a: A)(f: A => MyOption[Either[A, B]]): MyOption[B] = f(a) match {
      case MySome(Right(value)) => MySome(value)
      case MySome(Left(value)) => tailRecM(value)(f)
      case MyNone() => MyNone()
    }
  }

次にこれを実際にどのようにして MyOption から使うのかですが、自分は

  • MyOption のコンパニオンオブジェクトに先ほどの optionMonad を implicit に定義し、
  • MyOption のコンストラクタに implicit で渡す

ようにしてみました。

object MyOption {
  implicit def optionMonad: Monad[MyOption] = ...
}
sealed class MyOption[A](implicit val optionMonad: Monad[MyOption]) {
  def flatMap[B](f: A => MyOption[B]): MyOption[B] = optionMonad.flatMap(this)(f)

  def map[B](f: A => B): MyOption[B] = optionMonad.map(this)(f)
}

case class MySome[A](value: A) extends MyOption[A]

case class MyNone[A]() extends MyOption[A]

extends MyOption[A]MyOption[A] のコンストラクタ引数が足りないので、Scalaコンパイラは implicit に定義されたものを探しに行きます。この時に、コンパニオンオブジェクトの中に含まれている implicit 定義もコンパイラの探索対象となります。

変換のソース型か期待されるターゲット型のコンパニオンオブジェクトの中に含まれているimplicit定義も、コンパイラーの探索対象に入る

Scalaスケーラブルプログラミング 第4版, p.415 より引用

IntelliJView: Show Implicit Hints を on にすると、暗黙的に渡されているパラメータを表示することができるのですが、それを使うと、きちんと引数に渡されていることが確認できます。

まとめ

使ってあげるとこんな感じになり、map/flatMap が実装されているので for 式でも書くことができます。

object Main {
  def main(args: Array[String]): Unit = {
    val result = for {
      x <- MySome(1)
      y <- MySome(2)
    } yield {
      x + y
    }
    println(result) // MyOption(3)
  }
}

今回作成したMyOptionのコードをまとめると以下のようになります。

Scala with cats や fpinscala も進めていきたいなあ...)

参考

【因果推論】媒介された影響の排除

こちらの第3章後半です

媒介

ある変数が別の変数に影響を与える場合、直接影響を与える場合と間接的に影響を与える場合とが存在し、この二つを分離することは一般的には難しい。間接的な影響のことを、媒介された影響と呼ぶ。

例として、採用に性別による差別が存在するかどうか調査したいとする。今回は以下の4つの変数が関わっているとし、XからYへの直接的な効果を計測したい。

  • X: 性別
  • Y: 採用
  • Z: 資格
  • I: 収入
f:id:thescript1210:20220410235828p:plain:w500

まず資格について条件付けをする場合を考えてみる。条件付けするというのはつまり、P(採用決定|男性, 資格あり)P(採用決定|女性, 資格あり)を比べるということだ。この二つに差があれば、性別が採用決定に影響を及ぼしていると判断することができる。

しかし、今回のように収入I という交絡因子を含む場合は条件付けによって比べることはできない。なぜなら、資格Z で条件づけることによって、ZYI と通じて従属になってしまうためだ。(資格Z を条件付けするために収入I に影響を及ぼし、収入I が採用Y へと影響を及ぼすため。)

であれば、資格Z について条件付けをする代わりに、資格を固定すれば良い。変数を固定するのは、他の変数がどんな値を取ろうとも、その値に固定されるという意味であり、その変数に向かう矢印を全て削除することと同義である。

介入と固定に関しては過去記事を参照

hiramekun.hatenablog.com

したがって、今回の場合の直接効果、CDE (controlled direct effect) は以下で定義される。

{
CDE = P(Y=採用決定|do(X=男性), do(Z=資格あり)) - P(Y=採用決定|do(X=女性), do(Z=資格あり))
}

より一般的に、ZXY の間の媒介変数である時、X の値を x から x\prime に変化させたときの Y のCDEは

{
CDE = P(Y=y|do(X=x), do(Z=z)) - P(Y=y|do(X=x\prime), do(Z=z))
}

となる。

これは、I について調整することで次のように式変形ができる。

{
\begin{eqnarray}
CDE &=& P(Y=y|X=x, do(Z=z)) - P(Y=y|X=x\prime, do(Z=z)) \\
 &=& \sum_{i}\left[P(Y=y|X=x, Z=z, I=i) - P(Y=y|X=x\prime, Z=z, I=i)\right]P(I=i)
\end{eqnarray}
}

【因果推論】逆確率重み付け法

こちらの第3章後半です

以前効果検証入門でも学習していましたが、表記方法や捉え方が異なっていたため、まとめてみました。

hiramekun.hatenablog.com

前提

逆確率重み付け法とは、バックドア基準やフロントドア基準を満たしている共変量  Z に対して、傾向スコアを持ちいることで効率的に  P(y \mid do(x)) を求める方法である。(下図はバックドア基準を  z が満たしている例)

バックドア基準やフロントドア基準を使えば、実際に介入することなく効果量を計算することができる。このためには、基準を満たす共変量 Z を見つけて、調整化公式を適用すれば良い。

ここで調整化公式を思い出すと、いずれも Z に関して総和を取っている。

これは、Z の取りうる値が大きくなればなるほど計算量は大きくなるということを表している。

傾向スコアと逆確率重み付け法

これを解決するのが、傾向スコア逆確率重み付け法 である。

傾向スコアは g(x, z) = P(X=x|Z=z) で表される関数で、効果検証入門によると以下のような意味を持ち、ロジスティック回帰などで求めることができるという。

傾向スコア とは、各サンプルにおいて介入が行われる確率のこと。傾向スコアを用いた分析は、介入が行われた仕組みに注目し、介入グループと非介入グループのデータの性質を近くするする操作を行う。

『効果検証入門』用語まとめ - ひらめの日常

ベイズの法則より、

{
P(Y=y, Z=z|Z=z) = \frac{P(Y=y, Z=z, X=x)}{P(X=x)}
}

これ合わせて、調整化公式の P(y \mid d o(x))=\sum_{z} P(y \mid x, z) P(z) の分母分子に傾向スコアをかけて式変形をすると、以下が導出される。

{
\begin{eqnarray}
P(y|do(x)) &=& \sum_z \frac{P(Y=y|X=x, Z=z)P(X=x|Z=z)P(Z=z)}{P(X=x|Z=z)} \\
&=& \sum_z \frac{P(X=x, Y=y, Z=z)}{P(X=x|Z=z)}
\end{eqnarray}
}

つまり、母集団のそれぞれのケースの確率を、傾向スコアで割れば良い

※ 『効果検証入門』では確率ではなく期待値を求めていた点などで式の表現が異なっている。

何が嬉しいか

これにより、計算量は調整化公式を用いる時と比べて削減できている。例えば、Zの取りうる値が数百万あったとする。それに対して、得られたデータが数百件だったとする。この時、

  • 調整化公式を用いると、例えばZバックドア基準を満たしていた場合、全てのZに対して  P(y \mid x, z) P(z) を計算する必要がある。
  • 逆確率重み付け法を用いると、観測されていない P(X=x, Y=y,  z=z) は考慮に入れなくて良いため、観測された分のみ計算すれば良くなる。

この辺も後で読みたい

【Scala】Scala Design Patterns - Chapter10 - Monads

はじめに

Scala Design Patterns』 という本を、会社の輪読会で呼んでいるのですが、モナド周りの説明が難しかったので自分なりに日本語で咀嚼してみた内容になります。具体的には、『Scala Design Patterns』の265ページ、Chapter10-Monads の内容です。

ソースコードは出版社のGitHubリポジトリにあります。

github.com

(「モナド何も分からん」から、「モナドちょっと分からん」くらいにはなりました)

Monad の定義

本の中の定義

Monads are functors that have the unit and flatMap methods and follow the monad rules.

ということで、モナドの定義を知るためには Functors とは何かを知る必要がある。

Functorsとは

map メソッドを持ち、以下の決まりに則ったものを呼ぶ。これらを Functor則 と呼ぶことがある。

  • Identity: map(x) (i => i) == x
  • Composition: map(map(x) (i => y(i))) (i => z(i))map(x) (i => z(y(i))) は等しい。
  • map メソッドはデータ構造自体を変更することはない。データの表現を変えるだけ。

実際には以下のようなコードが Functor となる。

trait Functor[F[_]] {
  def map[T, Y](l: F[T])(f: T => Y): F[Y]
}

map メソッドが定義されており、F は保ったまま、その中の TY へと変換している。

これの具体的な実装は、例えば List の場合以下のようになる。

val listFunctor: Functor[List] = new Functor[List] {
  override def map[T, Y](l: List[T])(f: T => Y): List[Y] = l.map(f)
}

Monadとは

Monads are functors that have the unit and flatMap methods and follow the monad rules.

flatMap, unit, map

  • flatMap とは、map した後に flatten すること。
  • unit はコンストラクタと同義。
  • mapflatMapunit を用いて表現することができる。
    • f: T => Y の結果を unit で包んであげて、その後で flatten するイメージ。

前節で取り上げた Functor とは異なり、map などの関数の引数としてデータを受け取るのではなく、Functor自体がデータを保持しているとする。モナドの定義から、flatMapMonad 側に持たせ、map の具体的な実装は Monad 内で flatMapunit を用いて表している。

trait Functor[T] {
  def map[Y](f: T => Y): Functor[Y]
}

trait Monad[T] extends Functor[T] {

  def unit[Y](value: Y): Monad[Y]
  
  def flatMap[Y](f: T => Monad[Y]): Monad[Y]
  
  override def map[Y](f: T => Y): Monad[Y] =
    flatMap(i => unit(f(i)))
}

モナド

  • Identity law: 恒等写像に対して map をしてもデータは変わらない。また、unit メソッドに対して flatMapをしてもデータは変わらない。unit はモノイドで言うところの単位元
    • map(x)(i => i) == x
    • flatMap(x) (i => unit(i)) == x
  • The unit law

    • unit(x).flatMap { y => f(y) } == f(x)
    • unit(x).map { y => f(y) } == unit(f(x))
  • Composition: 複数の mapflatMap は合成することができ、順番によって違いは生まれない。(副作用が生まれる場合はこれが満たされないので注意が必要)

    • x.map(i => y(i)).map(i => z(i))x.map(i => z(y(i))) は等しい。
    • x.flatMap(i => y(i)).flatMap(i => z(i))x.flatMap(i => y(i).flatMap(j => z(j))) は等しい

何が嬉しいのか

状態を隠蔽して、重要な部分のみに注目することができるようになると書いてある。具体例がないと実感できないので、本に載っている具体例を見てみる。

Monads help us hide this state from the user and just expost the important parts as well as abstract the way we deal with errors, and so on.

具体例

Optionモナド

まずは基底クラスである Option を定義する。

sealed trait Option[A] extends Monad[A]

そして、具体的な実装である SomeNone を実装する。

case class Some[A](a: A) extends Option[A] {
  override def unit[Y](value: Y): Monad[Y] = Some(value)
  override def flatMap[Y](f: A => Monad[Y]): Monad[Y] = f(a)
}
case class None[A]() extends Option[A] {
  override def unit[Y](value: Y): Monad[Y] = None()
  override def flatMap[Y](f: (A) => Monad[Y]): Monad[Y] = None()
}

副作用の隠蔽という点では、適していない例のように思う。SomeNone を同じtraitから定義し、map, flatMap が定義してあるおかげで for文で書けるという嬉しさはある。

IOモナド

ファイルから行を読み取り、その全てを大文字にしてあtらしいファイルに書き出すプログラムを考える。「状態」として今回は FileIOState の数値をincrementする。

まずはIOの状態を保持する State を定義する。

sealed trait State {
  def next: State
}

IOAction は、古い State から、新しい State と操作の結果のタプルを返す関数を継承している。unit, map, flatMap を実装しており、モナド則を満たしている。

flatMap では、

  1. 新しい IOAction を返す。
  2. その新しい IOActionapply で古い IOActionapply を呼び出し、実行結果として新しい State と結果のタプルを受け取る。
  3. 受け取った結果を引数とし、 flatMap で受け取った関数 f: T => IOAction[Y] を実行。
  4. 実行した結果の IOAction である action2 に新しい State を渡し、apply を呼び出す。

ここまで全て、flatMap 実行時には呼び出しがされておらず、IOAction#apply が呼び出されて初めて実行されることに注意する。つまり副作用は flatMap 時点では発生していない。

sealed abstract class IOAction[T] extends ((State) => (State, T)) {

  def unit[Y](value: Y): IOAction[Y] = IOAction.unit(value)

  def flatMap[Y](f: (T) => IOAction[Y]): IOAction[Y] = {
    val self = this
    new IOAction[Y] { // 1
      override def apply(state: State): (State, Y) = {
        val (state2, res) = self(state) // 2
        val action2 = f(res) // 3
        action2(state2) // 4
      }
    }
  }

  def map[Y](f: T => Y): IOAction[Y] =
    flatMap(i => unit(f(i)))
}
object IOAction {

  def apply[T](result: => T): IOAction[T] =
    new SimpleAction[T](result)
  
  def unit[T](value: T): IOAction[T] =
    new EmptyAction[T](value)

  // 名前渡しで引数を受け取り、実行時に評価する
  private class SimpleAction[T](result: => T) extends IOAction[T] {
    override def apply(state: State): (State, T) =
      (state.next, result)
  }
  
  // unitを呼び出した時に状態を変えないような実装が必要
  private class EmptyAction[T](value: T) extends IOAction[T] {
    override def apply(state: State): (State, T) = 
      (state, value)
  }
}

具体的な使い方を見る。いずれも IOAction の引数は名前渡しなので、この時点では実行されず、受け取った IOAction に対して apply を呼んだ時に初めて実行される。

package object io {
  def readFile(path: String): IOAction[Iterator[String]] =
    IOAction(Source.fromFile(path).getLines())

  def writeFile(path: String, lines: Iterator[String]): IOAction[Unit] =
    IOAction({
      val file = new File(path)
      printToFile(file) { p => lines.foreach(p.println) }
    })

  private def printToFile(file: File)(writeOp: PrintWriter => Unit): Unit = {
    val writer = new PrintWriter(file)
    try {
      writeOp(writer)
    } finally {
      writer.close()
    }
  }
}

そして、FileIOを実行できるクラスを定義する。State の実装である FileIOState は privateにすることで、外から状態を作れないようにしている。

run でやっていることは、次のような意味になる。

  1. runIOIOAction を定義する。
  2. IOactionapply を、 FileIOStateの初期値を渡して実行する。

action を実行するまで副作用が発生しておらず、副作用を局所化できているのが嬉しい点。

abstract class FileIO {
  
  private class FileIOState(id: Int) extends State { 
    override def next: State = new FileIOState(id + 1)
  }

  def run(args: Array[String]): Unit = {
    val action = runIO(args(0), args(1))
    action(new FileIOState(0))
  }

  def runIO(readPath: String, writePath: String): IOAction[_]
}

実際の使い方としては次の通り。

object FileIOExample extends FileIO {
  
  def main(args: Array[String]): Unit = {
    run(args)
  }
  
  override def runIO(readPath: String, writePath: String): IOAction[_] =
    for {
      lines <- readFile(readPath)
      _ <- writeFile(writePath, lines.map(_.toUpperCase))
    } yield ()
 
  // 以下と同じ
  // override def runIO(readPath: String, writePath: String): IOAction[_] = {
  //   readFile(readPath).flatMap { lines =>
  //     writeFile(writePath, lines.map(_.toUpperCase))
  // }
}

2021年振り返りと2022年のインプット目標

2021年にどんな本を読んだかや、2022年にはどんな本を読みたいかを残しておきます。

一昨年書いたのはこちら。 hiramekun.hatenablog.com

2021年振り返り

去年の目標は24冊だったようです。

24冊読む を目標にやります。現時点で読もうと思っている本を上げておき、随時見直すようにしたいと思います。

途中の本もあるのですが、読み終わった本を数えると実際は 30冊 でした。目標達成ですね、嬉しいです。今年は教育系の本は読書会に参加して読み進めたおかげでコンスタントに消化することができました。他の参加者の方々に感謝です。

その一方で去年後半にだいぶ失速した印象があるので、今年は一年通してモチベーションを高く持ち続けたいですね。

数学/統計

エンジニアリング

教育

その他

2022年の目標

2022年はinputもなのですが、自分の中で知識をまとめ直して消化することを意識していきたいです。なので、以下のことに注力します。

  • これまでに読んできた本の内容をブログにまとめる
  • 仕事で使った知識をアウトプットする

新しい本の目標は7冊程度とします。

数学/統計

エンジニアリング

教育

教育系の本は、読書会で読み進めるものを随時追加するイメージです。

その他

MacOSアップデート後、gccが壊れたので直した

結論

  • MacOS をメジャーアップデートしてたら、CommandLineTools をアップデートしましょう
  • CommandLineTools がアップデートできない場合、以下の手順でインストールし直しましょう

    >> rm -rf /Library/Developer/CommandLineTools
    >> xcode-select --install 
    

環境

  • macOS Big Sur 11.4
  • gccbrewでインストールしていた

経緯

きっかけは競技プログラミングの問題を1年ぶりに解こうとしたとき

  >> g++-11 -D_GLIBXX_DEBUG ./main.cpp
  In file included from /usr/local/Cellar/gcc/11.2.0_3/include/c++/11/x86_64-apple-darwin20/bits/stdc++.h:33,
                   from ./main.cpp:1:
  /usr/local/Cellar/gcc/11.2.0_3/include/c++/11/cassert:44:10: fatal error: assert.h: No such file or directory
     44 | #include <assert.h>
        |          ^~~~~~~~~~
  compilation terminated.

ヘッダーファイルが見つからずコンパイルに失敗していました。

gcc, g++ は想定した通り brew でインストールした先を指しています。

  >> which gcc
  /usr/local/bin/gcc
  >> which g++
  /usr/local/bin/g++

もう少し細かくコンパイラのオプションを見てみます。

  >> g++-11 -v
  Using built-in specs.
  COLLECT_GCC=g++-11
  COLLECT_LTO_WRAPPER=/usr/local/Cellar/gcc/11.2.0_3/bin/../libexec/gcc/x86_64-apple-darwin20/11/lto-wrapper
  Target: x86_64-apple-darwin20
  Configured with: ../configure --prefix=/usr/local/opt/gcc --libdir=/usr/local/opt/gcc/lib/gcc/11 --disable-nls --enable-checking=release --with-gcc-major-version-only --enable-languages=c,c++,objc,obj-c++,fortran,d --program-suffix=-11 --with-gmp=/usr/local/opt/gmp --with-mpfr=/usr/local/opt/mpfr --with-mpc=/usr/local/opt/libmpc --with-isl=/usr/local/opt/isl --with-zstd=/usr/local/opt/zstd --with-pkgversion='Homebrew GCC 11.2.0_3' --with-bugurl=https://github.com/Homebrew/homebrew-core/issues --enable-libphobos --build=x86_64-apple-darwin20 --with-system-zlib --disable-multilib --with-native-system-header-dir=/usr/include --with-sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX11.sdk
  Thread model: posix
  Supported LTO compression algorithms: zlib zstd
  gcc version 11.2.0 (Homebrew GCC 11.2.0_3) 

ここでコマンドラインツールを指定しているようです。
--with-sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX11.sdk 。一応存在しているか見てみます。

  >> ls /Library/Developer/CommandLineTools/SDKs/
  .              ..             MacOSX.sdk     MacOSX10.15.sdk

...ないですね...

ここで思い当たったのが、MacOSのメジャーアップデートです。かなり前にやっていたのですが、CommandLineTools の更新はしていませんでした。しかも、その後に gcc のアップデートをしています。よって、gcc は最新のOSバージョンを見て CommandLineTools を探していたのかと推測されます。その一方、ローカルには古いOSバージョンの CommandLineTools しかなかったため、ヘッダーを見つけることができなかったと思われます。(ここは自分の推測なので、間違っていたらご指摘ください!)

f:id:thescript1210:20211226213608p:plain:w700
これが

f:id:thescript1210:20211226213637p:plain:w700
こうなった

解決

xcode-select --install はすでにインストールされているとの表示。

  >> xcode-select --install
  xcode-select: error: command line tools are already installed, use "Software Update" to install updates

言われた通りsoftwareupdateを見てもコマンドラインツールのアップデートは存在していませんでした。

そこで、無理矢理コマンドラインツール一式を削除し、インストールし直すことを試します。

  >> rm -rf /Library/Developer/CommandLineTools
  >> xcode-select --install
  xcode-select: note: install requested for command line developer tools

このログが出ますがインストールされません。

よくわからないのですが、OSが最新バージョンではないためでしょうか。More - Downloads - Apple Developerから最新版をダウンロードしたところ、無事にコンパイルができました...!

参考

この辺を大変参考にさせていただきました。