UniRx: Observable.ReturnUnit のソースを読む
Observable.ReturnUnit
のサンプルコード
using System; using UniRx; using UnityEngine; public class Main : MonoBehaviour { void Start() { var s = Observable.ReturnUnit(); s.Subscribe(e => Debug.Log("e: " + e), () => Debug.Log("OnCompleted")); } }
実行結果です。
e: () OnCompleted
Observable.ReturnUnit
のソースを読む
Observable.ReturnUnit()
を Subscribe()
するとき、内部でどのような処理が行われるのか眺めてみます。
まずは Observable.ReturnUnit()
の内部を見てみます。
/// <summary> /// Same as Observable.Return(Unit.Default); but no allocate memory. /// </summary> public static IObservable<Unit> ReturnUnit() { return ImmutableReturnUnitObservable.Instance; }
ImmutableReturnUnitObservable.Instance
インスタンスが使い回されているようです。
次に ImmutableReturnUnitObservable
クラスの実装を眺めてみます。
internal class ImmutableReturnUnitObservable : IObservable<Unit>, IOptimizedObservable<Unit> { internal static ImmutableReturnUnitObservable Instance = new ImmutableReturnUnitObservable(); ImmutableReturnUnitObservable() { } public bool IsRequiredSubscribeOnCurrentThread() { return false; } public IDisposable Subscribe(IObserver<Unit> observer) { observer.OnNext(Unit.Default); observer.OnCompleted(); return Disposable.Empty; } }
ふむふむ。Subscribe()
するとすぐに OnNext()
と OnCompleted()
を順に呼び出しています。
ふと疑問。サンプルコードで呼び出している Subscribe()
は 2 引数なのですが、上の定義では 1 引数の定義しかありませんね。
2 引数を受け取る Subscribe()
はどこで定義されているのでしょうか。
コードを読むと、以下の拡張メソッドで定義されていました。
public static IDisposable Subscribe<T>(this IObservable<T> source, Action<T> onNext, Action onCompleted) { return source.Subscribe(Observer.CreateSubscribeObserver(onNext, Stubs.Throw, onCompleted)); }
ImmutableReturnUnitObservable
クラスの Subscribe()
の引数には、Observer.CreateSubscribeObserver()
の戻り値が渡されます。
Observer.CreateSubscribeObserver()
の内部では、引数で渡した onNext
, onCompleted
を呼び出す Subject<T>
のインスタンスを生成しています。
// same as AnonymousObserver... class Subscribe<T> : IObserver<T> { readonly Action<T> onNext; readonly Action<Exception> onError; readonly Action onCompleted; int isStopped = 0; public Subscribe(Action<T> onNext, Action<Exception> onError, Action onCompleted) { this.onNext = onNext; this.onError = onError; this.onCompleted = onCompleted; } public void OnNext(T value) { if (isStopped == 0) { onNext(value); } } public void OnCompleted() { if (Interlocked.Increment(ref isStopped) == 1) { onCompleted(); } } // (コード一部省略・・・) }
以上で、Observable.ReturnUnit()
を Subscribe()
するときの処理の流れがわかりました。
ポイントは、ImmutableReturnUnitObservable.Subscribe()
の処理でした(再掲)。
internal class ImmutableReturnUnitObservable : IObservable<Unit>, IOptimizedObservable<Unit> { public IDisposable Subscribe(IObserver<Unit> observer) { observer.OnNext(Unit.Default); observer.OnCompleted(); return Disposable.Empty; } }
Subscribe()
すると即座に OnNext()
, OnCompleted()
を呼び出します。
引数として渡された observer
の参照を掴んでいませんので、サンプルコード上で生成しているクロージャ(onNext
, onCompleted
)はリークしていないことも確認できました。
参考
UniRx: Start
Observable.Start
メソッドは、引数で指定した Func<T>
関数から生成したデータを通知します。
using System; using UniRx; using UnityEngine; public class Main : MonoBehaviour { private IObservable<DateTime> _observable; void Start() { _observable = Observable.Start(() => DateTime.Now); } void Update() { if (Input.GetKeyDown(KeyCode.Space)) { // スペースキーを押すたびに Subscribe する _observable.Subscribe(e => Debug.Log(e), () => Debug.Log("OnCompleted")); } } }
実行結果です。スペースキーを押すたびに出力される時刻が更新されていますね。
2018/08/20 21:18:20 OnCompleted 2018/08/20 21:18:22 OnCompleted 2018/08/20 21:18:24 OnCompleted
参考
UniRx: Return
void Start() { var s = Observable.Return(100); s.Subscribe(e => Debug.Log(e), () => Debug.Log("OnCompleted")); }
実行結果です。
100 OnCompleted
UniRx の Return
の実装を読む。パフォーマンスを上げるために、通知する値の型に応じて実装をもっているようだ。
int の小さな値は内部でキャッシュしている。
参考
ToLookup 拡張メソッド
以前に Partition 関数 - C#練習日記 を作りましたが、 Linq には ToLookup 拡張メソッド があり、このメソッドを使うことで同様のグルーピングを行うことができます。
using System; using System.Linq; class Program { static void Main() { var xs = new[] { 1, 2, 3, 4, 5 }; // 偶数と奇数でグループ分けする var d = xs.ToLookup(e => e % 2 == 0); Console.WriteLine(string.Join(", ", d[true])); // 2, 4 Console.WriteLine(string.Join(", ", d[false])); // 1, 3, 5 // 正の整数とそれ以外でグループ分けする var d2 = xs.ToLookup(e => e > 0); Console.WriteLine(d2[true].Count()); // 5 Console.WriteLine(d2[false].Count()); // 0 } }
ToLookup
の戻り値は、ILookup<TKey, TElement>
です。
各キーに対する要素は IEnumerable<TElement>
で取得できます。
プロパティへの代入がコンストラクタ内のみなら、そのプロパティに private set を定義する必要はない
using System; class Person { public string Name { get; } // 代入はコンストラクタでのみ。private set; は不要 public int Age { get; } public Person(string name, int age) { Name = name; Age = age; } } class Program { public static void Main() { var p = new Person("Bob", 23); Console.WriteLine($"{p.Name} {p.Age}"); // Bob 23 // コンパイルエラー: The Property 'Person.Name' has no setter // p.Name = "Alice"; } }
N 番目の要素を O(1) で取得する Cycle クラスを作る
数列 a = { 0, 1, 0, -1, 0, 1, 0, -1, 0, 1, 0, -1, ... } のような 4 周期で繰り返す数列で、かつ N 番目の要素を O(1) で取得する Cycle クラスを作ってみました。
// 0, 1, ... を繰り返す数列 var zeroOne = new Cycle<int>(new[] { 0, 1 }); Console.WriteLine(string.Join(", ", zeroOne.Take(5))); //=> 0, 1, 0, 1, 0 Console.WriteLine(string.Join(", ", new[] { 0, 1, 2, 3, 5 }.Select(e => zeroOne[e]))); //=> 0, 1, 0, 1, 1 // 0, 1, 0, -1, ... を繰り返す数列 var seq = new Cycle<int>(new[] { 0, 1, 0, -1 }); Console.WriteLine(string.Join(", ", seq.Take(8))); //=> 0, 1, 0, -1, 0, 1, 0, -1 Console.WriteLine(string.Join(", ", new[] { 0, 1, 2, 3, 5, 7 }.Select(e => seq[e]))); //=> 0, 1, 0, -1, 1, -1
Cycle クラス:
class Cycle<T> : IEnumerable<T> { private readonly T[] _array; public Cycle(T[] array) { if (array == null) throw new ArgumentNullException(nameof(array)); if (array.Length == 0) throw new ArgumentException(nameof(array)); _array = array; } public IEnumerator<T> GetEnumerator() { while (true) { foreach (var x in _array) yield return x; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public T this[int index] => _array[index % _array.Length]; }
エラトステネスの篩
上の記事をみて、エラトステネスの篩を C# で書いてみました。
// 2, 3, 4, 5, ... static IEnumerable<int> Nums() { int n = 2; while (true) yield return n++; } static Func<int, bool> MakePrimeFilter(int prime, Func<int, bool> isPrime) { // x はこれまで登場した素数で割りきれない、かつ prime でも割り切れないならば、x は素数である return x => isPrime(x) && x % prime != 0; } static IEnumerable<int> Primes() { Func<int, bool> isPrime = n => true; foreach (var x in Nums()) { if (isPrime(x)) { yield return x; isPrime = MakePrimeFilter(x, isPrime); } } }
お試しコード。
public static void Main(string[] args) { Console.WriteLine(string.Join(", ", Primes().Take(10))); // => 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 Console.WriteLine(Primes().ElementAt(1000)); // 7927 // 10,000 より大きな最小の素数は? Console.WriteLine(Primes().SkipWhile(e => e <= 10000).First()); // 10007 }
※ このコードは効率はよくありません。関数を重ねるコードの書き方の練習用です。
双子素数も求めてみます。
// 双子素数を求める static IEnumerable<Tuple<int, int>> Twins() { foreach (var pair in Primes().Zip(Primes().Skip(1), (a, b) => Tuple.Create(a, b))) { if (pair.Item1 + 2 == pair.Item2) yield return pair; } } public static void Main(string[] args) { var ts = Twins().Take(10); Console.WriteLine(string.Join(", ", ts.Select(e => $"({e.Item1}, {e.Item2})"))); // => (3, 5), (5, 7), (11, 13), (17, 19), (29, 31), (41, 43), (59, 61), (71, 73), (101, 103), (107, 109) }
元コード:
twins = filter ((2==) . uncurry subtract) $ s zip tail primes
上の ((2==) . uncurry subtract)
のところがよく分からなかったので分解してみます。
subtract
は、subtract a b
で b - a を計算します。
Prelude> :t subtract subtract :: Num a => a -> a -> a Prelude> subtract 1 5 4
uncurry
は、uncurry (+) (a, b)
で (+) a b
を計算します。
Prelude> :t uncurry uncurry :: (a -> b -> c) -> (a, b) -> c Prelude> uncurry (+) (1, 3) 4
(2==)
は、2 と等しいかを判定する関数です。
Prelude> :t (2==) (2==) :: (Num a, Eq a) => a -> Bool Prelude> (2==) 2 True Prelude> (2==) 3 False
よって、(2==) . uncurry subtract
は、タプル (a, b) を受け取って substract a b
すなわち b - a
が 2 と等しいかを判定する関数、ということになります。
Prelude> a = (2==) . uncurry subtract Prelude> :t a a :: (Eq a, Num a) => (a, a) -> Bool Prelude> a (1, 3) True Prelude> a (1, 4) False