Jednym ze sposobów zasugerowania podwójnej definicji przeciążonych metod jest zastąpienie przeciążenia dopasowaniem wzorca:
object Bar {
def foo(xs: Any*) = xs foreach {
case _:String => println("str")
case _:Int => println("int")
case _ => throw new UglyRuntimeException()
}
}
Takie podejście wymaga, abyśmy zrezygnowali ze statycznego sprawdzania typu argumentów dla foo
. O wiele fajniej byłoby pisać
object Bar {
def foo(xs: (String or Int)*) = xs foreach {
case _: String => println("str")
case _: Int => println("int")
}
}
Mogę się zbliżyć Either
, ale robi się brzydko szybko z więcej niż dwoma typami:
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")
}
}
To wygląda jak ogólne (eleganckie, wydajne) rozwiązanie wymagałoby określającą Either3
, Either4
.... Czy ktoś wie alternatywnego rozwiązania dla osiągnięcia tego samego celu? Według mojej wiedzy Scala nie ma wbudowanego „rozróżnienia typów”. Ponadto, czy ukryte konwersje zdefiniowane powyżej czają się gdzieś w standardowej bibliotece, żebym mógł je po prostu zaimportować?
class StringOrInt[T]
tak się staniesealed
, „wyciek”, o którym mówiłeś („Oczywiście kod klienta może utworzyć krok po kroku, tworzącStringOrInt[Boolean]
„), jest zatkany, przynajmniej jeśliStringOrInt
znajduje się we własnym pliku. Następnie obiekty świadka muszą być zdefiniowane w tym samym sosie coStringOrInt
.Either
podejściem wydaje się być to, że tracimy dużo obsługi kompilatora do sprawdzania dopasowania.trait StringOrInt ...
StringOrInt[T]
naStringOrInt[-T]
(patrz stackoverflow.com/questions/24387701/… )Miles Sabin opisuje bardzo fajny sposób na uzyskanie typu związku w swoim niedawnym wpisie na blogu Rozpakowane typy związków w Scali poprzez izomorfizm Curry-Howarda :
Najpierw określa negację typów jako
stosowanie prawa De Morgana pozwala mu definiować typy związków
Z następującymi konstrukcjami pomocniczymi
możesz pisać typy unii w następujący sposób:
źródło
Dotty , nowy eksperymentalny kompilator Scala, obsługuje typy związków (napisane
A | B
), dzięki czemu możesz robić dokładnie to, co chciałeś:źródło
Oto sposób Rex Kerr do kodowania typów unii. Prosto i prosto!
Źródło: Komentarz nr 27 do tego doskonałego posta na blogu autorstwa Milesa Sabina, który zapewnia inny sposób kodowania typów związków w Scali.
źródło
scala> f(9.2: AnyVal)
przechodzi sprawdzanie typu.trait Contra[-A] {}
zamiast wszystkich funkcji do niczego. Otrzymujesz rzeczy takie jaktype Union[A,B] = { type Check[Z] = Contra[Contra[Z]] <:< Contra[Contra[A] with Contra[B]] }
używanedef f[T: Union[Int, String]#Check](t: T) = t match { case i: Int => i; case s: String => s.length }
(bez fantazyjnego Unicode).Możliwe jest uogólnienie rozwiązania Daniela w następujący sposób:
Główne wady tego podejścia to
Either
podejściu dalsze uogólnienie wymaga zdefiniowania analogiczneOr3
,Or4
itp cechy. Oczywiście zdefiniowanie takich cech byłoby znacznie prostsze niż zdefiniowanie odpowiednichEither
klas.Aktualizacja:
Mitch Blevins demonstruje bardzo podobne podejście i pokazuje, jak uogólnić na więcej niż dwa typy, nazywając to „jąkaniem lub”.
źródło
W pewnym sensie natknąłem się na względnie czystą implementację n-arskich typów unii, łącząc pojęcie list typów z uproszczeniem pracy Milesa Sabina w tym obszarze , o czym ktoś wspomina w innej odpowiedzi.
Biorąc pod uwagę typ,
¬[-A]
który jest sprzecznyA
, z definicji podając,A <: B
że możemy pisać¬[B] <: ¬[A]
, odwracając kolejność typów.Podane typy
A
,B
iX
chcemy wyrazićX <: A || X <: B
. Stosujemy kontrowariancję¬[A] <: ¬[X] || ¬[B] <: ¬[X]
. To z kolei może być wyrażone jako to,¬[A] with ¬[B] <: ¬[X]
w którym jednym zA
lubB
musi być nadtypemX
lubX
samym sobą (pomyśl o argumentach funkcji).Spędziłem trochę czasu, próbując połączyć ten pomysł z górną granicą typów elementów, jak widać na
TList
s harrah / up , jednak implementacjaMap
z typami granic okazała się dotychczas trudna.źródło
Function1
jako istniejący typ kontrowariantny. Nie potrzebujesz implementacji, wszystko czego potrzebujesz to dowód zgodności (<:<
).Rozwiązanie klasy typu jest prawdopodobnie najmilszym sposobem na skorzystanie z tej opcji. Jest to podobne do podejścia monoidu wspomnianego w książce Odersky / Spoon / Venners:
Jeśli następnie uruchomisz to w REPL:
źródło
Either
typ Scali wzmacnia to przekonanie. Używanie klas typów za pośrednictwem implikacji Scali jest lepszym rozwiązaniem podstawowego problemu, ale jest to stosunkowo nowa koncepcja i wciąż mało znana, dlatego OP nawet nie wiedział, że uważa je za możliwą alternatywę dla typu unii.Chcielibyśmy operatora typu
Or[U,V]
, które mogą być używane, aby ograniczyć do parametrów typuX
w taki sposób, że alboX <: U
alboX <: V
. Oto definicja, która jest tak bliska, jak to tylko możliwe:Oto jak jest używany:
Wykorzystuje kilka sztuczek typu Scala. Głównym z nich jest zastosowanie uogólnionych ograniczeń typu . Biorąc pod uwagę typy
U
iV
, kompilator Scala zapewnia klasę o nazwieU <:< V
(i niejawny obiekt tej klasy) tylko wtedy, gdy kompilator Scala może udowodnić, żeU
jest to podtypV
. Oto prostszy przykład z uogólnionymi typami ograniczeń, który działa w niektórych przypadkach:Ten przykład działa, gdy
X
instancja klasyB
, aString
lub ma typ, który nie jest ani podtypem, ani podtypem „B
lub”String
. W pierwszych dwóch przypadkach, to prawda przez definicjiwith
słowa kluczowego, które(B with String) <: B
i(B with String) <: String
tak Scala zapewni niejawny obiekt, który zostanie przekazany jakoev
: kompilator Scala będzie poprawnie przyjąćfoo[B]
ifoo[String]
.W ostatnim przypadku polegam na tym, że jeśli
U with V <: X
, toU <: X
lubV <: X
. Wydaje się to intuicyjnie prawdziwe i po prostu to zakładam. Z tego założenia wynika, że ten prosty przykład zawodzi, gdyX
jest nadtypem lub podtypem jednegoB
lubString
: na przykład w powyższym przykładziefoo[A]
jest niepoprawnie zaakceptowany ifoo[C]
niepoprawnie odrzucony. Ponownie, co chcemy jest jakiś rodzaj ekspresji na zmiennychU
,V
iX
to prawda, kiedy dokładnieX <: U
lubX <: V
.Pomysł Scali na sprzeczność może tu pomóc. Pamiętasz tę cechę
trait Inv[-X]
? Ponieważ jest sprzeczny w swoim parametrze typuX
,Inv[X] <: Inv[Y]
wtedy i tylko wtedyY <: X
. Oznacza to, że możemy zastąpić powyższy przykład przykładem, który faktycznie będzie działał:Wynika to z tego, że wyrażenie
(Inv[U] with Inv[V]) <: Inv[X]
jest prawdziwe, przy takim samym założeniu powyżej, dokładnie kiedyInv[U] <: Inv[X]
lubInv[V] <: Inv[X]
, a przez definicję kontrawariancji, jest to prawdziwe dokładnie wtedy, gdyX <: U
lubX <: V
.Można uczynić rzeczy nieco bardziej użytecznymi, deklarując parametr parametryzowalny
BOrString[X]
i używając go w następujący sposób:Scala spróbuje skonstruować typu
BOrString[X]
dla każdegoX
którafoo
jest wywołana, a typ zostanie wykonana dokładnie, gdyX
jest podtypem alboB
czyString
. To działa i istnieje skrótowy zapis. Poniższa składnia jest równoważna (z wyjątkiem tego, żeev
należy się do niej odwoływać w treści metody,implicitly[BOrString[X]]
a nie po prostuev
) i wykorzystujeBOrString
jako kontekst kontekstowy typu :To, co naprawdę chcielibyśmy, to elastyczny sposób na utworzenie powiązania kontekstu typu. Kontekst typu musi być parametryzowalnym typem, a my chcemy parametryzowalnego sposobu jego utworzenia. To brzmi, jakbyśmy próbowali wywoływać funkcje na typach, podobnie jak funkcje na wartościach. Innymi słowy, chcielibyśmy czegoś takiego:
Nie jest to bezpośrednio możliwe w Scali, ale istnieje pewien sposób na zbliżenie się. To prowadzi nas do powyższej definicji
Or
:Tutaj używamy typowania strukturalnego i operatora funta Scali do tworzenia typu strukturalnego,
Or[U,T]
który ma zagwarantowany jeden typ wewnętrzny. To dziwna bestia. Aby podać kontekst,def bar[X <: { type Y = Int }](x : X) = {}
należy wywołać funkcję z podklasami o zdefiniowanymAnyRef
typieY
:Korzystanie z operatora funta pozwala nam odnosić się do typu wewnętrznego
Or[B, String]#pf
, a przy użyciu notacji infix dla operatora typuOr
dochodzimy do naszej oryginalnej definicjifoo
:Możemy wykorzystać fakt, że typy funkcji są sprzeczne w swoim pierwszym parametrze typu, aby uniknąć zdefiniowania cechy
Inv
:źródło
A|B <: A|B|C
problem? stackoverflow.com/questions/45255270/… Nie mogę powiedzieć.Jest też ten hack :
Zobacz Obejście niejednoznaczności usuwania tekstu (Scala) .
źródło
(implicit e: DummyImplicit)
do jednego z podpisów typu.Możesz rzucić okiem na MetaScala , która ma coś o nazwie
OneOf
. Mam wrażenie, że nie działa to dobrze zmatch
instrukcjami, ale można symulować dopasowanie za pomocą funkcji wyższego rzędu. Na przykład spójrz na ten fragment kodu, ale zauważ, że część „symulowanego dopasowywania” jest komentowana, być może dlatego, że jeszcze nie działa.A teraz trochę redakcji: nie wydaje mi się, żeby w opisywaniu Either3, Either4 itd. Było coś rażącego. Jest to zasadniczo podwójne w stosunku do standardowych 22 krotek wbudowanych w Scalę. Byłoby na pewno fajnie, gdyby Scala miał wbudowane typy rozłączne i być może dla nich jakąś fajną składnię
{x, y, z}
.źródło
Myślę, że rozłącznym typem pierwszej klasy jest zapieczętowany nadtyp, z alternatywnymi podtypami i niejawnymi konwersjami do / z pożądanych typów rozłączenia na te alternatywne podtypy.
Zakładam, że odnosi się to do komentarzy 33 - 36 rozwiązania Milesa Sabina, więc jest to typ pierwszej klasy, który można zastosować na stronie użytkowania, ale go nie przetestowałem.
Jednym z problemów jest to, że Scala nie zastosuje w przypadku dopasowania kontekstu, niejawnej konwersji z
IntOfIntOrString
naInt
(iStringOfIntOrString
naString
), więc należy zdefiniować ekstraktory i używaćcase Int(i)
zamiastcase i : Int
.DODAJ: Odpowiedziałem Milesowi Sabinowi na jego blogu w następujący sposób. Być może istnieje kilka ulepszeń w stosunku do któregokolwiek:
size(Left(2))
lubsize(Right("test"))
.V
zamiastOr
np.IntVString
`Int |v| String
`, `Int or String
` Lub mojego ulubionego `Int|String
`?AKTUALIZACJA: Następuje logiczna negacja niezgodności powyższego wzoru, a ja dodałem alternatywny (i prawdopodobnie bardziej użyteczny) wzór na blogu Milesa Sabina .
KOLEJNA AKTUALIZACJA: Jeśli chodzi o komentarze 23 i 35 dotyczące rozwiązania Mile Sabin , oto sposób zadeklarowania typu związku na stronie użytkowania. Zauważ, że jest rozpakowywany po pierwszym poziomie, tj. Ma tę zaletę, że może być rozszerzany na dowolną liczbę typów w rozłączeniu , podczas gdy
Either
wymaga zagnieżdżenia boksu, a paradygmat w moim poprzednim komentarzu 41 nie był rozszerzalny. Innymi słowy, aD[Int ∨ String]
można przypisać (tj. Jest podtypem) aD[Int ∨ String ∨ Double]
.Najwyraźniej kompilator Scala ma trzy błędy.
D[¬[Double]]
przypadku z meczu.3)
Metoda get nie jest poprawnie ograniczona do typu danych wejściowych, ponieważ kompilator nie pozwala
A
na pozycję kowariantną. Można argumentować, że jest to błąd, ponieważ wszystko czego chcemy to dowód, nigdy nie uzyskujemy dostępu do dowodów w funkcji. I dokonał wyboru nie do badaniacase _
wget
sposób, więc nie będzie musiał unboxOption
wmatch
wsize()
.05 marca 2012: poprzednia aktualizacja wymaga ulepszenia. Rozwiązanie Milesa Sabina działało poprawnie z podtytułem.
Moja poprzednia propozycja aktualizacji (dla typu unii prawie pierwszej klasy) złamała podtytuł.
Problem polega na tym, że
A
in(() => A) => A
pojawia się zarówno w pozycjach kowariantnej (typ zwracany), jak i kontrawariantnej (wejście funkcji, lub w tym przypadku wartość zwracana funkcji, która jest wejściem funkcji), dlatego podstawienia mogą być tylko niezmienne.Należy pamiętać, że
A => Nothing
jest to konieczne tylko dlatego, że chcemyA
w pozycji kontrawariantny, tak że supertypes zA
nie podtypów zD[¬[A]]
aniD[¬[A] with ¬[U]]
( patrz również ). Ponieważ potrzebujemy tylko podwójnej sprzeczności, możemy osiągnąć równowartość rozwiązania Milesa, nawet jeśli możemy odrzucić¬
i∨
.Tak więc pełna poprawka jest.
Zwróć uwagę, że pozostały 2 poprzednie błędy w Scali, ale trzeciego unika się, ponieważ
T
obecnie jest ograniczony do podtypuA
.Możemy potwierdzić prace podtypów.
Myślałem, że typy przecięcia pierwszej klasy są bardzo ważne, zarówno dla powodów Ceylon je ma , i dlatego zamiast podporządkowania do
Any
jakich środków unboxing zmatch
oczekiwanych typów może wygenerować błąd wykonywania, unboxing o ( kolekcja heterogenicznym zawierającym a) rozłączność można sprawdzić pod kątem typu (Scala musi naprawić błędy, które zauważyłem). Związki są bardziej proste niż złożoności przy użyciu eksperymentalnego HList z metascala dla zbiorów heterogenicznych.źródło
size
funkcji .size
nie jest już akceptowanyD[Any]
jako dane wejściowe.Jest inny sposób, który jest nieco łatwiejszy do zrozumienia, jeśli nie grokujesz Curry-Howarda:
Używam podobnej techniki w Dijon
źródło
Cóż, to wszystko jest bardzo sprytne, ale jestem pewien, że już wiesz, że odpowiedzi na twoje wiodące pytania to różne odmiany „Nie”. Scala radzi sobie z przeciążeniem w różny sposób i, trzeba przyznać, nieco mniej elegancko niż to opisujesz. Niektóre z nich wynikają z interoperacyjności Javy, niektóre z tego wynikają z tego, że nie chcą trafiać w przypadki algorytmu wnioskowania typu, a niektóre z tego powodu nie są po prostu Haskellem.
źródło
Dodając do już świetnych odpowiedzi tutaj. Oto podstawa, która opiera się na typach związków Milesa Sabina (i pomysłach Josha), ale także sprawia, że są one rekurencyjnie zdefiniowane, dzięki czemu możesz mieć> 2 typy w związku (
def foo[A : UNil Or Int Or String Or List[String]
)https://gist.github.com/aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb
NB: Powinienem dodać, że po przeanalizowaniu powyższego projektu, w końcu wróciłem do zwykłych starych sum (tj. Zapieczętowanej cechy z podklasami). Typy złączy Milesa Sabina świetnie nadają się do ograniczania parametru type, ale jeśli musisz zwrócić typ związku, nie oferuje on wiele.
źródło
A|C <: A|B|C
problem z podtytułem? stackoverflow.com/questions/45255270/... Moje jelita czują się NIE, ponieważ wtedy oznaczałoby to, żeA or C
musiałby to być podtyp,(A or B) or C
ale nie zawiera tego typu,A or C
więc nie ma nadziei na zrobienieA or C
podtypuA or B or C
z tym kodowaniem przynajmniej .. . co myślisz ?Z dokumentów , z dodatkiem
sealed
:W odniesieniu do
sealed
części:źródło