Skip to content

Instantly share code, notes, and snippets.

@bojanrajkovic
Last active March 14, 2024 20:13
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bojanrajkovic/b1ff4d52fccffcf7e6e98aa041b52ee7 to your computer and use it in GitHub Desktop.
Save bojanrajkovic/b1ff4d52fccffcf7e6e98aa041b52ee7 to your computer and use it in GitHub Desktop.
Quick and dirty Rust-like Option<T> in C#. Basically untested, use at your own risk!
public static class Option
{
public static Option<T> None<T>() => new None<T>();
public static Option<T> Some<T>(T value) => new Some<T>(value);
}
public class Option<T> : IEquatable<Option<T>>
{
public static Option<T> None => new None<T>();
public bool IsNone => !hasValue;
public bool IsSome => !IsNone;
bool hasValue;
T value;
protected Option() {}
protected Option(T value) { hasValue = true; this.value = value; }
public bool Equals(Option<T> other)
{
if (ReferenceEquals(other, null))
return false;
if (hasValue != other.hasValue)
return false;
return !hasValue || Equals (value, other.value);
}
public override bool Equals(object obj)
{
if (obj is Option<T>)
return Equals((Option<T>)obj);
return false;
}
string FriendlyName(Type t) {
using (var provider = new Microsoft.CSharp.CSharpCodeProvider())
return provider.GetTypeOutput (new System.CodeDom.CodeTypeReference(t));
}
public T Expect(string msg) => IsSome ? value : throw new Exception(msg);
public T Unwrap() => IsSome ? value : throw new Exception($"Tried to unwrap a None<{FriendlyName(typeof(T))}>!");
public T UnwrapOr(T def = default(T)) => IsSome ? value : def;
public T UnwrapOr(Func<T> provider) => IsSome ? value : provider();
public Option<U> Map<U>(Func<T, U> converter) => IsSome ? new Option<U>(converter(value)) : new None<U>();
public async Task<Option<U>> Map<U>(Func<T, Task<U>> converter) => IsSome ? new Option<U>(await converter(value)) : new None<U>();
public U MapOr<U>(U def, Func<T, U> converter) => IsSome ? converter(value) : def;
public U MapOr<U>(Func<U> provider, Func<T, U> converter) => IsSome ? converter(value) : provider();
public Option<U> And<U>(Option<U> option) => IsNone ? Option<U>.None : option;
public Option<U> AndThen<U>(Func<T, Option<U>> option) => IsNone ? Option<U>.None : option(value);
public Task<Option<U>> AndThen<U>(Func<T, Task<Option<U>>> option) => IsNone ? Task.FromResult(Option<U>.None) : option(value);
public Option<T> Or(Option<T> other) => IsSome ? this : other;
public Option<T> OrElse(Func<Option<T>> option) => IsSome ? this : option();
public Task<Option<T>> OrElse(Func<Task<Option<T>>> option) => IsSome ? Task.FromResult(this): option();
public void Take()
{
value = default(T);
hasValue = false;
}
public override int GetHashCode() => !hasValue ? 0 : (ReferenceEquals (value, null) ? -1 : value.GetHashCode());
public static bool operator ==(Option<T> left, Option<T> right) => left.Equals(right);
public static bool operator !=(Option<T> left, Option<T> right) => !left.Equals(right);
public static implicit operator Option<T>(T value) => value == null ? (Option<T>) new None<T> () : new Some<T>(value);
}
public class None<T> : Option<T> {}
public class Some<T> : Option<T>
{
public Some(T value) : base (value) {}
}
using static Option;
class Driver
{
public static void Print<T> (Option<T> opt){
if (opt is null)
Console.WriteLine("Null :(");
if (opt is Some<T> some)
Console.WriteLine(some.Unwrap());
if (opt is None<T> none)
Console.WriteLine("None :(");
}
async Task<Option<string>> FetchUrl (string url) {
using (var client = new HttpClient ()) {
try {
return Some(await client.GetStringAsync (url));
} catch (Exception e) {
return None<string>();
}
}
}
public static void Main(string[] args)
{
var option = Some(5);
var none = None<int>();
Console.WriteLine(option.IsSome); // true
Console.WriteLine(option.IsNone); // false
Console.WriteLine(option == none); // false
var and = option.And(10);
Console.WriteLine(and.IsSome); // true
Console.WriteLine(and.IsNone); // false
Console.WriteLine(and == 5); // false
Console.WriteLine(and == 10); // true
var andThen = option.AndThen<int> ((val) => (int)Math.Pow(val, 2));
Console.WriteLine(andThen.IsSome); // true
Console.WriteLine(andThen.IsNone); // false
Console.WriteLine(andThen == 25); // true
var or = option.Or(10);
Console.WriteLine(or.IsSome); // true
Console.WriteLine(or.IsNone); // false
Console.WriteLine(or == 10); // false
Console.WriteLine(or == 5); // true
var orElse = option.OrElse(() => 10);
Console.WriteLine(or.IsSome); // true
Console.WriteLine(or.IsNone); // false
Console.WriteLine(or == 10); // false
Console.WriteLine(or == 5); // true
Print(andThen); // => 25 is printed
Print(none); // => "None :(" is printed
Print(await Some(10).AndThen<double>(async task => await Task.FromResult(Math.Pow(2, task)))); // 1024 is printed
Print((await FetchUrl("https://foooooooooooooooooo.xom")).AndThen<string> (fooText => fooText.Substring (0, 10))); // "None :(" is printed
Print((await FetchUrl("https://google.com")).AndThen<string> (fooText => fooText.Substring (0, 10))); // "<!doctype" is printed
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment