Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions Source/FunicularSwitch.Generators/Generation/GeneralGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,14 @@ private static void WriteMethod(MethodGenerationInfo info, CSharpBuilder cs)

private static void WriteMonadInterfaceImplementation(MonadImplementationGenerationInfo data, CSharpBuilder cs)
{
var typeParameters = Enumerable.Range(0, data.Monad.ExtraArity).Select(x => $"T{x}").ToList();

var interfaceA = InterfaceFn("A");
var interfaceB = InterfaceFn("B");
var typeNameA = data.GenericTypeName(["A"]);
var typeNameB = data.GenericTypeName(["B"]);
var monadTypeNameA = data.Monad.GenericTypeName(["A"]);
var monadTypeNameB = data.Monad.GenericTypeName(["B"]);
var typeNameA = data.GenericTypeName([..typeParameters, "A"]);
var typeNameB = data.GenericTypeName([..typeParameters, "B"]);
var monadTypeNameA = data.Monad.GenericTypeName([..typeParameters, "A"]);
var monadTypeNameB = data.Monad.GenericTypeName([..typeParameters, "B"]);

cs.WriteLine($"private readonly record struct {typeNameA}({monadTypeNameA} M) : {interfaceA}");
using var _ = cs.Scope();
Expand All @@ -63,9 +65,9 @@ private static void WriteMonadInterfaceImplementation(MonadImplementationGenerat
WriteCommonMethodAttributes(cs);
cs.WriteLine($"public static implicit operator {monadTypeNameA}({typeNameA} ma) => ma.M;");
WriteCommonMethodAttributes(cs);
cs.WriteLine($"public {interfaceB} Return<B>(B a) => ({typeNameB}){data.Monad.ReturnMethod.Invoke(["B"], ["a"])};");
cs.WriteLine($"public {interfaceB} Return<B>(B a) => ({typeNameB}){data.Monad.ReturnMethod.Invoke([..typeParameters, "B"], ["a"])};");
WriteCommonMethodAttributes(cs);
cs.WriteLine($"public {interfaceB} Bind<B>(global::System.Func<A, {interfaceB}> fn) => ({typeNameB}){data.Monad.BindMethod.Invoke(["A", "B"], ["M", $"a => ({monadTypeNameB})({typeNameB})fn(a)"])};");
cs.WriteLine($"public {interfaceB} Bind<B>(global::System.Func<A, {interfaceB}> fn) => ({typeNameB}){data.Monad.BindMethod.Invoke([..typeParameters, "A", "B"], ["M", $"a => ({monadTypeNameB})({typeNameB})fn(a)"])};");
WriteCommonMethodAttributes(cs);
cs.WriteLine("public B Cast<B>() => (B)(object)M;");

Expand Down
76 changes: 72 additions & 4 deletions Source/FunicularSwitch.Generators/Generation/MonadMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public static IReadOnlyList<MethodGenerationInfo> CreateExtendMonadMethods(
..Return(genericTypeName, monad),
..BindMethods(),
..MapMethods(),
..CombineMethods(),
..FlattenMethods(),
];
return methodGenerationInfos
.Distinct(MethodGenerationInfo.SignatureComparer.Instance)
Expand All @@ -69,6 +71,12 @@ IEnumerable<MethodGenerationInfo> MapMethods() =>
..Map("Map", genericTypeName, monad),
..Map("Select", genericTypeName, monad),
];

IEnumerable<MethodGenerationInfo> CombineMethods() =>
Combine(genericTypeName, monad, 12);

IEnumerable<MethodGenerationInfo> FlattenMethods() =>
Flatten("Flatten", genericTypeName, monad);
}

private static IEnumerable<MethodGenerationInfo> AsyncVariants(string parameterName, Func<string, MethodGenerationInfo> fn)
Expand Down Expand Up @@ -146,13 +154,73 @@ IEnumerable<MethodGenerationInfo> ForFnType(Func<IReadOnlyList<TypeInfo>, TypeIn
));
}

private static IEnumerable<MethodGenerationInfo> Combine(ConstructType genericTypeName, MonadInfo chainedMonad, int maxCount)
{
var mapMethod = new InvokeMethod((t, p) => $"{p[0]}.Map<{string.Join(", ", t)}>({p[1]})");

return Enumerable.Range(2, maxCount - 1)
.SelectMany(ForCount);

string Tuple(int count) => $"({string.Join(", ", Enumerable.Range(0, count).Select(i => $"S{i}"))})";

string CombineArgs(int count) => string.Join(", ", Enumerable.Range(0, count).Select(i => $"s{i}"));

IEnumerable<MethodGenerationInfo> ForCount(int count)
{
var typeParameters = Enumerable.Range(0, count).Select(x => $"S{x}").ToList();
yield return Create(
chainedMonad.ExtraArity,
$"({string.Join(", ", typeParameters)})",
genericTypeName,
typeParameters,
t => Enumerable.Range(0, count)
.Select(i => new ParameterGenerationInfo(
genericTypeName([..t, $"S{i}"]), $"s{i}"))
.ToList(),
"Combine",
t => count > 2
? CombineTail(t)
: CombineHead(t)
);

string CombineHead(IReadOnlyList<TypeInfo> t) =>
chainedMonad.BindMethod.Invoke([..t, "S0", "(S0, S1)"], ["s0", $"v0 => {mapMethod([..t, "S1", "(S0, S1)"], ["s1", "v1 => (v0, v1)"])}"]);

string CombineTail(IReadOnlyList<TypeInfo> t)
{
var fromTupleType = Tuple(count - 1);
var toTupleType = Tuple(count);
var lastType = $"S{count - 1}";
var lastArg = $"s{count - 1}";
var mapFn = $"last => ({string.Join(", ", Enumerable.Range(1, count - 1).Select(i => $"prev.Item{i}"))}, last)";
return chainedMonad.BindMethod.Invoke([..t, fromTupleType, toTupleType], [$"Combine({CombineArgs(count - 1)})", $"prev => {mapMethod([..t, lastType, toTupleType], [lastArg, mapFn])}"]);
}
}
}

private static IEnumerable<MethodGenerationInfo> Flatten(string name, ConstructType genericTypeName, MonadInfo monad) =>
AsyncVariants("ma", p => Create(
monad.ExtraArity,
"A",
genericTypeName,
["A"],
t =>
[
new ParameterGenerationInfo(genericTypeName([..t, genericTypeName([..t, "A"])]), "ma", true),
],
name,
t => $"{p}.{monad.BindMethod.Name}([{Constants.DebuggerStepThroughAttribute}](a) => a)"
));

private static IEnumerable<MethodGenerationInfo> Lift(ConstructType genericTypeName, MonadInfo chainedMonad, MonadInfo outerMonad, MonadInfo innerMonad) =>
AsyncVariants("ma", p => new(
genericTypeName(["A"]),
AsyncVariants("ma", p => Create(
chainedMonad.ExtraArity,
"A",
genericTypeName,
["A"],
[new ParameterGenerationInfo(outerMonad.GenericTypeName(["A"]), "ma")],
t => [new ParameterGenerationInfo(outerMonad.GenericTypeName([..t.Take(outerMonad.ExtraArity), "A"]), "ma")],
"Lift",
$"{outerMonad.BindMethod.Invoke(["A", $"{innerMonad.GenericTypeName(["A"])}"], [p, $"[{Constants.DebuggerStepThroughAttribute}](a) => {chainedMonad.ReturnMethod.Invoke(["A"], ["a"])}"])}"
t => $"{outerMonad.BindMethod.Invoke([..t.Take(outerMonad.ExtraArity), "A", $"{innerMonad.GenericTypeName([..t.Skip(outerMonad.ExtraArity), "A"])}"], [p, $"[{Constants.DebuggerStepThroughAttribute}](a) => {chainedMonad.ReturnMethod.Invoke([..t, "A"], ["a"])}"])}"
));

private static IEnumerable<MethodGenerationInfo> Map(string name, ConstructType genericTypeName, MonadInfo monad) =>
Expand Down
16 changes: 9 additions & 7 deletions Source/FunicularSwitch.Generators/Transformer/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ public static (string filename, string source) Emit(

private static void WriteGenericMonad(GenericMonadGenerationInfo data, CSharpBuilder cs)
{
var nestedTypeName = data.Monad.GenericTypeName([data.TypeParameter]);
var monadInterface = $"global::FunicularSwitch.Transformers.Monad<{data.TypeParameter}>";
var altTypeParameter = $"{data.TypeParameter}_";
var monadInterfaceAlt = $"global::FunicularSwitch.Transformers.Monad<{altTypeParameter}>";
var nestedTypeName = data.Monad.GenericTypeName(data.TypeParameters.Select(TypeInfo.Parameter).ToList());
var extraTypeParametersString = string.Concat(data.TypeParameters.Take(data.TypeParameters.Count - 1).Select(x => $"{x}, "));
var valueTypeParameter = data.TypeParameters.Last();
var monadInterface = $"global::FunicularSwitch.Transformers.Monad<{valueTypeParameter}>";
var altValueTypeParameter = $"{data.TypeParameters.Last()}_";
var monadInterfaceAlt = $"global::FunicularSwitch.Transformers.Monad<{altValueTypeParameter}>";
using var _ = new Scope(cs, $"{Types.DetermineAccessModifier(data.Accessibility)} {data.Modifier} {data.TypeNameWithTypeParameters}({nestedTypeName} M) : {monadInterface}");

if (!data.IsRecord)
Expand All @@ -45,10 +47,10 @@ private static void WriteGenericMonad(GenericMonadGenerationInfo data, CSharpBui
GeneralGenerator.WriteCommonMethodAttributes(cs);
cs.WriteLine($"public static implicit operator {nestedTypeName}({data.TypeNameWithTypeParameters} ma) => ma.M;");
GeneralGenerator.WriteCommonMethodAttributes(cs);
cs.WriteLine($"{monadInterfaceAlt} {monadInterface}.Return<{altTypeParameter}>({altTypeParameter} a) => {data.TypeName}.{data.Monad.ReturnMethod.Name}(a);");
cs.WriteLine($"{monadInterfaceAlt} {monadInterface}.Return<{altValueTypeParameter}>({altValueTypeParameter} a) => {data.TypeName}.{data.Monad.ReturnMethod.Name}<{extraTypeParametersString}{altValueTypeParameter}>(a);");
GeneralGenerator.WriteCommonMethodAttributes(cs);
cs.WriteLine($"{monadInterfaceAlt} {monadInterface}.Bind<{altTypeParameter}>(global::System.Func<{data.TypeParameter}, {monadInterfaceAlt}> fn) => this.{data.Monad.BindMethod.Name}(a => ({data.TypeName}<{altTypeParameter}>)fn(a));");
cs.WriteLine($"{monadInterfaceAlt} {monadInterface}.Bind<{altValueTypeParameter}>(global::System.Func<{valueTypeParameter}, {monadInterfaceAlt}> fn) => this.{data.Monad.BindMethod.Name}(a => ({data.TypeName}<{extraTypeParametersString}{altValueTypeParameter}>)fn(a));");
GeneralGenerator.WriteCommonMethodAttributes(cs);
cs.WriteLine($"{altTypeParameter} {monadInterface}.Cast<{altTypeParameter}>() => ({altTypeParameter})(object)M;");
cs.WriteLine($"{altValueTypeParameter} {monadInterface}.Cast<{altValueTypeParameter}>() => ({altValueTypeParameter})(object)M;");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ internal record GenericMonadGenerationInfo(
Accessibility Accessibility,
string Modifier,
string TypeName,
string TypeParameter,
IReadOnlyList<string> TypeParameters,
string TypeNameWithTypeParameters,
bool IsRecord,
MonadInfo Monad);
40 changes: 25 additions & 15 deletions Source/FunicularSwitch.Generators/Transformer/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,26 +56,36 @@ from chainedMonads in transformerTypes
))
select transformMonadData;

static ConstructType ChainGenericTypeName(ConstructType outer, ConstructType inner) =>
x => outer([inner(x)]);
static ConstructType ChainGenericTypeName(MonadInfo outer, MonadInfo inner) =>
x => outer.GenericTypeName(
[..x.Take(outer.ExtraArity), inner.GenericTypeName([..x.Skip(outer.ExtraArity)])]);

static MethodInfo CombineReturn(MethodInfo outer, MonadInfo inner) =>
static MethodInfo CombineReturn(MonadInfo outer, MonadInfo inner) =>
new MethodInfo(
DetermineMethodName(outer.Name, inner.ReturnMethod.Name, "Return"),
(t, p) => $"{outer.Invoke([inner.GenericTypeName([t[0]])], [inner.ReturnMethod.Invoke(t, p)])}");
DetermineMethodName(outer.ReturnMethod.Name, inner.ReturnMethod.Name, "Return"),
(t, p) =>
$"{outer.ReturnMethod.Invoke([..t.Take(outer.ExtraArity), inner.GenericTypeName([..t.Skip(outer.ExtraArity)])], [inner.ReturnMethod.Invoke([..t.Skip(outer.ExtraArity)], p)])}");

static MethodInfo TransformBind(MonadInfo outer, MonadInfo inner, string transformerTypeName, ConstructType outerInterfaceImplName)
{
var chainedGenericType = ChainGenericTypeName(outer.GenericTypeName, inner.GenericTypeName);
var chainedGenericType = ChainGenericTypeName(outer, inner);

return new MethodInfo(
DetermineMethodName(outer.BindMethod.Name, inner.BindMethod.Name, "Bind"),
(t, p) =>
{
var ma = $"({outerInterfaceImplName([inner.GenericTypeName([t[0]])])}){p[0]}";
var fn = $"[{Constants.DebuggerStepThroughAttribute}](a) => ({outerInterfaceImplName([inner.GenericTypeName([t[1]])])})(new global::System.Func<{t[0]}, {chainedGenericType([t[1]])}>({p[1]}).Invoke(a))"; // A -> Monad<X<B>>

var call = $"{transformerTypeName}.BindT<{t[0]}, {t[1]}>({ma}, {fn}).Cast<{chainedGenericType([t[1]])}>()";
var extraTypeArgs = t.Take(outer.ExtraArity + inner.ExtraArity).ToList();
var fromType = t.Skip(outer.ExtraArity + inner.ExtraArity).First();
var toType = t.Last();
var fromInterfaceType = outerInterfaceImplName([..t.Take(outer.ExtraArity), inner.GenericTypeName([..t.Skip(outer.ExtraArity).Take(inner.ExtraArity), fromType])]);
var fromNestedType = outer.GenericTypeName([
..extraTypeArgs.Take(outer.ExtraArity),
inner.GenericTypeName([..extraTypeArgs.Skip(outer.ExtraArity), fromType]),
]);
var ma = $"({fromInterfaceType})({fromNestedType}){p[0]}";
var fn = $"[{Constants.DebuggerStepThroughAttribute}](a) => ({outerInterfaceImplName([..t.Take(outer.ExtraArity), inner.GenericTypeName([..t.Skip(outer.ExtraArity).Take(inner.ExtraArity), toType])])})(new global::System.Func<{fromType}, {chainedGenericType([..t.Take(outer.ExtraArity + inner.ExtraArity), toType])}>({p[1]}).Invoke(a))"; // A -> Monad<X<B>>

var call = $"{transformerTypeName}.BindT<{string.Join(", ", t.Skip(outer.ExtraArity))}>({ma}, {fn}).Cast<{chainedGenericType([..t.Take(outer.ExtraArity + inner.ExtraArity), toType])}>()";
return call;
});
}
Expand All @@ -92,9 +102,9 @@ static GenerationResult<MonadInfo> TransformMonad(MonadInfo outer, INamedTypeSym
outerInterfaceImplementation?.GenericTypeName ?? outer.GenericTypeName;

var transformedMonad = new MonadInfo(
ChainGenericTypeName(outer.GenericTypeName, innerMonad.GenericTypeName),
ChainGenericTypeName(outer, innerMonad),
outer.ExtraArity + innerMonad.ExtraArity,
CombineReturn(outer.ReturnMethod, innerMonad),
CombineReturn(outer, innerMonad),
TransformBind(outer, innerMonad, transformerTypeName, outerInterfaceName));
return transformedMonad;
});
Expand All @@ -106,14 +116,14 @@ private static GenericMonadGenerationInfo BuildGenericMonad(
{
var typeModifier = DetermineTypeModifier(transformedMonadSymbol);
var isRecord = transformedMonadSymbol.IsRecord;
var typeParameter = transformedMonadSymbol.TypeArguments[0].Name;
var typeParameters = transformedMonadSymbol.TypeArguments.Select(x => x.Name).ToList();

return new(
transformedMonadSymbol.GetActualAccessibility(),
typeModifier,
transformedMonadSymbol.Name,
typeParameter,
$"{transformedMonadSymbol.Name}<{typeParameter}>",
typeParameters,
$"{transformedMonadSymbol.Name}<{string.Join(", ", typeParameters)}>",
isRecord,
chainedMonad
);
Expand Down
Loading
Loading