Mniejsza o to - po prostu nie zrozumiałem co chcesz przekazać. Możesz napisać posta z przykładami pseudokodu? — @somekind dziś, 16:44
Zastrzegam, że może się okazać, że bzdury piszę.
Niejasno sobie przypominam, że w Pythonie obowiązuje konwencja, że tylko metody / funkcje czyste z punktu widzenia niemutowalności mają prawo zwracać obiekt, który zmieniają.
Zgodne z konwencją, imperatywne:
l = [1, 2, 3, 4]
modify_list(l) # zwraca none czyli pythonowy odpowiednik voida, nie można zrobić modified_l = modify_list(l)
print(l) # drukuje [5, 6, 7, 8]
Zgodne z konwencją, funkcyjne:
l = [1, 2, 3, 4]
modified_l = modify_list(l)
print(l) # drukuje [1, 2, 3, 4]
print(modified_l) #drukuje [5, 6, 7, 8]
Niezgodne z konwencją, mylące, choć technicznie poprawne:
l = [1, 2, 3, 4]
modified_l = modify_list(l) # źle (niezgodnie z konwencją) napisana funkcja modify_list
print(l) # drukuje [5, 6, 7, 8]
print(modified_l) # drukuje [5, 6, 7, 8]
Ma to swój sens moim zdaniem: trudno popełnić błąd w rodzaju przypadkowej mutacji jakiejś referencji bo się uważa, że metoda, która tak naprawdę mutuje, jest czysta i na odwrót trudno przypadkowo nie dokonać jakiejś zmiany, bo potraktowało się czystą metodę jakby mutowała.
Widać, że przy tym podejściu tylko czyste funkcje / metody mogą być dotchainowane. Też ma to sens moim zdaniem: przyjęty w programowaniu imperatywnym separator to średnik (w Pythonie nowa linia), nie wiem czemu upierać się przy zastępowaniu średnika kropką?
Ale - jak mi się zdaje - C#powcy (i Javowcy także) bardzo lubią dotchainować. Niejasno mi się przypominało, że Fowler bodajże dał temu szumną nazwę "flow api", ale może bzdurę palnąłem.
Dlatego za brzydkie jest uważane pisanie:
var foo = new Foo();
foo.Bar = 5;
foo.Baz = 6;
Zamiast tego za estetyczniejsze jest uważane:
var foo = new Foo()
.WithBar(5)
.WithBaz(6);
Przy czym WithBar
oraz WithBaz
oczywiście działają imperatywnie:
public class Foo
{
public int Bar {get; private set;}
public Foo WithBar(int bar)
{
this.Bar = bar;
return this;
}
}
Jaki z tego zysk, nie wiem. Widać natomiast, że różni się to od konwencji pythonowej, co utrudnia odróżnienie na pierwszy rzut oka metody czystej od nieczystej. Czy WithBar
mutuje this
, czy nie mutuje? Nie widać od razu.
Wracając do twojego przykładu:
var rawList = GetFromSomewhere();
var modifiedList = ModifyList(rawList);
var diff = CalculateDiff(rawData, modifiedData);
Czemu ModifyList
zwraca listę, skoro jednocześnie mutuje swój argument? Gdyby konwencja pythonowa przyjęła się w C#, to trudno byłoby popełnić ten błąd, o którym piszesz: wiadomo, że modifyList
zwraca listę, więc nie mutuje argumentu. Ale C#owcy wolą móc zastępować średniki kropkami, czy też, jak w tym wypadku, choćby nawiasami. Ważniejsze jest, by móc napisać:
var list = ModifyList(GetFromSomewhere());
zamiast:
var list = GetFromSomewhere();
ModifyList(list);
niż by była jasność, co tak naprawdę robi ModifyList
.
Zresztą i w C# można się przed tym bronić. Wystarczy stosować inną konwencję: jeśli metoda mutuje, niech przyjmuje jako swój argument mutowalny interfejs, a jeśli nie mutuje, niech przyjmuje niemutowalny interfejs. Mamy ModifyList(List<Cośtam> l)
? Wiadomo, że mutuje. Mamy ModifyList(ReadOnlyCollection<Cośtam> l)
? Nie mutuje. Trochę porządku i wszystko wiadomo.
Nadal wydaje mi się, że mutowanie bywa wygodne. Np. stan gry. Gdzieś głęboko obsługujemy jakiś atak. Ale ten atak jest typu Fire
, w związku z czym mamy 20% szans, że przeciwnik otrzyma status ailment Burned
. Nie wygodnie byłoby dodać odpowiedniej postaci w grze tego statusu imperatywnie? hurtCharacter.Effects.Add(new Burned());
? No niby można zwrócić nową postać, ale w ostateczności to:
hurtCharacter.Effects.Add(new Burned()); // GameState mutowalny, GameState.Characters mutowalne, Character mutowalny, Character.Effects mutowalne
jest po prostu wygodniejsze, niż to:
return gameState with { // GameState niemutowalny
Characters = gameState.Characters.SetItem( // Characters to ImmutableDictionary
key: hurtCharacter.Id, // Bug jeśli pokręcę id!
value: hurtCharacter with { // Character niemutowalny
Effects = hurtCharacter.Effects.Add(new Burned()); // // Character.Effects to ImmutableList
}
)
}
(Chętnie dam się przekonać, że się mylę)