Jaka jest motywacja przypisania Scali do oceny Jednostki, a nie przypisanej wartości?
Typowy wzorzec w programowaniu we / wy polega na wykonywaniu następujących czynności:
while ((bytesRead = in.read(buffer)) != -1) { ...
Ale w Scali nie jest to możliwe, ponieważ ...
bytesRead = in.read(buffer)
.. zwraca Unit, a nie nową wartość bytesRead.
Wydaje się, że interesujące jest pominięcie języka funkcjonalnego. Zastanawiam się, dlaczego tak się stało?
scala
functional-programming
io
assignment-operator
Graham Lea
źródło
źródło
Odpowiedzi:
Opowiadałem się za tym, aby przypisania zwracały przypisaną wartość, a nie jednostkę. Martin i ja omawialiśmy to w kółko, ale jego argument był taki, że umieszczanie wartości na stosie tylko po to, aby usunąć go w 95% przypadków, było marnowaniem kodów bajtowych i miało negatywny wpływ na wydajność.
źródło
void
. W Scalifoo_=(v: Foo)
powinien wrócić,Foo
jeśli przydział tak.void
(Unit
), przypisaniax = value
są tłumaczone na odpowiednikx.set(value);x.get(value)
; kompilator eliminuje podczas optymalizacji faz połączeniaget
wywołań, jeśli wartość nie była używana. Mogłaby to być mile widziana zmiana w nowej dużej (z powodu wstecznej niekompatybilności) wersji Scala i mniej irytacji dla użytkowników. Co myślisz?Nie mam dostępu do informacji wewnętrznych na temat rzeczywistych powodów, ale moje podejrzenie jest bardzo proste. Scala sprawia, że pętle wywołujące efekty uboczne są niewygodne w użyciu, więc programiści naturalnie wolą for-comp R.
Robi to na wiele sposobów. Na przykład nie masz
for
pętli, w której deklarujesz i modyfikujesz zmienną. Nie możesz (łatwo) zmutować stanu wwhile
pętli w tym samym czasie, gdy testujesz warunek, co oznacza, że często musisz powtórzyć mutację tuż przed nią i na końcu. Zmienne zadeklarowane wewnątrzwhile
bloku nie są widoczne zwhile
warunku testu, codo { ... } while (...)
znacznie zmniejsza ich użyteczność. I tak dalej.Obejście:
while ({bytesRead = in.read(buffer); bytesRead != -1}) { ...
Cokolwiek to jest warte.
Jako alternatywne wyjaśnienie, być może Martin Odersky musiał zmierzyć się z kilkoma bardzo brzydkimi błędami wynikającymi z takiego użycia i zdecydował się zakazać tego w swoim języku.
EDYTOWAĆ
David Pollack został odpowiedział z pewnymi faktami, które są wyraźnie zatwierdzone przez fakt, że Martin Odersky sam skomentował swoją odpowiedź, dając wiarę argumentu problemów związanych z wydajnością przedstawianej przez Pollacka.
źródło
for
wersja pętli wyglądałaby tak:for (bytesRead <- in.read(buffer) if (bytesRead) != -1
co jest świetne, z wyjątkiem tego, że nie zadziała, ponieważ nie maforeach
i jestwithFilter
dostępne!Stało się tak, ponieważ Scala miała bardziej „poprawny formalnie” system typów. Z formalnego punktu widzenia przypisanie jest instrukcją o działaniu ubocznym i dlatego powinno zostać zwrócone
Unit
. Ma to dobre konsekwencje; na przykład:class MyBean { private var internalState: String = _ def state = internalState def state_=(state: String) = internalState = state }
Te
state_=
powroty SposóbUnit
(jak można by oczekiwać na nastawczym) właśnie dlatego powraca przyporządkowaniaUnit
.Zgadzam się, że w przypadku wzorców w stylu C, takich jak kopiowanie strumienia itp., Ta konkretna decyzja projektowa może być nieco kłopotliwa. Jednak ogólnie rzecz biorąc, jest to stosunkowo bezproblemowe i naprawdę przyczynia się do ogólnej spójności systemu typów.
źródło
Być może wynika to z zasady separacji poleceń i zapytań ?
CQS wydaje się być popularny na przecięciu OO i funkcjonalnych stylów programowania, ponieważ tworzy oczywiste rozróżnienie między metodami obiektowymi, które mają lub nie mają skutków ubocznych (tj. Zmieniają obiekt). Zastosowanie CQS do przypisań zmiennych prowadzi dalej niż zwykle, ale ta sama idea ma zastosowanie.
Krótki ilustracją dlaczego CQS jest przydatna: Rozważmy hipotetyczny język hybrydowy F / oo z
List
klasy, która posiada metodySort
,Append
,First
, iLength
. W imperatywnym stylu OO można by napisać taką funkcję:func foo(x): var list = new List(4, -2, 3, 1) list.Append(x) list.Sort() # list now holds a sorted, five-element list var smallest = list.First() return smallest + list.Length()
Podczas gdy w bardziej funkcjonalnym stylu bardziej prawdopodobne jest napisanie czegoś takiego:
func bar(x): var list = new List(4, -2, 3, 1) var smallest = list.Append(x).Sort().First() # list still holds an unsorted, four-element list return smallest + list.Length()
Wydaje się, że próbują zrobić to samo, ale oczywiście jedna z nich jest niepoprawna i nie wiedząc więcej o zachowaniu metod, nie możemy powiedzieć, która z nich.
Używając CQS, chcielibyśmy jednak nalegać, aby jeśli
Append
i zmieniliSort
listę, musieli zwrócić typ jednostki, zapobiegając w ten sposób tworzeniu błędów przez użycie drugiej formy, gdy nie powinniśmy. W związku z tym obecność skutków ubocznych staje się również domniemana w sygnaturze metody.źródło
Sądzę, że dzieje się tak, aby program / język był wolny od skutków ubocznych.
To, co opisujesz, to celowe użycie efektu ubocznego, który w ogólnym przypadku jest uważany za zły.
źródło
val a = b = 1
(wyobrazić „magiczny”val
z przodub
) vs.val a = 1; val b = 1;
.Używanie przypisania jako wyrażenia logicznego nie jest najlepszym stylem. Wykonujesz dwie rzeczy jednocześnie, co często prowadzi do błędów. Przypadkowe użycie znaku „=” zamiast „==” jest unikane dzięki ograniczeniu Scalas.
źródło
Przy okazji: uważam, że początkowe sztuczki są głupie, nawet w Javie. Dlaczego nie coś takiego?
for(int bytesRead = in.read(buffer); bytesRead != -1; bytesRead = in.read(buffer)) { //do something }
To prawda, przypisanie pojawia się dwukrotnie, ale przynajmniej bytesRead znajduje się w zakresie, do którego należy, i nie bawię się zabawnymi sztuczkami z przypisaniem ...
źródło
Możesz obejść ten problem, o ile masz typ referencyjny dla pośredniego. W naiwnej implementacji możesz użyć następujących dla dowolnych typów.
case class Ref[T](var value: T) { def := (newval: => T)(pred: T => Boolean): Boolean = { this.value = newval pred(this.value) } }
Następnie, pod ograniczeniem, którego będziesz musiał użyć,
ref.value
aby później uzyskać dostęp do odniesienia, możesz zapisać swójwhile
predykat jakoval bytesRead = Ref(0) // maybe there is a way to get rid of this line while ((bytesRead := in.read(buffer)) (_ != -1)) { // ... println(bytesRead.value) }
i możesz przeprowadzić sprawdzanie
bytesRead
w bardziej niejawny sposób bez konieczności wpisywania go.źródło