Параметры типа и типы членов в Scala
-
09-09-2019 - |
Вопрос
Я хотел бы знать, как типы членов работают в Scala и как мне следует связывать типы.
Один из подходов — сделать связанный тип параметром типа.Преимущество этого подхода в том, что я могу предписать вариацию типа и быть уверенным, что подтип не изменит тип.Недостатком является то, что я не могу вывести параметр типа из типа функции.
Второй подход заключается в том, чтобы сделать связанный тип членом второго типа, но проблема заключается в том, что я не могу прописать границы связанных типов подтипов и, следовательно, не могу использовать тип в параметрах функции (когда x :X, X#T могут не иметь никакого отношения к x.T)
Конкретным примером может быть:
У меня есть особенность для DFA (может быть и без параметра типа).
trait DFA[S] { /* S is the type of the symbols in the alphabet */
trait State { def next(x : S); }
/* final type Sigma = S */
}
и я хочу создать функцию для запуска этого DFA над входной последовательностью, и я хочу
- функция должна принимать что угодно
<% Seq[alphabet-type-of-the-dfa]
как тип входной последовательности - вызывающему функцию не нужно указывать параметры типа, все они должны быть выведены
- Я бы хотел, чтобы функция вызывалась с конкретным типом DFA (но если есть решение, в котором функция не будет иметь параметр типа для DFA, это нормально).
- типы алфавита не должны быть ограничены (т.должен быть DFA для Char, а также для еще неизвестного пользовательского класса)
- DFA с разными типами алфавита не являются подтипами
Я попробовал это:
def runDFA[S, D <: DFA[S], SQ <% Seq[S]](d : D)(seq : SQ) = ....
это работает, за исключением того, что тип S здесь не выводится, поэтому мне приходится писать весь список параметров типа на каждом сайте вызова.
def runDFA[D <: DFA[S] forSome { type S }, SQ <% Seq[D#Sigma]]( ... same as above
это не сработало (неверная циклическая ссылка на тип D???(что это такое?))
Я также удалил параметр типа, создал абстрактный тип Sigma и попытался связать этот тип с конкретными классами.runDFA будет выглядеть так
def runDFA[D <: DFA, SQ <% Seq[D#Sigma]]( ... same as above
но это неизбежно сталкивается с такими проблемами, как «несоответствие типов:ожидал dfa.Sigma
, получил D#Sigma
"
Есть идеи?Указатели?
Редактировать:
Поскольку ответы показывают, что простого способа сделать это не существует, может ли кто-нибудь подробнее рассказать, почему это невозможно и что нужно изменить, чтобы это работало?
Причина, по которой я хочу, чтобы runDFA ro была бесплатной функцией (а не методом), заключается в том, что мне нужны другие подобные функции, такие как минимизация автоматов, обычные языковые операции, преобразования NFA в DFA, языковая факторизация и т. д.и наличие всего этого внутри одного класса противоречит практически любому принципу объектно-ориентированного проектирования.
Решение
Во-первых, вам не нужна параметризация SQ <% Seq[S].Запишите параметр метода как Seq[S].Если SQ <% Seq[S], то любой его экземпляр неявно преобразуется в Seq[S] (это то, что означает <%), поэтому при передаче как Seq[S] компилятор автоматически вставит преобразование.
Кроме того, то, что Хорхе сказал о параметрах типа в D и превращении его в метод в DFA, остается в силе.Из-за того, как в Scala работают внутренние классы, я бы настоятельно советую поместив runDFA в DFA.Пока механизм типизации, зависящий от пути, не работает, работа с внутренними классами какого-либо внешнего класса может быть довольно болезненной.
Итак, теперь у вас есть
trait DFA[S]{
...
def runDFA(seq : Seq[S]) = ...
}
И с помощью runDFA неожиданно стало довольно легко определить параметры типа для:У него ничего нет.
Другие советы
Вывод типов в Scala иногда оставляет желать лучшего.
Есть ли какая-либо причина, по которой вы не можете использовать этот метод внутри своей черты DFA?
def run[SQ <% Seq[S]](seq: SQ)
Если позже параметр D вам не понадобится, вы также можете попробовать определить свой метод без него:
def runDFA[S, SQ <% Seq[S]](d: DFA[S])(seq: SQ) = ...
Некоторая полезная информация о том, чем они отличаются:
Из бесформенного гид:
Без параметров типа вы не можете создавать зависимые типы, например
trait Generic[A] {
type Repr
def to(value: A): Repr
def from(value: Repr): A
}
import shapeless.Generic
def getRepr[A](value: A)(implicit gen: Generic[A]) =
gen.to(value)
Здесь тип, возвращаемый to
зависит от типа входа A
(поскольку предоставленное неявное значение зависит от A
):
case class Vec(x: Int, y: Int)
case class Rect(origin: Vec, size: Vec)
getRepr(Vec(1, 2))
// res1: shapeless.::[Int,shapeless.::[Int,shapeless.HNil]] = 1 :: 2 ::
HNil
getRepr(Rect(Vec(0, 0), Vec(5, 5)))
// res2: shapeless.::[Vec,shapeless.::[Vec,shapeless.HNil]] = Vec(0,0)
:: Vec(5,5) :: HNil
без членов типа это было бы невозможно:
trait Generic2[A, Repr]
def getRepr2[A, R](value: A)(implicit generic: Generic2[A, R]): R =
???
Нам пришлось бы перенести желаемое значение PERP в GetRepr в качестве параметра типа, что делает getRepr бесполезным.Интуиция от этого является то, что параметры типа полезны, поскольку «входные», а члены типа полезны в качестве «выходов».
пожалуйста, взгляни на бесформенное гид для получения подробной информации.