2014-02-10 2 views
0

Я пытался взломать голову вокруг этого, но я не могу найти решение этой проблемы. Кажется, я не могу правильно моделировать его в Scala.Полиморфное обновление неизменяемых классов

Допустим, у меня есть черта MyTrait с некоторыми непреложными классами, реализующими ее.

И это выглядит примерно так:

trait MyTrait { 
    type Repr <: MyTrait 

    def substitute(original: Item, replacement: Item) : Repr 

    def substituteAll(
    originals: List[Item], 
    replacement: Item 
) : Repr = { 
    originals match { 
     case head :: tail => substitute(head).substituteAll(tail, replacement) 
     case Nil => this //this complains that this is not of type Repr 
    }  
    } 
} 

trait MyTrait2 { ... } 

case class MyClassA(originals: List[Item]) 
extends MyTrait with MyTrait2 { 
    type Repr = MyClassA 

    def substitute(original: Item, replacement: Item) : MyClassA = { 
    //whatever code that updates the list etc. 

    MyClassA(newOriginals) 
    }  
} 

case class MyClassB(originals: List[Item]) 
extends MyTrait with MyTrait2 { 
    type Repr = MyClassB 

    def substitute(original: Item, replacement: Item) : MyClassB = { 
    //whatever code that updates the list etc. 

    MyClassB(newOriginals) 
    }  
} 

case class CompoundClass(list : List[MyTrait with MyTrait2]) 
extends MyTrait { 
    type Repr = CompoundClass 

    def substitute(
    original: Item, 
    replacement: Item 
) : CompoundClass = 
    CompoundClass(list.map(
     myClass => myClass.substitute(original, replacement) 
    )) 
) 
    //the above complains that it is expecting 
    // List[MyTrait with MyTrait2] 
    //while in fact it is getting MyTrait2#Repr 
} 

Если бы я суммировать мои проблемы они будут выглядеть следующим образом:

  • Обновление неизменные классы по методу супер-признака должен возвращать тот же тип реализующего класса.

  • Супер-черта должна быть в состоянии вернуть this, когда имеет смысл, а не возвращать другой объект. У меня, похоже, проблема с этим.

  • Мне нужно, чтобы передать супер-черту в функции, не зная фактического типа. Стандартный полиморфизм. Таким образом, тогда функция может вызывать методы признака без , зная конкретный конкретный тип.

  • Мне нужно иметь возможность использовать композицию для объединения классов в другие. (Представьте выражение, которое сделано из подвыражений). Похоже, стандартный состав, но с вышесказанным я получаю эти ошибки с #Repr

Я первоначально попытался с помощью универсального типа в MyTrait[T], но это делает его невозможно передать какой-то конкретный класс я хочу. Я пытаюсь использовать абстрактные типы сейчас, и я, похоже, сталкиваюсь с той же проблемой во время компиляции. По сути, я думаю, что сейчас нет никакой разницы, я снова падаю в той же ловушке.

Что я делаю неправильно? Я смотрю на это не так?

+0

На первый взгляд, я подозреваю, что некоторые из того, что вы хотите, не представляется возможным. Но найдите «F-Bounded Polymophism» (в Scala) для шаблона, который позволит вам выполнить ваш первый запрос: «Обновление неизменяемых классов с помощью метода супер-trait должно возвращать тот же тип реализующего класса». –

+0

@KevinWright Да, его уточнение моего предыдущего вопроса. У меня все еще возникают трудности с моим сценарием, который более подробно, чем предыдущий, и предыдущий не работает. – jbx

+0

@RandallSchulz Моим первым решением было использование F-Bounded Polymorphism, вот что я намеревался с помощью MyTrait [T] '. Однако я столкнулся с проблемой, что везде, где я ожидал «MyTrait» в качестве аргумента, компилятор начал ожидать какого-то типа для «[T]», и я оказался в полном беспорядке. KevinWright (в предыдущем вопросе) предположил, что вместо этого я использую элемент абстрактного типа, который, как представляется, хорошо работает в небольшом примере, однако на самом деле я даже не мог вернуть 'this' в функции, которые возвращали этот член типа из суперкласса , и передача экземпляра в качестве параметра начала получать '# Repr' – jbx

ответ

1

У вас здесь несколько проблем, некоторые из них совершенно не связаны с полиморфизмом!

Начиная с методом substituteAll:

trait MyTrait { 
    type Repr <: MyTrait 

    def substitute(original: Item, replacement: Item) : Repr 

    def substituteAll(
    originals: List[Item], 
    replacements: List[Item] 
) : Repr = { 
    originals match { 
     case head :: tail => substitute(head).substituteAll(tail) 
     case Nil => this 
    }  
    } 
} 

substitute и substituteAll оба принимают два аргумента, но вы пытаетесь назвать их одним. Это никогда не сработает!

У вас также есть проблема, что у компилятора нет доказательств того, что this является Repr. Первая проблема может быть исправлено достаточно легко с сжать два входа в один список кортежей затем, используя внутреннюю функцию, то второй может быть исправлена ​​путем предоставления доказательств вручную:

trait MyTrait { 
    type Repr <: MyTrait 

    def substitute(original: Item, replacement: Item) : Repr 

    def substituteAll(
    originals: List[Item], 
    replacements: List[Item] 
)(implicit typed: this.type => Repr) : Repr = { 
    def loop(pairs: List[(Item, Item)]): Repr = pairs match { 
     case (orig, rep) :: tail => 
     substitute(orig, rep) 
     loop(tail) 
     case Nil => typed(this) //this complains that this is not of type Repr 
    } 
    loop(originals zip replacements) 
    } 
} 

Ваша следующая проблема в том, что Repr тип param не поддерживает то, что вы хотите, чтобы он удерживался для составного типа MyTrait with MyTrait2.

Это не полный тип, поскольку параметр Repr по-прежнему является абстрактным.То, что вы действительно хотите это полностью заданный тип MyTrait with MyTrait2 { type Repr = MyTrait with MyTrait2 }

Учитывая, что это несколько громоздким, что легче представить еще одну черту, чтобы представить его:

trait CompoundElem extends MyTrait with MyTrait2 { 
    type Repr <: CompoundElem 
} 

Вы можете использовать это в течение всего остального код:

case class MyClassA(originals: List[Item]) extends CompoundElem { 
    type Repr = MyClassA 

    def substitute(original: Item, replacement: Item) : MyClassA = { 
    val newOriginals = originals 
    MyClassA(newOriginals) 
    } 
} 

case class MyClassB(originals: List[Item]) extends CompoundElem { 
    type Repr = MyClassB 

    def substitute(original: Item, replacement: Item) : MyClassB = { 
    val newOriginals = originals 
    MyClassB(newOriginals) 
    } 
} 

case class CompoundClass(list : List[CompoundElem]) extends MyTrait { 
    type Repr = CompoundClass 

    def substitute(
    original: Item, 
    replacement: Item 
) = CompoundClass(
    list.map(_.substitute(original, replacement)) 
) 
} 

Если вы хотите сохранить типы элементов висит круглый немного дольше, этот последний класс также может быть написано:

object MyTrait { 
    //type alias helper to view the type member as though it were a param 
    //A neat trick, shamelessly borrowed from the shapeless library 
    type Aux[R] = MyTrait { type Repr = R } 
} 

case class CompoundClass[E <: MyTrait.Aux[E]](list : List[E]) extends MyTrait { 
    type Repr = CompoundClass[E] 

    def substitute(
    original: Item, 
    replacement: Item 
) = CompoundClass(
    list.map(_.substitute(original, replacement)) 
) 
} 

Написанный таким образом, вам также не нужны промежуточные черты для большинства сценариев. Например, CompoundClass, содержащий список ClassA, будет работать нормально.

Если вы хотите List ассорти ClassA-х и ClassB 'S, то вы все еще будете нуждаться в промежуточных, чтобы помочь компилятору распутать именно то, что Repr должно быть, это не в состоянии подсчитать, что Repr из ClassA with ClassB должен также be ClassA with ClassB.

и если вы не против стирания типов элементов полностью, вы можете сделать это:

case class CompoundClass(list : List[MyTrait]) extends MyTrait { 
    type Repr = CompoundClass  
    def substitute(
    original: Item, 
    replacement: Item 
) = CompoundClass(
    list.map(_.substitute(original, replacement)) 
) 
} 

Это будет только CompoundClass из MyTraits, и вы должны будете использовать поиск по шаблону когда вы тянете элементы для обработки их как ClassA или ClassB или MyTrait2, но для этого вам не понадобятся любые промежуточные черты.


И наконец ... Для справки, вот те же идеи, которые были реализованы с использованием F-Bounds. Обратите внимание, как параметры типа позволяют для self type, так что вам не нужно явно определить Repr во всех ваших подклассов:

trait Item {} 

trait MyTrait { 
    type Repr <: MyTrait 

    def substitute(original: Item, replacement: Item) : Repr 
    def substituteAll(originals: List[Item], replacements: List[Item]) : Repr 
} 

object MyTrait { 
    trait Aux[T <: MyTrait.Aux[T]] extends MyTrait { self: T => 
    type Repr = T 

    def substituteAll(originals: List[Item], replacements: List[Item]) : T = { 
     def loop(pairs: List[(Item, Item)]): Repr = pairs match { 
     case (orig, rep) :: tail => 
      substitute(orig, rep) 
      loop(tail) 
     case Nil => this 
     } 
     loop(originals zip replacements) 
    } 
    } 
} 

trait MyTrait2 { } 

case class MyClassA(originals: List[Item]) extends MyTrait.Aux[MyClassA] with MyTrait2 { 
    def substitute(original: Item, replacement: Item) = MyClassA(originals) 
} 

case class MyClassB(originals: List[Item]) extends MyTrait.Aux[MyClassB] with MyTrait2 { 
    def substitute(original: Item, replacement: Item) = MyClassB(originals) 
} 

case class CompoundClass(list : List[MyTrait]) extends MyTrait.Aux[CompoundClass] { 
    def substitute(
    original: Item, 
    replacement: Item 
) = CompoundClass(
    list.map(_.substitute(original, replacement)) 
) 
} 
+0

Спасибо. Проблема 'substituteAll' была просто опечаткой, когда я абстрагировал свой код на более простом примере, чтобы показать здесь, слишком устал, чтобы заметить ошибку. Вы все равно поняли :). Заглядывая в ваш ответ более подробно ... – jbx

+0

Итак, '(implicit typed: this.type => Repr)' работал блестяще. Я не знал, что могу указать типы там. Тем не менее, я все еще не совсем отношусь к «Repr». Почему «MyTrait» заботится о том, действительно ли конкретный класс реализует другие черты? Я не могу удовлетворить все возможные черты, которые я мог бы реализовать в разных конкретных классах (у меня около 10, и они смешивают и сопоставляют 4 или 5 других признаков, которые не связаны с базовым). Почему я не могу просто сказать, что 'Repr' является' MyClassA', и до тех пор, пока он расширяет 'MyTrait', его не волнует, реализует ли он другие черты? – jbx

+0

Подчеркнул последний пример + абзац - он может помочь вам. –

Смежные вопросы