- Notifications
You must be signed in to change notification settings - Fork1
Object-Oriented .Net primitives. A port of cactoos library by Yegor Bugayenko, author of the "Elegant Objects" books.
License
icarus-consulting/Yaapii.Atoms
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Object-Oriented Primitives for .NET.This is a .NET port of the java libraryCactoos by Yegor Bugayenko.
It follows all the rules suggested in the two "Elegant Objects" books.
Version 1 of Atoms follows the principles of cactoos. All objects in cactoos are so-called live objects. This means, if you create a Text from a urlnew TextOf(new Uri("http://www.google.de"))
, every call to that object will fetch the content again. There is no caching until you explicitely define it by using a Sticky object. Sticky objects exist for all Types (Text, Enumerable, Map...).
However, after two years of working with Atoms, we realized that developers in our teams tend to think differently. They build objects and use them as if they have been cached. This produces a lot of unnecessary calculations and leads to slowdowns in our apps which we then solve in a second round where we analyze which objects should have been sticky. On the other hand, there are only a few cases where developers really need the live sensing of changes.
This has led to the decision to invert the library caching principle.Atoms 2.0 now has all objects sticky by default. We then introduced newLive Decorators instead. So if you need an object which senses changes, you decorate it using the live decorator:
varexchangeRate=newLive(()=>newTextOf(newUri("https://api.exchangeratesapi.io/latest")));
Live decorators are available for all types.
If you want to write your own objects based on Atoms envelopes, you have a switch which you can use to tell the envelope if it should behave as a live object or not (default is no)
publicsealedclassMyLiveTextObject:TextEnvelope{MyLiveTextObject(...):base(...,live:true)}
Input and Output types are not sticky by default.
While Java developers can skip the generic expression in constructors, C# does not allow it. We added shorthand objects to allow skipping it if you use string as generic type, for Enumerables and Maps. You have two objects to make an Enumerable:new ManyOf
to get an enumerable of strings andnew ManyOf<T>
if you need another type.
There are three objects to make a map:
newMapOf(newKvpOf("Key","Value"));//A map string to stringnewMapOf<int>(newKvpOf<int>("Key",123));//A map string to generic typenewMapOf<int,int>(newKvpOf<int,int>(123,123));//A map generic type to generic type
Envelopes are available for all three map types.
We decided to not leave the old sticky objects in the new version to get a fresh start.
If you want to migrate from 1.x to 2.x and want to preserve the behaviour regarding sticky objects, you should (in this order):
- Replace
ScalarOf
withLive
- Replace
Sticky
withScalarOf
- Replace
ManyOf
withLiveMany
- Replace
StickyEnumerable
withManyOf
- Replace
ListOf
withLiveList
- Replace
StickyList
withListOf
- Replace
CollectionOf
withLiveCollection
- Replace
Collection.Sticky
withCollectionOf
- Replace
TextOf
withLiveText
(StickyText did not exist) - Replace
NumberOf
withLiveNumber
(Stickynumber did not exist)
Maps are grouped in the Lookup Area of atoms. All map objects implement C#IDictionary<Key, Value>
interface.
varmap=newMapOf<string,int>("Amount",100);
CSharp maps useKeyValuePair<Key,Value>
as contents. These are structs and therefore not objects which we can implement or decorate. Atoms offers the typeIKVp<Key,Value>
as alternative.
varmap=newMapOf<string,int>(newKvpOf<string,int>("age",28),newKvpOf<string,int>("height",184),)
This allows maps to accept functions to build the value, and get a simplestrategy pattern
//If you ask the map for "github", it will execute the function and retrieve the content. The content of the first kvp is NOT retrieved.vargithubContent=newMapOf<string,string>(newKvpOf<string,string>("google",()=>newTextOf(newUri("https://www.google.de"))),newKvpOf<string,string>("github",()=>newTextOf(newUri("https://www.github.com"))))["github"];//Note that MapOf is sticky, so if you need live content, decorate the map:varliveContent=newLiveMap<string,string>(()=>newMapOf<string,string>(newKvpOf<string,string>("google",()=>newTextOf(newUri("https://www.google.de"))),newKvpOf<string,string>("github",()=>newTextOf(newUri("https://www.github.com")))))["github"];//Beware: If you have lazy values, you normally do NOT want to execute all functions. Atoms prevents it, so the following will fail:foreach(vardoNotDoThisingithubContent){ ...}//If you know that you need all values, simply enumerate the keys:foreach(varkeyingithubContent.Keys){varvalue=githubContent[key];}
To save typing, there are two shorthand map objects:
newMapOf(newKvpOf("Key","Value"))//Use without generic to get a string-string mapnewMapOf<int>(newKvpOf("Key",100))//Use without generic to get a string-generic map
You can make a string-string map by simple writing value pairs one after another:
vartranslations=newMapOf("Objects","Objekte","Functions","Funktionen","Bigmac","Viertelpfünder mit Käse")
The interfaces are:
//Function with input and outputpublicinterfaceIFunc<In,Out>{OutInvoke(Ininput);}//Function with output onlypublicinterfaceIFunc<Out>{OutInvoke();}//Function with two inputs and one outputpublicinterfaceIFunc<In1,In2,Out>{OutInvoke(In1input1,In2input2);}//Function with input onlypublicinterfaceIAction<In>{voidInvoke(Ininput);}
vari=newFuncOf<int,int>((number)=>number++).Invoke(1);//i will be 2
varurl=newUrl("https://www.google.de");varf=newStickyFunc<Url,IText>((u)=>newTextOf(newInputOf(u))).Invoke(url);varhtml=f.Invoke();//will load the page content from the webvarhtml=f.Invoke();//will load the page content from internal cache
newRetryFunc<int,int>( input=>{if(newRandom().NextDouble()>input){thrownewArgumentException("May happen");}return0;},100000).Invoke(0.3d);//will try 100.000 times to get a random number under 0.3
varcount=0;newRepeatedFunc<int,int>( input=>input++,3).Invoke(count);//will return 3
newFuncWithFallback<string,string>( name=>{thrownewException("Failure");}, ex=>"Never mind, "+name).Invoke("Miro");//will be "Never mind, Miro"
- And more
The IInput and IOutput interfaces:
publicinterfaceIInput{StreamStream();}publicinterfaceIOutput{StreamStream();}
newInputOf(newUri(@"file:///c:\secret-content-inside.txt")).Stream();//returns a readable stream of the file
newOutputTo(newUri(@"file:///c:\secret-content-inside.txt")).Stream();//returns a readable stream of the file
newConsoleOutput().Stream();//Default console outputnewConsoleErrorOutput().Stream();//Console error output
varfileContent=newTextOf(newInputOf(newUri(@"file:///c:\secret-content-inside.txt"))).AsString();//reads the content and gives it as text
newTextOf(newInputOf(newUrl("https://www.google.de"))).AsString();//gives the content html of the google start page
newTextOf(newInputWithFallback(newInputOf(newUri(Path.GetFullPath("/this-file-does-not-exist.txt"))//file to read),newInputOf(newTextOf("Alternative text!"))//fallback to use).AsString();//will be "Alternative Text!"
longlength=newLengthOf(newInputOf(newUri(@"file:///c:\great-content-inside.txt"))).Value();//will be the number of bytes in the file
- Write to file, url, byte arrays, streams
newLengthOf(newTeeInput("Welcome to the world of c:!",newOutputTo(newUri(@"file:///c:\greeting.txt")))).Value();//will write "Welcome to the world of c:!" to the file c:\greeting.txt.//This happens because TeeInput puts every byte that is read to the specified output.//When calling Value(), every byte is read to count the content.
varinPath=Path.GetFullPath(@"file:///c:\input-file.txt");varoutPath=Path.GetFullPath(@"file:///c:\output-file.txt");newLengthOf(newTeeInput(newInputOf(newUri(inPath)),newOutputTo(newUri(outPath)))).Value();//since LengthOf will read all the content when calling Value(), all that has been read will be copied to the output path.//Alternative: Copy to Console outputnewLengthOf(newTeeInput(newInputOf(newUri(inPath)),newConsoleOutput())).Value();//will dump the read content to output
newTextOf(newStickyInput(newInputOf(newUrl("http://www.google.de"))));varhtml1=input.AsString();//will read the url from the webvarhtml2=input.AsString();//will return from the cache
Enumerables use the IEnumerable and IEnumerator interfaces from C#:
publicinterfaceIEnumerable<outT>:IEnumerable{IEnumerator<T>GetEnumerator();}publicinterfaceIEnumerator<outT>:IEnumerator,IDisposable{TCurrent{get;}}
Naming of base objects differs. To save chars, shorthand names are used:
//use without generic and get an IEnumerable<string>varstrings=newManyOf("a string","another string");//use with generic and get an IEnumerable<T>varints=newManyOf<int>(98,20);
newFiltered<string>(newList<string>(){"A","B","C"},(input)=>input!="B");//will be a list with "A" and "C" inside
newItemAt<int>(newManyOf<int>(1,2,3),2).Value();//will be 3 (Zero based)//To get the first item simply do not specify a position:newItemAt<int>(newManyOf<int>(1,2,3)).Value();//will be 1//To get an item with a fallback if it isn't there:Stringfallback="fallback";newItemAt<string>(newManyOf<string>(),//empty list,12,//position 12 which does not existfallback).Value();//will be "fallback"
//Default sorting is forwardnewSorted<int>(newManyOf<int>(3,2,10,44,-6,0));//items will be sorted to -6, 0, 2, 3, 10, 44//Use another comparator for sortingnewSorted<string>(IReverseComparer<string>.Default,//comparator is from C#.NET librarynewManyOf<string>("a","c","hello","dude","Friend"));//will be sorted to hello, Friend, dude, c, a
varl=newLengthOf<int>(newManyOf<int>(1,2,3,4,5)).Value();//will be 5
ITextgreeting=newItemAt<IText>(newMapped<String,IText>(newManyOf<string>("hello","world","damn"), input=>newUpperText(newTextOf(input))//is applied to every item and will make a uppertext of it),0).Value();//will be "HELLO"
// Mapping items of a list to another type using index of itemsnewMapped<string,string>(newList<string>(){"One","Two",Three"},(input,index)=>$"{input}={index+1}");// Returns a IEnumerable<string> with Content {"One=1", "Two=2", Three=3"}
//here is a list with 3 items and you call the 7th item. The cycled list will not fail but start over when it reaches the end.newItemAt<string>(newCycled<string>(//make a cycled list of the enumerable with 3 itemsnewManyOf<string>("one","two","three")),7).Value();//will be "two"
newLengthOf(newJoined<string>(newManyOf<string>("hello","world","Miro"),newManyOf<string>("how","are","you"),newManyOf<string>("what's","up"))).Value();//will be 8
newSumOfInts(newHeadOf<int>(newManyOf<int>(0,1,2,3,4),3)).Value();//will be 3 (0 + 1 + 2)
//this snippet has an endless list, which then is limited to the size. Every time someone calls the list, size increases and the list would grow. But StickyEnumerable prevents that and always returns the same list.intsize=2;varlist=newStickyEnumerable<int>(newHeadOf<int>(newEndless<int>(1),newScalarOf<int>(()=>Interlocked.Increment(refsize))));newLengthOf(list).Value();//will be 2newLengthOf(list).Value();//will be 2
- and more
The IScalar interface looks like this:
publicinterfaceIScalar<T>{TValue();}
A scalar is an object which can encapsulate objects and functions that return objects. It enables you to let a function appear as its return value. This is very useful to keep constructors code-free but also keep your overall object count low.
Also, scalars can be used to perform logical operations like And, Or, Not and more on function results or objects.
varsc1=newScalarOf<string>("this brave string");stringstr=sc.Value();//returns the stringvarsc2=newScalarOf<IEnumerable<int>>(newManyOf<int>(1,2,3,4,5));IEnumerable<int>lst=sc2.Value();//returns the list
varsc=newScalarOf<string>(()=>newTextOf(newInputOf(newUrl("http://www.ars-technica.com"))).AsString());stringhtml=sc.Value();//will fetch the html from the url and return it as a string
varsc=newStickyScalar<string>(()=>newTextOf(newInputOf(newUrl("http://www.ars-technica.com"))).AsString()).Value();stringhtml=sc.Value();//will fetch the html from the url and return it as a stringstringhtml2=sc.Value();//will return the html from the cache
varresult=newAnd<True>(()=>true,()=>false,()=>true).Value();//will be falsevarnumber=3;newAnd<True>(()=>true,//function that returns true()=>number==4//function that returns false).Value();//will be false//you can also pass scalars into AND, and more.
newTernary<bool,int>(newTrue(),//condition is true6,//if true16//if false).Value();//will be 6newTernary<int,int>(5,//input to test input=>input>3,//condition input=>input=8,//return if condition true input=>input=2//return if condition false).Value();//will be 8
And more...
- Negative
- Max
- Min
- Or
The IText interface looks like this:
publicinterfaceIText:IEquatable<IText>{StringAsString();}
//Lower a textnewLowerText(newTextOf("HelLo!")).AsString();//will be "hello!"//upper a textnewUpperText(newTextOf("Hello!")).AsString();//will be "HELLO!"
newReversedText(newTextOf("Hello!")).AsString();//"!olleH"
newTrimmedText(newTextOf(" Hello!\t ")).AsString();// "Hello!"newTrimmedLeftText(newTextOf(" Hello!\t ")).AsString();// "Hello! \t "newTrimmedRightText(newTextOf(" Hello!\t ")).AsString();// " Hello!"
IEnumerable<Text>splitted=newSplitText("Hello world!","\\s+");
newReplacedText(newTextOf("Hello!"),"ello",// what to replace"i"// replacement).AsString();// "Hi!"
newJoinedText(" ","hello","world").AsString();// "hello world"
newFormattedText("{0} Formatted {1}",1,"text").AsString();// "1 Formatted text"
//text from a string with encodingvarcontent="Greetings, Mr.Freeman!";newTextOf(content,Encoding.UTF8);//text from a input with default encodingvarcontent="Hello, my precious coding friend! with default charset";newTextOf(newInputOf(content));//text from a StringReaderStringsource="Hello, Dude!";newTextOf(newStringReader(source),Encoding.UTF8);//text from a char arraynewTextOf('O',' ','q','u','e',' ','s','e','r','a',' ','q','u','e',' ','s','e','r','a');//text from a byte arraybyte[]bytes=newbyte[]{(byte)0xCA,(byte)0xFE};newTextOf(bytes);//text from a StringBuilderStringstarts="Name it, ";Stringends="then it exists!";Assert.True(newTextOf(newStringBuilder(starts).Append(ends));//text from an exceptionnewTextOf(newIOException("It doesn't work at all"));
LinQ | Yaapii.Atoms |
---|---|
Aggregate | Not available yet |
All | And<T> |
Any | Or<T> |
AsEnumerable | var arr = new int[]{ 1, 2, 3, 4 }; |
Average | var avg = new AvgOf(1, 2, 3, 4).AsFloat(); //avg = 2.5 |
Cast | Not available yet |
Concat | var joined = new Joined<string>( |
Contains | var b = new Contains<string>( |
Count | var length = new LengthOf<int>( |
DefaultIfEmpty | Not available yet |
Distinct | var dis = new Distinct<int>(//actual with bug |
ElementAt | var itm = new ItemAt<int>( |
ElementAtOrDefault | var itm = new ItemAt<string>( |
Empty | new EnmuerableOf<int>() |
Except | Not available yet |
First | var list = new EnumerableO<int>(1, 2, 3); |
FirstOrDefault | var itm = new ItemAt<string>( |
Foreach | var list = new List[]; |
GroupBy | Not available yet |
GroupJoin | Not available yet |
Intersect | Not available yet |
Join | Not available yet |
Last | var last = new ItemAt<int>( |
LastOrDefault | var itm = new ItemAt<string>( |
LongCount | Not available yet* |
Max | var max = new MaxOf(22, 2.5, 35.8).AsDouble(); //max = 35.8; .AsInt() = 35 |
Min | var max = new MaxOf(22, 2.5, 35.8).AsDouble(); //max = 2.5; .AsInt() = 2 |
OfType | Not available yet |
OrderBy | var sorted = new Sorted<int>( |
OrderByDescending | var sorted = new Sorted<string>( |
Range | Not available yet |
Repeat | var repeated = new Repeated<int>(10,5) // repeated = {10, 10, 10, 10, 10} |
Reverse | var reversed = new Reversed<int>(ManyOf(2,3,4)); //reversed = {4,3,2} |
Select | var selected = Mapped<string,string>( |
SelectMany | Not available yet |
SequenceEqual | Not available yet |
Single | Not available yet |
SingleOrDefault | Not available yet |
Skip | var skipped = new Skipped<string>( |
SkipWhile | Not available yet |
Sum | var sum = new SumOf( |
Take | var lmt = new HeadOf<int>( |
TakeWhile | Not available yet |
ThenBy | Not available yet |
ThenByDescending | Not available yet |
ToArray | Not available yet |
ToDictionary | var dic = new MapOf( |
ToList | var list = new CollectionOf<int>( |
ToLookup | Not available yet |
Union | var enu = new Distinct<int>( |
Where | var newFiltered = new Filtered<string>( |
Zip | Not available yet |
About
Object-Oriented .Net primitives. A port of cactoos library by Yegor Bugayenko, author of the "Elegant Objects" books.