Вызов конструктора на основе переданного параметра

Построение объекта на основе переданного параметра. Но параметр не String. Я нашел решение, как это сделать с помощью String Scala создает экземпляры объектов из имени класса String но я считаю, что это можно сделать красивее. Скажем, следующие классы:

sealed trait Figure
object Figure {
    final case class Circle(radius: Double) extends Figure
    final case class Square(a: Double) extends Figure
}

И давайте определим функцию (что не имеет смысла), которая принимает параметр, основанный на том, что я могу вызвать правильный конструктор:

val construct123: Figure => Either[String, Figure] = (figure: Figure) => Right(figure.apply(1.23))

я хочу вызвать

construct123(Circle)
//or
construct123(Square)

Это вообще возможно?

Вам нужен класс типов или отражение, ни один из которых я бы не рекомендовал тем, кто изучает язык.

Luis Miguel Mejía Suárez 05.02.2023 00:29

Ваш параметр figure — это не Figure, а скорее Function1[Int, Figure]

Bergi 05.02.2023 02:04
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
103
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проще всего было бы немного изменить подпись construct123

def construct123(figure: Double => Figure): Either[String, Figure] =
  Right(figure(1.23))

construct123(Circle.apply) // Right(Circle(1.23))
construct123(Square.apply) // Right(Square(1.23))
construct123(Circle(_)) // Right(Circle(1.23))
construct123(Square(_)) // Right(Square(1.23))
construct123(Circle) // Right(Circle(1.23))
construct123(Square) // Right(Square(1.23))

Или construct123 можно записать как функцию высшего порядка

val construct123: (Double => Figure) => Either[String, Figure] =
  figure => Right(figure(1.23))

Разница между методом и функцией в Scala


Circle и Square в construct123(Circle) и construct123(Square) не классы case Circle и Square, а их сопутствующие объекты

Объект-компаньон класса против самого класса case

Итак, на самом деле вы хотите преобразовать объект в экземпляр класса-компаньона. Вы можете сделать это, например, с помощью макроса

// libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

def construct123[A](figure: A): Either[String, Figure] = macro construct123Impl[A]

def construct123Impl[A: c.WeakTypeTag](c: blackbox.Context)(figure: c.Tree): c.Tree = {
  import c.universe._
  val companionClass = weakTypeOf[A].companion
  q"_root_.scala.Right.apply(new $companionClass(1.23))" // using constructor of the case class
}

или

def construct123Impl[A: c.WeakTypeTag](c: blackbox.Context)(figure: c.Tree): c.Tree = {
  import c.universe._
  val A = symbolOf[A].asClass.module
  q"_root_.scala.Right.apply($A.apply(1.23))" // using apply method of the companion object
}

Тестирование (в другом подпроекте):

construct123(Circle) // Right(Circle(1.23))
construct123(Square) // Right(Square(1.23))

   // scalacOptions += "-Ymacro-debug-lite"
//scalac: scala.Right.apply(new Macros.Figure.Circle(1.23))
//scalac: scala.Right.apply(new Macros.Figure.Square(1.23))

//scalac: scala.Right.apply(Circle.apply(1.23))
//scalac: scala.Right.apply(Square.apply(1.23))

Вы можете скрыть работу с макросами в классах шрифтов (определяемых с помощью вайтбоксов неявных макросов ). Класс типа ToCompanion ниже аналогичен HasCompanion в Получить сопутствующий объект класса по заданному универсальному типу Scala ( ответ ). Класс типа Generic из Shapeless (используемый ниже для определения construct123) также генерируется макросом. Некоторые вступления к классам шрифтов (обычные, не сгенерированные макросом):

import scala.language.experimental.macros
import scala.reflect.macros.whitebox

// type class
trait ToCompanion[A] {
  type Out
}

object ToCompanion {
  type Aux[A, Out0] = ToCompanion[A] {type Out = Out0}
  // materializer
  def apply[A](implicit tcc: ToCompanion[A]): ToCompanion.Aux[A, tcc.Out] = tcc
  // def apply[A](implicit tcc: ToCompanion[A]): tcc.type = tcc

  // instance of the type class
  implicit def mkToCompanion[A, B]: ToCompanion.Aux[A, B] = macro mkToCompanionImpl[A]
  // implicit def mkToCompanion[A]: ToCompanion[A] = macro mkToCompanionImpl[A] // then implicitly[ToCompanion.Aux[Circle, Circle.type]] doesn't compile in spite of white-boxity

  def mkToCompanionImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
    import c.universe._
    val A = weakTypeOf[A]
    val companion = A.companion
    val ToCompanion = weakTypeOf[ToCompanion[A]]
    q"new $ToCompanion { type Out = $companion }"
  }
}
implicitly[ToCompanion.Aux[Circle, Circle.type]] // compiles
implicitly[ToCompanion.Aux[Circle.type, Circle]] // compiles

val tc = ToCompanion[Circle.type]
implicitly[tc.Out =:= Circle] // compiles
val tc1 = ToCompanion[Circle]
implicitly[tc1.Out =:= Circle.type] // compiles

// libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.10"
import shapeless.{::, Generic, HNil}

def construct123[A, B <: Figure](figure: A)(implicit
  toCompanion: ToCompanion.Aux[A, B],
  generic: Generic.Aux[B, Double :: HNil]
): Either[String, Figure] = Right(generic.from(1.23 :: HNil))

construct123(Circle) // Right(Circle(1.23))
construct123(Square) // Right(Square(1.23))

Поскольку все классы теперь известны во время компиляции, лучше использовать отражение во время компиляции (приведенные выше макросы). Но в принципе отражение во время выполнения тоже можно использовать

import scala.reflect.runtime.{currentMirror => rm}
import scala.reflect.runtime.universe._

def construct123[A: TypeTag](figure: A): Either[String, Figure] = {
  val classSymbol = symbolOf[A].companion.asClass
  //val classSymbol = typeOf[A].companion.typeSymbol.asClass
  val constructorSymbol = typeOf[A].companion.decl(termNames.CONSTRUCTOR).asMethod
  val res = rm.reflectClass(classSymbol).reflectConstructor(constructorSymbol)(1.23).asInstanceOf[Figure]
  Right(res)
}

или

import scala.reflect.ClassTag
import scala.reflect.runtime.{currentMirror => rm}
import scala.reflect.runtime.universe._

def construct123[A: TypeTag : ClassTag](figure: A): Either[String, Figure] = {
  val methodSymbol = typeOf[A].decl(TermName("apply")).asMethod
  val res = rm.reflect(figure).reflectMethod(methodSymbol).apply(1.23).asInstanceOf[Figure]
  Right(res)
}

Или вы можете использовать структурные типы, также известные как утиная типизация (то есть также отражение во время выполнения под капотом).

import scala.language.reflectiveCalls

def construct123(figure: { def apply(x: Double): Figure }): Either[String, Figure] =
  Right(figure(1.23))

Другие вопросы по теме