There are two types of signals. Read/write signals can be assigned values. Here we create one called counter which we initially a assign a value of 4
varcounter=Signal.State(4);this can be changed to a five etc using Set.
counter.Set(5);and read back using Get
Console.WriteLine(counter.Get());// prints 5The second type of signals are computed ones, these are based on other signals
varisEven=Signal.Computed(()=>counter.Get()%2==0);Console.WriteLine(isEven.Get());// prints false as counter is 5counter.Set(6);Console.WriteLine(isEven.Get());// prints true as counter is 6Computed signals can also be based on other computed signals
varparity=Signal.Computed(()=>isEven.Get()?"Even":"Odd";);Console.WriteLine(parity.Get());// prints Even as isEven is true`counter.Set(9);Console.WriteLine(parity.Get());// prints odd as isEven is now falseor on multiple signals
varfirstname=Signal.State("David");varlastname=Signal.State("Betteridge");varfullname=Signal.Computed(()=>$"{firstname.Get()}{lastname.Get()}");Effects can be added to signals which are automatically triggered when a value changes
counter.AddEffect((previous,current)=>Console.WriteLine($"Counter changed {previous} to {current}"));isEven.AddEffect((previous,current)=>Console.WriteLine($"IsEven changed {previous} to {current}"));parity.AddEffect((previous,current)=>Console.WriteLine($"Parity changed {previous} to {current}"));counter.Set(10);// Counter changed 9 to 10// IsEven changed False to True// Parity changed Odd to Evencounter.Set(12);// Counter changed 10 to 12Effects can also be removed from a signal.
vareffect=isEven.AddEffect((p,c)=>{ ...}));isEven.RemoveEffect(effect);In the previous example not only aren't the other two effects triggered, but parity isn't even computed.
varparity=SignalBuilder.DependsOn(isEven).ComputedBy(v =>{Console.WriteLine("Compute parity");returnv.Get()?"Even":"Odd";});// This changes the value for isEven so parity is computedcounter.Set(13);Console.WriteLine(parity.Get());// This doesn't change the value for isEven so parity isn't computedcounter.Set(15);Console.WriteLine(parity.Get());So far we have only looked at a signals based on a primitive type (integer). In the real world we need to cater for classes/records/arrays/lists etc. For example:
recordPeople(stringName,intAge);vardata=Signal.State<List<People>>([newPeople("David",48)]);varadults=Signal.Computed(()=>data.Get().Where(p =>p.Age>18).ToList());varnumberOfAdults=Signal.Computed(()=>adults.Get().Count);numberOfAdults.AddEffect((oldCount,newCount)=>Console.WriteLine($"Number of adults changed from {oldCount} to {newCount}"));The numberOfAdults signal only needs to be computed if adults changes.
In order to get this to work, we have to supply your own Equality function. This is provided using a second (optional) argument to the Signal.State and Signal.Computed methods.
varadults=Signal.Computed(()=>data.Get().Where(p =>p.Age>18).ToList(),CompareOrderedLists);boolCompareOrderedLists<T>(IList<T>lhs,IList<T>rhs)whereT:notnull{// T must be a record type as they generate the EqualityContract property// There is no proper way to enforce this in C# however.if(lhs.Count!=rhs.Count)returnfalse;for(vari=0;i<lhs.Count;i++){if(!lhs[i].Equals(rhs[i]))returnfalse;}returntrue;}