Skip to content

Commit

Permalink
Optics for immutable collections
Browse files Browse the repository at this point in the history
  • Loading branch information
danslapman committed Nov 19, 2023
1 parent 53d0e19 commit aee56d0
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 0 deletions.
58 changes: 58 additions & 0 deletions src/LeviySoft.Visor.Tests/PropertyTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.Collections.Immutable;
using Shouldly;
using Xunit;
using LeviySoft.Visor.Collections.Immutable;

namespace LeviySoft.Visor.Tests;

public class PropertyTests
{
[Fact]
public void AtIndexTest()
{
var list = ImmutableList.Create<int?>(1, 2, 3);
var sut = Property.IImmutableList.AtIndex<int?>(1);

sut.MaybeGet(list).ShouldBe(2);
sut.MaybeGet(ImmutableList<int?>.Empty).ShouldBeNull();

sut.Update(i => i * 2)(list).ShouldBe(ImmutableList.Create<int?>(1, 4, 3));
sut.Update(i => i * 2)(ImmutableList<int?>.Empty).ShouldBe(ImmutableList<int?>.Empty);
}

[Fact]
public void FirstTest()
{
var list = ImmutableList.Create<string?>("a", "bb", "ccc");
var sut = Property.IImmutableList.First<string?>(s => (s?.Length % 2) == 0);

sut.MaybeGet(list).ShouldBe("bb");
sut.MaybeGet(ImmutableList<string?>.Empty).ShouldBeNull();

sut.Update(s => s + s?.Length)(list).ShouldBe(ImmutableList.Create<string?>("a", "bb2", "ccc"));
sut.Update(s => s + s?.Length)(ImmutableList<string?>.Empty).ShouldBe(ImmutableList<string?>.Empty);
}

[Fact]
public void AtKeyTest()
{
var bld = ImmutableDictionary.CreateBuilder<string, int>();
bld.Add("a", 1);
bld.Add("b", 2);
var map = bld.ToImmutable();
var sut = Property.IImmutableDictionary.AtKey<string, int>("b");
var sut2 = Property.IImmutableDictionary.AtKey("b", 42);

sut.MaybeGet(map).ShouldBe(2);
sut.MaybeGet(ImmutableDictionary<string, int>.Empty).ShouldBe(0);

sut2.MaybeGet(map).ShouldBe(2);
sut2.MaybeGet(ImmutableDictionary<string, int>.Empty).ShouldBe(42);

sut.Update(v => v * 2)(map)["b"].ShouldBe(4);
sut.Update(v => v * 2)(ImmutableDictionary<string, int>.Empty).ShouldBe(ImmutableDictionary<string, int>.Empty);

sut2.Update(v => v * 2)(map)["b"].ShouldBe(4);
sut2.Update(v => v * 2)(ImmutableDictionary<string, int>.Empty)["b"].ShouldBe(84);
}
}
54 changes: 54 additions & 0 deletions src/LeviySoft.Visor/Collections/Immutable/Property.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using LeviySoft.Visor.Internal;

namespace LeviySoft.Visor.Collections.Immutable;

/// <summary>
/// Optics for System.Collections.Immutable
/// </summary>
public static class Property
{
/// <summary>
/// Optics for System.Collections.Immutable.IImmutableList
/// </summary>
public static class IImmutableList
{
public static IProperty<IImmutableList<T>, T> AtIndex<T>(int index) =>
Property<IImmutableList<T>, T>.New(
list => index < list.Count ? list[index] : default,
upd => list => index < list.Count ? list.SetItem(index, upd(list[index])) : list
);

public static IProperty<IImmutableList<T>, T> First<T>() where T : class? => AtIndex<T>(0);

public static IProperty<IImmutableList<T>, T> First<T>(Func<T, bool> predicate) where T : class? =>
Property<IImmutableList<T>, T>.New(
list => list.FirstOrDefault(predicate),
upd => list =>
{
var index = list.FindIndex(predicate);
return index >= 0 ? list.SetItem(index, upd(list[index])) : list;
}
);
}

/// <summary>
/// Optics for System.Collections.Immutable.IImmutableDictionary
/// </summary>
public static class IImmutableDictionary
{
public static IProperty<IImmutableDictionary<K, V>, V> AtKey<K, V>(K key) =>
Property<IImmutableDictionary<K, V>, V>.New(
map => map.ContainsKey(key) ? map[key] : default,
upd => map => map.ContainsKey(key) ? map.SetItem(key, upd(map[key])) : map
);

public static IProperty<IImmutableDictionary<K, V>, V> AtKey<K, V>(K key, V defaultVal) =>
Property<IImmutableDictionary<K, V>, V>.New(
map => map.ContainsKey(key) ? map[key] : defaultVal,
upd => map => map.SetItem(key, upd(map.ContainsKey(key) ? map[key] : defaultVal))
);
}
}
37 changes: 37 additions & 0 deletions src/LeviySoft.Visor/Internal/Utils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;

namespace LeviySoft.Visor.Internal;

internal static class Utils
{
public static int FindIndex<T>(this IEnumerable<T> source, Predicate<T> predicate)
{
int index = 0;
foreach (T elem in source)
{
if (predicate(elem))
{
return index;
}
++index;
}

return -1;
}

public static int FindIndex<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
int index = 0;
foreach (T elem in source)
{
if (predicate(elem))
{
return index;
}
++index;
}

return -1;
}
}

0 comments on commit aee56d0

Please sign in to comment.