Skip to content

Instantly share code, notes, and snippets.

@y-yoshinoya
Created April 18, 2025 08:06
Show Gist options
  • Save y-yoshinoya/4db5382cac01a9c5cb28ecab52b58739 to your computer and use it in GitHub Desktop.
Save y-yoshinoya/4db5382cac01a9c5cb28ecab52b58739 to your computer and use it in GitHub Desktop.
Get companion object sample (Scala3)
ThisBuild / scalaVersion := "3.6.4"
package extensions
import scala.quoted.*
trait CompanionProvider[T]:
type CompanionType
def companion: CompanionType
object CompanionProvider:
private def deriveImpl[T: Type](using q: Quotes): Expr[CompanionProvider[T]] =
import q.reflect.*
val tpe = TypeRepr.of[T]
val symbol = tpe.typeSymbol
if !symbol.isClassDef then
report.errorAndAbort(s"Cannot derive CompanionProvider for non-class type ${tpe.show}")
val companionSymbol = symbol.companionModule
if !companionSymbol.flags.is(Flags.Module) then
report.errorAndAbort(s"Cannot find companion object for ${tpe.show}")
val companionTypeRepr = Ref(companionSymbol).tpe // コンパニオンの型 (シングルトン型)
// companionTypeRepr (例: MyService.type) を型パラメータ C として取得
companionTypeRepr.asType match
case '[c] =>
// CompanionProvider[T] { type CompanionType = c; def companion = ??? } を実装する Expr を構築
'{
new CompanionProvider[T]:
type CompanionType = c
// ${ Ref(companionSymbol).asExprOf[c] } でコンパニオンオブジェクトへの参照を埋め込む
def companion: CompanionType = ${ Ref(companionSymbol).asExprOf[c] }
}
case _ =>
report.errorAndAbort(s"Could not resolve companion type for ${tpe.show}")
inline given derived[T]: CompanionProvider[T] = ${ deriveImpl[T] }
end CompanionProvider
trait CompanionInfoProvider {
def getServiceInfo(): String
def defaultPort: Int
}
import extensions._
// BaseService トレイト (CompanionProvider を要求するメソッドを持つ)
trait BaseService { self =>
def serviceType: String
def commonBaseLogic(): String = s"Executing common logic for $serviceType"
// --- ★★★ 型クラスを使用 ★★★ ---
// このメソッドは、呼び出し元の型 T (self の型) に対する
// CompanionProvider[T] の given インスタンスを必要とする
// (コンパイラが CompanionProvider.derived で自動生成してくれる)
def printCompanionAndBaseInfo()(using cp: CompanionProvider[self.type]): Unit = {
println(s"Service Type: $serviceType")
// 型クラス経由でコンパニオンを取得
val comp = cp.companion
// comp の型は cp.CompanionType (例: MyService.type) なので、
// CompanionInfoProvider へのキャスト/マッチは依然として必要
comp match {
case cip: CompanionInfoProvider =>
println(s"Companion Info: ${cip.getServiceInfo()}")
println(s"Companion Port: ${cip.defaultPort}")
case _ =>
println(s"Companion object for $serviceType does not provide CompanionInfoProvider.")
}
println(s"Base Logic: ${commonBaseLogic()}")
}
}
case class MyService(name: String) extends BaseService {
override def serviceType: String = "MyService"
}
object MyService extends CompanionInfoProvider {
override def defaultPort: Int = 8080
override def getServiceInfo(): String = "Companion for MyService"
}
case class AnotherService(id: Int) extends BaseService {
override def serviceType: String = "AnotherService"
}
object AnotherService extends CompanionInfoProvider {
override val defaultPort: Int = 9090
override def getServiceInfo(): String = s"Companion for AnotherService (Config: DefaultConfig123)"
}
@main def runTypeClassExample(): Unit = {
println("--- Testing MyService ---")
val service1 = MyService("Processor")
// printCompanionAndBaseInfo を呼び出すと、コンパイラが裏で
// given CompanionProvider[MyService] = CompanionProvider.derived[MyService] を解決する
service1.printCompanionAndBaseInfo()
println("\n--- Testing AnotherService ---")
val service2 = AnotherService(101)
// 同様に CompanionProvider[AnotherService] が解決される
service2.printCompanionAndBaseInfo()
println("\nType class pattern test successful.")
}
sbt run

--- Testing MyService ---
Service Type: MyService
Companion Info: Companion for MyService
Companion Port: 8080
Base Logic: Executing common logic for MyService

--- Testing AnotherService ---
Service Type: AnotherService
Companion Info: Companion for AnotherService (Config: DefaultConfig123)
Companion Port: 9090
Base Logic: Executing common logic for AnotherService
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment