#scala
#scala
Вопрос:
Когда я расширяю черты, я могу выбрать, какую реализацию метода использовать. Как здесь:
object Main {
def main(args: Array[String]): Unit = {
val c = new C
println(c.a)
println(c.b)
}
trait Parent {
def foo: String
}
trait A extends Parent {
override def foo = "from A"
}
trait B extends Parent {
override def foo = "from B"
}
class C extends A with B {
val b = super[A].foo
val a = super[B].foo
}
}
Но если я хочу сделать то же самое с самотипами, кажется, что это невозможно:
object Main {
def main(args: Array[String]): Unit = {
val c = new C with A with B
println(c.a)
println(c.b)
}
trait Parent {
def foo: String
}
trait A extends Parent {
override def foo = "from A"
}
trait B extends Parent {
override def foo = "from B"
}
class C {
self: A with B =>
val b = super[A].foo
val a = super[B].foo
}
}
Это не компилируется. Я прав, и это невозможно? Если я прав, почему это так и есть ли обходной путь для этого?
ОБНОВЛЕНИЕ: Зачем мне это нужно в первую очередь? Я играл с внедрением зависимостей, используя собственные типы вместо внедрения конструктора. Итак, у меня был базовый преобразователь признаков и дочерние преобразователи признаков FooConverter и BarConverter. И я хотел написать это именно так (что, конечно, не работает).:
object Main {
class Foo
class Bar
trait Converter[A] {
def convert(a: A): String
}
trait FooConverter extends Converter[Foo] {
override def convert(a: Foo): String = ???
}
trait BarConverter extends Converter[Bar] {
override def convert(a: Bar): String = ???
}
class Service {
this: Converter[Foo] with Converter[Bar] =>
def fooBar(f: Foo, b:Bar) = {
convert(f)
convert(b)
}
}
}
Я думал, что это из-за дженериков, но оказалось, что это не так. Итак, мне просто интересно, можно ли каким-то образом вызвать супер метод выбранного признака с самотипами. Потому что при простом наследовании это возможно. Что касается моей первоначальной проблемы, я могу написать ее так, и она будет работать:
object Main {
class Foo
class Bar
trait Converter[A] {
def convert(a: A): String
}
trait FooConverter extends Converter[Foo] {
override def convert(a: Foo): String = ???
}
trait BarConverter extends Converter[Bar] {
override def convert(a: Bar): String = ???
}
class Service {
this: FooConverter with BarConverter =>
def fooBar(f: Foo, b:Bar) = {
convert(f)
convert(b)
}
}
}
Возможно, более жесткая абстракция, но я не уверен, плохо ли это для такой ситуации и нужна ли мне вообще такая широкая абстракция, как Converter [A] .
Комментарии:
1. Вы правы. Это невозможно. И я не думаю, что есть обходной путь (кроме добавления определенных «foo» к признакам:
def aFoo = "from A"; override def foo = aFoo
2. Зачем вам это нужно?
3. @m-z Я обновил свой вопрос.
Ответ №1:
Вызов super
методов из уже сконструированного типа невозможен (вы можете сделать это только изнутри). В вашем примере вы пытаетесь вызвать foo
экземпляр self
, который создается во время выполнения, поэтому foo
является виртуальным и может быть переопределен — компилятор не знает, какая фактическая реализация будет вызвана (проблема с формальным или реальным типом). Итак, технически невозможно делать то, что вы хотите (вызывать виртуальный метод как статический).
Наивный взлом :
trait CC extends A with B {
val b = super[A].foo
val a = super[B].foo
}
class C {
self: CC =>
}
Он в основном обеспечивает инкапсуляцию, которую вы хотите — возможно, вы захотите переопределить a
и b
в классе C, поскольку они не будут доступны (в самом типе C
), пока вы C
не смешаете CC
с.
Обратите внимание, что в каждом примере, который вы предоставляете (включая мое наивное решение), результирующий val c
имеет доступ в foo
любом случае, и какой именно foo
будет вызываться, зависит от того, как вы смешиваете A
и B
( A with B
или B with A
). Итак, единственная инкапсуляция, которую вы получаете, заключается в том, что C
сам тип не будет иметь foo
метода. Это означает, что самотипирование дает вам своего рода способ временно закрыть (сделать приватным) метод в «подклассе» без нарушения LSP — но это не единственный способ (см. Ниже).
Помимо всего этого, некоторые авторы считают непрактичным внедрение cake-injection, которое вы пытаетесь реализовать. Возможно, вы захотите взглянуть на шаблон Thin Cake — в качестве замечания, я успешно использовал нечто подобное в реальном проекте (в сочетании с внедрением конструктора).
Я бы реализовал ваши сервисы конвертера таким образом:
class Foo
class Bar
trait Converter[A] {
def convert(a: A): String
}
object FooConverter1 extends Converter[Foo] {
override def convert(a: Foo): String = ???
}
object BarConverter1 extends Converter[Bar] {
override def convert(a: Bar): String = ???
}
trait FooBarConvertService {
def fooConverter: Converter[Foo]
def barConverter: Converter[Bar]
def fooBar(f: Foo, b: Bar) = {
fooConverter(f)
barConverter(b)
}
}
trait Converters {
def fooConverter: Converter[Foo] = FooConverter1
def barConverter: Converter[Bar] = BarConverter1
}
object App extends FooBarConvertService with Converters with ...
Это позволяет вам изменять / имитировать реализацию конвертера при объединении всего этого.
Я бы также заметил, что Converter[Bar]
это не что иное, как Function1[Bar, String]
or just Bar => String
, так что на самом деле вам не нужен отдельный интерфейс для этого:
sealed trait FooBar //introduced it just to make types stronger, you can omit it if you prefer
class Foo extends FooBar
class Bar extends FooBar
trait FooBarConvertService {
type Converter[T <: FooBar] = T => String
def fooConverter: Converter[Foo]
def barConverter: Converter[Bar]
def fooBar(f: Foo, b: Bar) = {
fooConverter(f)
barConverter(b)
}
}
trait FooConverterProvider {
def fooConverter: Foo => String = ???
}
trait BarConverterProvider {
def barConverter: Bar => String = ???
}
object App
extends FooBarConvertService
with FooConverterProvider
with BarConverterProvider
Вы также можете использовать def fooConverter(f: Foo): String = ???
вместо def fooConverter: Foo => String = ???
этого.
Говоря об инкапсуляции — здесь она более слабая, поскольку вы можете получить доступ к транзитивным зависимостям, поэтому, если вам это действительно нужно — используйте private[package]
modifier .
Модуль конвертеров:
package converters
trait FooBarConvertService {
type Converter[T <: FooBar] = T => String
private[converters] def fooConverter: Converter[Foo]
private[converters] def barConverter: Converter[Bar]
def fooBar(f: Foo, b: Bar) = {
fooConverter(f)
barConverter(b)
}
}
trait FooConverterProvider {
private[converters] def fooConverter: Foo => String = ???
}
trait BarConverterProvider {
private[converters] def barConverter: Bar => String = ???
}
Основной модуль:
package client
import converters._
object App
extends FooBarConvertService
with FooConverterProvider
with BarConverterProvider
Вы можете использовать объекты object converters {...}; object client {...}
вместо пакетов, если предпочитаете.
Эта инкапсуляция даже сильнее, чем инкапсуляция на основе самотипа, поскольку вы не можете получить доступ fooConverter
к / barConverter
из App
объекта (в вашем примере foo
все еще доступен из val c = new C with A with B
):
client.App.fooBar(new Foo, new Bar) //OK
client.App.fooConverter
<console>:13: error: method fooConverter in trait FooConverterProvider cannot be accessed in object client.App
client.App.fooConverter
^
Комментарии:
1. Большое вам спасибо. Это именно то, что я искал.
Ответ №2:
Имейте в виду, что самотипы предназначены для того, чтобы вы могли требовать, чтобы любой клиентский код, который использует признак, который вы смешиваете, также должен смешиваться с другим признаком. Другими словами, это способ объявления зависимостей. Но это не классическое наследование. Итак, когда вы говорите, что класс C { self: A с B => } A и B на самом деле не существует в данный момент. Вы только что определили, что клиентский код должен смешиваться с A и B, чтобы затем смешиваться с C.
Но для вашего конкретного варианта использования кажется, что вы можете достичь той же цели с помощью чего-то вроде этого кода. Другими словами, сначала создайте третью черту, а затем расширьте ее до определенного класса.
object DoubleSelfType extends App {
val c = new DoubleFoo
println(c.a)
println(c.b)
trait Parent {
def foo: String
}
trait A extends Parent {
override def foo = "from A"
}
trait B extends Parent {
override def foo = "from B"
}
trait C {
self: A with B =>
val a = ""
val b = ""
}
class DoubleFoo extends C with A with B {
override val b = super[A].foo
override val a = super[B].foo
}
}
Комментарии:
1. лучше никогда не переопределять значения val в чертах scalapuzzlers.com/#pzzlr-005
2. Спасибо за ваш ответ. Это здорово, но это не совсем то, что я хотел, потому что я не хочу, чтобы у типа DoubleFoo были общедоступные методы из признаков A и B. Я обновил свой вопрос на случай, если вам интересно, что я пытался сделать.