Skip to content

Instantly share code, notes, and snippets.

@halcat0x15a
Created November 13, 2019 04:39
Show Gist options
  • Save halcat0x15a/f069da664105c184d038823a3f970f11 to your computer and use it in GitHub Desktop.
Save halcat0x15a/f069da664105c184d038823a3f970f11 to your computer and use it in GitHub Desktop.
rpscala第256回
# 値によって変わる型
[@halcat0x15a](https://twitter.com/halcat0x15a)
---
## 今日はなすネタ
面白そうなツイートを見つけた
https://twitter.com/Kory__3/status/1193510317541797889
---
## これはなにか
なにがわからないのか考える
```scala
def groupsLayoutFor(achievementCategory: AchievementCategory)
: Map[Int, AchievementGroupRepr[achievementCategory.type]] =
achievementCategory match {
case BrokenBlock => Map(... -> (BrokenBlockAmount, ...), ...)
case Building => Map()
case Login => Map(... -> (PlayTime, ...), ...)
}
```
---
## Singleton Types
ぱっと見てsingleton typesを使っているところに目がいく
```scala
scala> val a = "hoge"
a: String = hoge
scala> val b: a.type = "hoge"
<console>:12: error: type mismatch;
found : String("hoge")
required: a.type
val b: a.type = "hoge"
^
scala> val b: a.type = a
b: a.type = hoge
```
つまりこれは `achievementCategory` 自体を返す必要がある
---
## なにがやりたいか
推察するに `achievementCategory` に対してパターンマッチした結果の型情報を保ったまま値を返したかったということが考えられる
---
これをScalaでやるには型引数をとるデータ型へのパターンマッチをやる他ない
```scala
sealed abstract class AchievementCategory
case object BrokenBlock extends AchievementCategory
case object Login extends AchievementCategory
sealed abstract class AchievementGroup[+A]
case object BrokenBlockAmount extends AchievementGroup[BrokenBlock.type]
case object PlayTime extends AchievementGroup[Login.type]
def f[A <: AchievementCategory](category: A): AchievementGroup[A] =
category match {
case BrokenBlock => BrokenBlockAmount
case Login => PlayTime
}
```
---
引数で与える値によって型が変わっていることがわかる
```scala
scala> f(BrokenBlock)
res2: AchievementGroup[BrokenBlock.type] = BrokenBlockAmount
scala> f(Login)
res3: AchievementGroup[Login.type] = PlayTime
```
これで本当に嬉しいのかはわからないが実現できた
---
もう少しわかりやすい例を紹介する
```scala
sealed abstract class X[T]
case object I extends X[Int]
case object S extends X[String]
def f[A](x: X[A]): A =
x match {
case I => 1
case S => "hoge"
}
```
`A` を返す関数だがIntやStringを返している
これはパターンマッチによって `A` の型が変わっていることを意味する
---
値によって型が変わっていることがわかる
```scala
scala> f(I)
res0: Int = 1
scala> f(S)
res1: String = hoge
```
---
## GADTs
Scalaのデータ型はGADTsといえる
つまりコンストラクタに型が付けられる
---
## ADTとの違い
例えばHaskellによる定義
```haskell
data Option a = Some a | None
```
普通のADTはこのような定義に対して `Some a :: Option a` `None :: Option a` という型が付く
Scalaはサブタイプもあるので複雑だが、例えば `None` は `Option[Nothing]` という型が付く
---
## 応用
Effで使われる
https://www.slideshare.net/konn/freer-monads-more-extensible-effects-59411772
---
## おまけ
dependent method typesでは実現できないのか
```scala
sealed abstract class AchievementCategory { type Group <: AchievementGroup }
case object BrokenBlock extends AchievementCategory { type Group = BrokenBlockAmount.type }
case object Login extends AchievementCategory { type Group = PlayTime.type }
sealed abstract class AchievementGroup
case object BrokenBlockAmount extends AchievementGroup
case object PlayTime extends AchievementGroup
def f(category: AchievementCategory): category.Group =
category match {
case BrokenBlock => BrokenBlockAmount
case Login => PlayTime
}
```
---
`asInstanceOf` が必要になる
```scala
<pastie>:21: error: type mismatch;
found : BrokenBlockAmount.type
required: category.Group
case BrokenBlock => BrokenBlockAmount
^
```
パターンマッチは型パラメータのunifyしてくれるがtype memberは考慮されない
次期Scalaでは改善されるかもしれない
---
## まとめ
- パターンマッチで型パラメータの型を変えられる
- type memberとパターンマッチは相性が悪い
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment