Один способ, который был предложен для работы с двойными определениями перегруженных методов, состоит в том, чтобы заменить перегрузку сопоставлением с шаблоном:
object Bar {
def foo(xs: Any*) = xs foreach {
case _:String => println("str")
case _:Int => println("int")
case _ => throw new UglyRuntimeException()
}
}
Этот подход требует, чтобы мы отказались от статической проверки типов в аргументах foo
. Было бы намного лучше иметь возможность писать
object Bar {
def foo(xs: (String or Int)*) = xs foreach {
case _: String => println("str")
case _: Int => println("int")
}
}
Я могу приблизиться Either
, но это становится ужасно быстро с более чем двумя типами:
type or[L,R] = Either[L,R]
implicit def l2Or[L,R](l: L): L or R = Left(l)
implicit def r2Or[L,R](r: R): L or R = Right(r)
object Bar {
def foo(xs: (String or Int)*) = xs foreach {
case Left(l) => println("str")
case Right(r) => println("int")
}
}
Похоже , общее (элегантное, эффективное) решение потребовало бы определяющих Either3
, Either4
, .... Кто - нибудь знают альтернативное решения для достижения тех же целей? Насколько мне известно, Scala не имеет встроенной «дизъюнкции типа». Кроме того, неявные преобразования, определенные выше, скрываются где-то в стандартной библиотеке, чтобы я мог просто импортировать их?
class StringOrInt[T]
это сделаноsealed
, то «утечка», на которую вы ссылались («Конечно, это может быть обойдено клиентским кодом путем созданияStringOrInt[Boolean]
»), по крайней мере, еслиStringOrInt
находится в собственном файле. Тогда объекты свидетелей должны быть определены в том же источнике, что иStringOrInt
.Either
подходом заключается в том, что мы теряем большую поддержку компилятора для проверки соответствия.trait StringOrInt ...
StringOrInt[T]
наStringOrInt[-T]
(см. Stackoverflow.com/questions/24387701/… )Майлз Сабин (Miles Sabin) описывает очень хороший способ получить тип объединения в своем недавнем сообщении в блоге « Unboxed Типы объединения» в Scala с помощью изоморфизма Карри-Ховарда :
Сначала он определяет отрицание типов как
используя закон де Моргана, это позволяет ему определять типы профсоюзов
Со следующими вспомогательными конструкциями
Вы можете написать типы объединения следующим образом:
источник
Dotty , новый экспериментальный компилятор Scala, поддерживает объединенные типы (написано
A | B
), так что вы можете делать именно то, что хотели:источник
Вот способ Rex Kerr для кодирования типов объединения. Прямо и просто!
Источник: Комментарий № 27 к этому прекрасному сообщению в блоге Майлза Сабина, который предоставляет другой способ кодирования типов объединений в Scala.
источник
scala> f(9.2: AnyVal)
проходит проверку типов.trait Contra[-A] {}
вместо всех функций ничего. Таким образом, вы получаете такие вещи, какtype Union[A,B] = { type Check[Z] = Contra[Contra[Z]] <:< Contra[Contra[A] with Contra[B]] }
используется какdef f[T: Union[Int, String]#Check](t: T) = t match { case i: Int => i; case s: String => s.length }
(без фантазии Unicode).Можно обобщить решение Даниэля следующим образом:
Основными недостатками этого подхода являются
Either
подход, дальнейшее обобщение потребует определения аналогичноOr3
,Or4
и т.д. черты. Конечно, определить такие черты было бы намного проще, чем определить соответствующиеEither
классы.Обновить:
Митч Блевинс демонстрирует очень похожий подход и показывает, как обобщить его более чем на два типа, назвав его «заиканием или».
источник
Я наткнулся на относительно чистую реализацию n-арных типов объединений, объединив понятие списков типов с упрощением работы Майлза Сабина в этой области , о которой кто-то упоминает в другом ответе.
Данный тип,
¬[-A]
который является контравариантнымA
, по определениюA <: B
мы можем написать¬[B] <: ¬[A]
, инвертируя порядок типов.Данные типы
A
,B
иX
, мы хотим выразитьX <: A || X <: B
. Применяя контравариантность, получаем¬[A] <: ¬[X] || ¬[B] <: ¬[X]
. Это в свою очередь может быть выражено как то,¬[A] with ¬[B] <: ¬[X]
в котором один изA
илиB
должен быть супертипомX
илиX
самим собой (подумайте об аргументах функции).Я потратил некоторое время, пытаясь объединить эту идею с верхней границей для типов элементов, как это видно в разделе
TList
« harrah / up» , однако реализацияMap
с типами границ до сих пор оказалась сложной.источник
Function1
в качестве существующего контравариантного типа. Вам не нужна реализация, все, что вам нужно, это подтверждение соответствия (<:<
).Решение с использованием класса типов, вероятно, является наиболее подходящим способом использования имплицитов. Это похоже на моноидальный подход, упомянутый в книге Одерского / Ложки / Веннерса:
Если вы затем запустите это в REPL:
источник
Either
тип Scala усиливает это убеждение. Использование классов типов через импликации Scala - лучшее решение основной проблемы, но это относительно новая концепция, которая до сих пор не получила широкого распространения, поэтому ФП даже не знал, что рассматривать их как возможную альтернативу объединенному типу.Нам нужен оператор типа,
Or[U,V]
который можно использовать для ограничения параметров типаX
таким образом, чтобы либо либо,X <: U
либоX <: V
. Вот определение, которое подходит как можно ближе:Вот как это используется:
Это использует несколько трюков типа Scala. Основным из них является использование обобщенных типовых ограничений . Для заданных типов
U
иV
компилятор Scala предоставляет класс с именемU <:< V
(и неявный объект этого класса) тогда и только тогда, когда компилятор Scala может доказать, что онU
является подтипомV
. Вот более простой пример использования обобщенных ограничений типов, который работает в некоторых случаях:Этот пример работает, когда
X
экземпляр классаB
,String
или имеет тип, который не является ни супертипом, ни подтипомB
илиString
. В первых двух случаях это верно по определениюwith
ключевого слова,(B with String) <: B
и(B with String) <: String
поэтому Scala предоставит неявный объект, который будет передан какev
: компилятор Scala правильно приметfoo[B]
иfoo[String]
.В последнем случае я полагаюсь на то, что если
U with V <: X
, тоU <: X
илиV <: X
. Это кажется интуитивно верным, и я просто предполагаю это. Из этого предположения ясно, почему этот простой пример не срабатывает, когдаX
является супертипом или подтипом либо,B
либоString
: например, в приведенном выше примереfoo[A]
он неверно принят иfoo[C]
неправильно отклонен. Опять же , что мы хотим , чтобы это своего рода выражение типа на переменныеU
,V
иX
это правда , когда именноX <: U
илиX <: V
.Понятие контравариантности Скалы может помочь здесь. Помните черту
trait Inv[-X]
? Потому что контравариантен в параметре типаX
,Inv[X] <: Inv[Y]
если и только еслиY <: X
. Это означает, что мы можем заменить приведенный выше пример тем, который действительно будет работать:Это потому, что выражение
(Inv[U] with Inv[V]) <: Inv[X]
верно, в соответствии с тем же предположением выше, точно, когдаInv[U] <: Inv[X]
илиInv[V] <: Inv[X]
, и по определению контравариантности, это верно точно, когдаX <: U
илиX <: V
.Можно сделать вещи немного более пригодными для повторного использования, объявив параметризуемый тип
BOrString[X]
и используя его следующим образом:Теперь Scala будет пытаться создать тип
BOrString[X]
для каждогоX
,foo
с кем вызывается, и тип будет создан именно тогда, когда онX
является подтипомB
илиString
. Это работает, и есть сокращенная запись. Синтаксис ниже эквивалентен (за исключением того, чтоev
теперь в теле метода должны указываться ссылки,implicitly[BOrString[X]]
а не простоev
) и используетсяBOrString
в качестве привязки контекста типа :Что нам действительно нужно, так это гибкий способ создания привязки к контексту типа. Контекст типа должен быть параметризуемым типом, и мы хотим, чтобы его можно было параметризировать. Звучит так, как будто мы пытаемся каррировать функции на типах так же, как мы каррируем функции на значениях. Другими словами, мы хотели бы что-то вроде следующего:
Это не возможно напрямую в Scala, но есть хитрость, которую мы можем использовать, чтобы подойти довольно близко. Это подводит нас к определению
Or
выше:Здесь мы используем структурную типизацию и оператор фунта в Scala для создания структурного типа,
Or[U,T]
который гарантированно будет иметь один внутренний тип. Это странный зверь. Чтобы получить некоторый контекст, функцияdef bar[X <: { type Y = Int }](x : X) = {}
должна вызываться с подклассамиAnyRef
, для которых определен типY
:Использование оператора фунта позволяет нам обращаться к внутреннему типу
Or[B, String]#pf
, а используя инфиксную нотацию для оператора типаOr
, мы приходим к нашему первоначальному определениюfoo
:Мы можем использовать тот факт, что типы функций являются контравариантными в параметре первого типа, чтобы избежать определения черты
Inv
:источник
A|B <: A|B|C
проблему? stackoverflow.com/questions/45255270/… Я не могу сказать.Также есть этот хак :
См. Работа с неопределенностью типа стирания (Scala) .
источник
(implicit e: DummyImplicit)
одну из сигнатур типа.Вы можете взглянуть на MetaScala , которая называется
OneOf
. У меня складывается впечатление, что это не очень хорошо работает сmatch
утверждениями, но вы можете имитировать сопоставление, используя функции более высокого порядка. Взгляните , например, на этот фрагмент , но обратите внимание, что часть «симулированного соответствия» закомментирована, возможно, потому что она еще не совсем работает.Теперь для некоторой редакционной публикации: я не думаю, что есть что-то вопиющее в определении Either3, Either4 и т.д., как вы описываете. Это, по сути, двойственно стандартным 22 типам кортежей, встроенным в Scala. Было бы неплохо, если бы в Scala были встроенные дизъюнктивные типы и, возможно, какой-нибудь приятный синтаксис для них, например
{x, y, z}
.источник
Я думаю, что дизъюнктный тип первого класса - это запечатанный супертип с альтернативными подтипами и неявными преобразованиями в / из желаемых типов дизъюнкции к этим альтернативным подтипам.
Я предполагаю, что это относится к комментариям 33 - 36 решения Майлза Сабина, так что это первый тип класса, который можно использовать на сайте использования, но я его не тестировал.
Одной из проблем является то, что Scala не будет использовать в контексте сопоставления случаев неявное преобразование из
IntOfIntOrString
вInt
(иStringOfIntOrString
вString
), поэтому необходимо определить экстракторы и использоватьcase Int(i)
вместоcase i : Int
.ДОБАВИТЬ: Я ответил Майлзу Сабину в его блоге следующим образом. Возможно, есть несколько улучшений по сравнению с Either:
size(Left(2))
илиsize(Right("test"))
.V
вместоOr
, напримерIntVString
, `Int |v| String
`, `Int or String
` или мой любимый `Int|String
`?ОБНОВЛЕНИЕ: логическое отрицание дизъюнкции для вышеупомянутого образца следует, и я добавил альтернативный (и вероятно более полезный) образец в блоге Майлза Сабина .
ДРУГОЕ ОБНОВЛЕНИЕ: Что касается комментариев 23 и 35 решения Mile Sabin , вот способ объявить тип объединения на сайте использования. Обратите внимание, что он распакован после первого уровня, то есть он имеет преимущество, заключающееся в том, что он может быть расширен на любое количество типов в дизъюнкции , тогда как
Either
требует вложенного бокса, и парадигма в моем предыдущем комментарии 41 не была расширяемой. Другими словами, aD[Int ∨ String]
присваивается (то есть является подтипом) aD[Int ∨ String ∨ Double]
.Видимо компилятор Scala имеет три ошибки.
D[¬[Double]]
случай из матча.3.
Метод get не ограничен должным образом типом ввода, потому что компилятор не допустит
A
ковариантную позицию. Кто-то может возразить, что это ошибка, потому что все, что нам нужно, это доказательства, мы никогда не получаем доступ к доказательствам в функции. И я сделал выбор не тест дляcase _
вget
методе, так что я не должен был бы распаковыватьOption
вmatch
вsize()
.05 марта 2012 г .: Предыдущее обновление нуждается в улучшении. Решение Майлза Сабина работало правильно с подтипами.
Предложение моего предыдущего обновления (для почти первоклассного типа объединения) сломало подтип.
Проблема состоит в том, что
A
in(() => A) => A
появляется как в ковариантном (тип возврата), так и в контравариантном (вход функции или в этом случае возвращаемое значение функции, являющейся входом функции), таким образом, подстановки могут быть только инвариантными.Обратите внимание , что
A => Nothing
необходимо только потому , что мы хотим , чтобыA
в контравариантном положении, так что супертипыA
не являются подтипы изD[¬[A]]
ниD[¬[A] with ¬[U]]
( см также ). Поскольку нам нужна только двойная противоположность, мы можем получить эквивалентное решение Майлза, даже если мы можем отбросить¬
и∨
.Так что полное исправление есть.
Обратите внимание, что предыдущие 2 ошибки в Scala остаются, но 3-й ошибки избегают, так как
T
теперь он ограничен подтипомA
.Мы можем подтвердить работы подтипов.
Я думал, что первоклассные типы пересечений очень важны, как по причинам, которые есть у Цейлона , так и потому, что вместо того , чтобы подчиняться,
Any
что означает, что распаковка сmatch
ожидаемыми типами может привести к ошибке времени выполнения, распаковка ( гетерогенной коллекции, содержащей а) дизъюнкция может быть проверена по типу (Scala должна исправить отмеченные мной ошибки). Профсоюзы более простые , чем сложность использования экспериментальной HList из metascala разнородных коллекций.источник
size
функции .size
больше не принимает вD[Any]
качестве ввода.Есть еще один способ, который немного легче понять, если вы не прогуливаете карри-говарда:
Я использую подобную технику в дижоне
источник
Ну, это все очень умно, но я уверен, что вы уже знаете, что ответы на ваши наводящие вопросы - это разные варианты «Нет». Scala обрабатывает перегрузки по-разному и, надо признать, несколько менее элегантно, чем вы описываете. Отчасти это связано с функциональной совместимостью Java, отчасти из-за нежелания попадать в крайние случаи алгоритма вывода типов, а отчасти из-за того, что он просто не является Haskell.
источник
Добавление к уже отличным ответам здесь. Вот суть, которая основывается на типах объединения Майлза Сабина (и идеях Джоша), но также делает их рекурсивно определенными, так что вы можете иметь> 2 типа в объединении (
def foo[A : UNil Or Int Or String Or List[String]
)https://gist.github.com/aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb
NB: Я должен добавить, что после игры с вышеупомянутым для проекта, я вернулся к типам простой старой суммы (то есть запечатанная черта с подклассами). Типы объединения Miles Sabin отлично подходят для ограничения параметра типа, но если вам нужно вернуть тип объединения, он не предлагает много.
источник
A|C <: A|B|C
проблему подтипов? stackoverflow.com/questions/45255270/… Я чувствую себя НЕТ, потому что тогда это будет означать, чтоA or C
это должен быть подтип,(A or B) or C
но он не содержит тип,A or C
поэтому нет надежды сделатьA or C
подтипA or B or C
с этой кодировкой хотя бы .. . что ты думаешь ?Из документов , с добавлением
sealed
:Относительно
sealed
части:источник