diff --git a/.gitignore b/.gitignore index ce89292..29bbcb7 100644 --- a/.gitignore +++ b/.gitignore @@ -416,3 +416,4 @@ FodyWeavers.xsd *.msix *.msm *.msp +whyfp90.pdf diff --git a/Mark-CSharp/.gitignore b/Mark-CSharp/.gitignore new file mode 100644 index 0000000..1c97ff6 --- /dev/null +++ b/Mark-CSharp/.gitignore @@ -0,0 +1,8 @@ +#Ignore any test result output from unit testing +/[T][t]est[R][r]esults/ + +# Ignore .idea folder +/.idea/ + +# Ignore any personal settings +*.[D][d]ot[S][s]ettings.* diff --git a/Mark-CSharp/WhyFunctional.Tests/ListOfExtensionsTests.cs b/Mark-CSharp/WhyFunctional.Tests/ListOfExtensionsTests.cs new file mode 100644 index 0000000..17f1ad4 --- /dev/null +++ b/Mark-CSharp/WhyFunctional.Tests/ListOfExtensionsTests.cs @@ -0,0 +1,201 @@ +using FluentAssertions; + +namespace WhyFunctional.Tests; + +[TestClass] +public class ListOfExtensionsTests +{ + [TestMethod] + [DataRow(new int[] { 1 })] + [DataRow(new int[] { 10 })] + [DataRow(new int[] { 1, 2, 3 })] + public void VerifySumExtension(int[] values) + { + int expected = 0; + ListOf sut = ListOf.Nil(); + foreach (int value in values.Reverse()) + { + sut = ListOf.Cons(value, sut); + expected += value; + } + + var result = sut.Sum(); + result.Should().Be(expected); + } + + [TestMethod] + [DataRow(new int[] { 1 })] + [DataRow(new int[] { 10 })] + [DataRow(new int[] { 1, 2, 3 })] + public void VerifyProductExtension(int[] values) + { + int expected = 1; + ListOf sut = ListOf.Nil(); + foreach (int value in values.Reverse()) + { + sut = ListOf.Cons(value, sut); + expected *= value; + } + + var result = sut.Product(); + result.Should().Be(expected); + } + + [TestMethod] + [DataRow(new int[] { })] + [DataRow(new int[] { 1 })] + [DataRow(new int[] { 1, 2, 3, 5, 7 })] + public void VerifyBuildListExtension(int[] values) + { + ListOf sut = values.BuildList(); + + sut.Should().NotBeNull(); + sut.ToArray.Length.Should().Be(values.Length); + sut.ToArray.Should().ContainInOrder(values); + } + + [TestMethod] + [DataRow(new bool[] { }, false)] + [DataRow(new bool[] { false }, false)] + [DataRow(new bool[] { false, false, true }, true)] + [DataRow(new bool[] { false, true, false }, true)] + public void VerifyAnyTrueExtension(bool[] values, bool expected) + { + ListOf sut = values.BuildList(); + + sut.Should().NotBeNull(); + sut.ToArray.Length.Should().Be(values.Length); + sut.ToArray.Should().ContainInOrder(values); + ListOfExtensions.AnyTrue(sut).Should().Be(expected); + } + + [TestMethod] + [DataRow(new bool[] { }, true)] + [DataRow(new bool[] { false }, false)] + [DataRow(new bool[] { false, false, true }, false)] + [DataRow(new bool[] { false, true, false }, false)] + [DataRow(new bool[] { true }, true)] + [DataRow(new bool[] { true, true }, true)] + [DataRow(new bool[] { true, true, false }, false)] + public void VerifyAllTrueExtension(bool[] values, bool expected) + { + ListOf sut = values.BuildList(); + + sut.Should().NotBeNull(); + sut.ToArray.Length.Should().Be(values.Length); + sut.ToArray.Should().ContainInOrder(values); + ListOfExtensions.AllTrue(sut).Should().Be(expected); + } + + [TestMethod] + [DataRow(new int[] { })] + [DataRow(new int[] { 1 })] + [DataRow(new int[] { 10, 20, 30 })] + public void VerifyCopyExtension(int[] values) + { + ListOf sut = values.BuildList(); + + ListOf copy = sut.Copy(); + + sut.Should().NotBeNull(); + copy.Should().NotBeNull(); + copy.Should().NotBeSameAs(sut); + copy.ToArray.Length.Should().Be(sut.ToArray.Length); + copy.ToArray.Should().ContainInOrder(sut.ToArray); + } + + [TestMethod] + [DataRow(new int[] { }, new int[] { })] + [DataRow(new int[] { 1 }, new int[] { 2 })] + [DataRow(new int[] { 1 }, new int[] { })] + [DataRow(new int[] { }, new int[] { 2 })] + [DataRow(new int[] { 1, 11 }, new int[] { 2, 22 })] + [DataRow(new int[] { 1, 2 }, new int[] { 3, 4 })] + public void VerifyAppendExtension(int[] lhs, int[] rhs) + { + ListOf first = lhs.BuildList(); + ListOf second = rhs.BuildList(); + + first.Should().NotBeNull(); + second.Should().NotBeNull(); + + int[] expectedAppend = lhs.Concat(rhs).ToArray(); + + ListOf sut = first.Append(second); + + sut.Should().NotBeNull(); + sut.ToArray.Length.Should().Be(expectedAppend.Length); + sut.ToArray.Should().ContainInOrder(expectedAppend); + } + + [TestMethod] + [DataRow(new int[] { }, 0)] + [DataRow(new int[] { 1 }, 1)] + [DataRow(new int[] { 1, 1 }, 2)] + [DataRow(new int[] { 1, 1, 1, 1 }, 4)] + public void VerifyLengthExtension(int[] values, int expectedLength) + { + ListOf sut = values.BuildList(); + + sut.Should().NotBeNull(); + sut.Length().Should().Be(expectedLength); + } + + [TestMethod] + [DataRow(new int[] { })] + [DataRow(new int[] { 1 })] + [DataRow(new int[] { 1, 2 })] + [DataRow(new int[] { 1, 2, 3 })] + [DataRow(new int[] { 4, 3, 2, 1 })] + public void VerifyDoubleAllExtension(int[] values) + { + ListOf initial = values.BuildList(); + int[] expected = values.Select(v => 2 * v).ToArray(); + + initial.Should().NotBeNull(); + + ListOf sut = initial.DoubleAll(); + sut.Should().NotBeNull(); + sut.Should().NotBeSameAs(initial); + sut.ToArray.Length.Should().Be(expected.Length); + sut.ToArray.Should().ContainInOrder(expected); + } + [TestMethod] + [DataRow(new int[] { })] + [DataRow(new int[] { 1 })] + [DataRow(new int[] { 1, 2 })] + [DataRow(new int[] { 1, 2, 3 })] + [DataRow(new int[] { 4, 3, 2, 1 })] + public void VerifyDoubleAllExExtension(int[] values) + { + ListOf initial = values.BuildList(); + int[] expected = values.Select(v => 2 * v).ToArray(); + + initial.Should().NotBeNull(); + + ListOf sut = initial.DoubleAllEx(); + sut.Should().NotBeNull(); + sut.Should().NotBeSameAs(initial); + sut.ToArray.Length.Should().Be(expected.Length); + sut.ToArray.Should().ContainInOrder(expected); + } + [TestMethod] + [DataRow(new int[] { })] + [DataRow(new int[] { 1 })] + [DataRow(new int[] { 1, 2 })] + [DataRow(new int[] { 1, 2, 3 })] + [DataRow(new int[] { 4, 3, 2, 1 })] + public void VerifyTripleAllExtension(int[] values) + { + ListOf initial = values.BuildList(); + int[] expected = values.Select(v => 3 * v).ToArray(); + + initial.Should().NotBeNull(); + + ListOf sut = initial.TripleAll(); + sut.Should().NotBeNull(); + sut.Should().NotBeSameAs(initial); + sut.ToArray.Length.Should().Be(expected.Length); + sut.ToArray.Should().ContainInOrder(expected); + } +} \ No newline at end of file diff --git a/Mark-CSharp/WhyFunctional.Tests/ListOfTests.cs b/Mark-CSharp/WhyFunctional.Tests/ListOfTests.cs new file mode 100644 index 0000000..5076487 --- /dev/null +++ b/Mark-CSharp/WhyFunctional.Tests/ListOfTests.cs @@ -0,0 +1,95 @@ +using FluentAssertions; + +namespace WhyFunctional.Tests; + +[TestClass] +public sealed class ListOfTests +{ + [TestMethod] + public void VerifyNilProducesEmptyResult() + { + ListOf sut = ListOf.Nil(); + + sut.Should().NotBeNull(); + sut.ToArray.Should().BeEmpty(); + sut.IsNil.Should().BeTrue(); + } + + [TestMethod] + public void VerifySingleElement() + { + ListOf sut = ListOf.Cons(1, ListOf.Nil()); + + sut.Should().NotBeNull(); + int[] values = sut.ToArray; + values.Should().NotBeEmpty(); + values.Should().HaveCount(1); + values.Should().Contain(1); + sut.IsNil.Should().BeFalse(); + } + + [TestMethod] + public void VerifyThreeElements() + { + ListOf sut = ListOf.Cons(1, ListOf.Cons(2, ListOf.Cons(3, ListOf.Nil()))); + + sut.Should().NotBeNull(); + int[] values = sut.ToArray; + values.Should().NotBeEmpty(); + values.Should().HaveCount(3); + values.Should().Contain([1, 2, 3]); + sut.IsNil.Should().BeFalse(); + } + + [TestMethod] + [DataRow(new int[] { 1 }, 1)] + [DataRow(new int[] { 13, 27, 3 }, 13)] + public void VerifyHeadProperty(int[] values, int expected) + { + ListOf sut = ListOf.Nil(); + foreach (int value in values.Reverse()) + { + sut = ListOf.Cons(value, sut); + } + + sut.Should().NotBeNull(); + int[] sutValues = sut.ToArray; + sutValues.Should().HaveCount(values.Length); + sutValues.Should().Contain(expected); + sut.IsNil.Should().BeFalse(); + sut.Head.Should().Be(expected); + } + + public static IEnumerable VerifyTailTestData() + { + var tv_1 = ListOf.Cons(1, ListOf.Nil()); + var tv_3 = ListOf.Cons(3, ListOf.Nil()); + var tv_27 = ListOf.Cons(27, tv_3); + var tv_13 = ListOf.Cons(13, tv_27); + return + [ + [tv_1.ToArray, tv_1.Head, Array.Empty()], + [tv_13.ToArray, tv_13.Head, tv_27.ToArray] + ]; + } + + [TestMethod] + [DynamicData(nameof(VerifyTailTestData), DynamicDataSourceType.Method)] + public void VerifyTailProperty(int[] values, int expectedHead, int[] expectedTail) + { + ListOf sut = ListOf.Nil(); + foreach (int value in values.Reverse()) + { + sut = ListOf.Cons(value, sut); + } + + sut.Should().NotBeNull(); + int[] sutValues = sut.ToArray; + sutValues.Should().HaveCount(values.Length); + sutValues.Should().Contain(expectedHead); + sut.IsNil.Should().BeFalse(); + sut.Head.Should().Be(expectedHead); + sut.Tail.ToArray.Should().HaveCount(expectedTail.Length); + sut.Tail.ToArray.Should().ContainInOrder(expectedTail); + } +} \ No newline at end of file diff --git a/Mark-CSharp/WhyFunctional.Tests/MSTestSettings.cs b/Mark-CSharp/WhyFunctional.Tests/MSTestSettings.cs new file mode 100644 index 0000000..aaf278c --- /dev/null +++ b/Mark-CSharp/WhyFunctional.Tests/MSTestSettings.cs @@ -0,0 +1 @@ +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/Mark-CSharp/WhyFunctional.Tests/WhyFunctional.Tests.csproj b/Mark-CSharp/WhyFunctional.Tests/WhyFunctional.Tests.csproj new file mode 100644 index 0000000..2585ecb --- /dev/null +++ b/Mark-CSharp/WhyFunctional.Tests/WhyFunctional.Tests.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + latest + enable + enable + + + + + + + + + + + + + + + + + diff --git a/Mark-CSharp/WhyFunctional.sln b/Mark-CSharp/WhyFunctional.sln new file mode 100644 index 0000000..fb0701b --- /dev/null +++ b/Mark-CSharp/WhyFunctional.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WhyFunctional", "WhyFunctional\WhyFunctional.csproj", "{8F6902EC-8687-4F79-AA2F-F3A1CF7191F4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WhyFunctional.Tests", "WhyFunctional.Tests\WhyFunctional.Tests.csproj", "{BCDE153E-CE0B-461C-AC74-35B230F3DE56}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8F6902EC-8687-4F79-AA2F-F3A1CF7191F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F6902EC-8687-4F79-AA2F-F3A1CF7191F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F6902EC-8687-4F79-AA2F-F3A1CF7191F4}.Debug|x64.ActiveCfg = Debug|Any CPU + {8F6902EC-8687-4F79-AA2F-F3A1CF7191F4}.Debug|x64.Build.0 = Debug|Any CPU + {8F6902EC-8687-4F79-AA2F-F3A1CF7191F4}.Debug|x86.ActiveCfg = Debug|Any CPU + {8F6902EC-8687-4F79-AA2F-F3A1CF7191F4}.Debug|x86.Build.0 = Debug|Any CPU + {8F6902EC-8687-4F79-AA2F-F3A1CF7191F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F6902EC-8687-4F79-AA2F-F3A1CF7191F4}.Release|Any CPU.Build.0 = Release|Any CPU + {8F6902EC-8687-4F79-AA2F-F3A1CF7191F4}.Release|x64.ActiveCfg = Release|Any CPU + {8F6902EC-8687-4F79-AA2F-F3A1CF7191F4}.Release|x64.Build.0 = Release|Any CPU + {8F6902EC-8687-4F79-AA2F-F3A1CF7191F4}.Release|x86.ActiveCfg = Release|Any CPU + {8F6902EC-8687-4F79-AA2F-F3A1CF7191F4}.Release|x86.Build.0 = Release|Any CPU + {BCDE153E-CE0B-461C-AC74-35B230F3DE56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BCDE153E-CE0B-461C-AC74-35B230F3DE56}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BCDE153E-CE0B-461C-AC74-35B230F3DE56}.Debug|x64.ActiveCfg = Debug|Any CPU + {BCDE153E-CE0B-461C-AC74-35B230F3DE56}.Debug|x64.Build.0 = Debug|Any CPU + {BCDE153E-CE0B-461C-AC74-35B230F3DE56}.Debug|x86.ActiveCfg = Debug|Any CPU + {BCDE153E-CE0B-461C-AC74-35B230F3DE56}.Debug|x86.Build.0 = Debug|Any CPU + {BCDE153E-CE0B-461C-AC74-35B230F3DE56}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BCDE153E-CE0B-461C-AC74-35B230F3DE56}.Release|Any CPU.Build.0 = Release|Any CPU + {BCDE153E-CE0B-461C-AC74-35B230F3DE56}.Release|x64.ActiveCfg = Release|Any CPU + {BCDE153E-CE0B-461C-AC74-35B230F3DE56}.Release|x64.Build.0 = Release|Any CPU + {BCDE153E-CE0B-461C-AC74-35B230F3DE56}.Release|x86.ActiveCfg = Release|Any CPU + {BCDE153E-CE0B-461C-AC74-35B230F3DE56}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Mark-CSharp/WhyFunctional/ListOf.cs b/Mark-CSharp/WhyFunctional/ListOf.cs new file mode 100644 index 0000000..c3525b4 --- /dev/null +++ b/Mark-CSharp/WhyFunctional/ListOf.cs @@ -0,0 +1,108 @@ +namespace WhyFunctional; + +public class ListOf + where T : notnull +{ + #region Fields + + /// + /// Holds the first value for the object + /// + private readonly T _value = default(T)!; + + /// + /// Holds all other values for the object + /// + private readonly ListOf _others = null!; + + #endregion + + #region Properties + + /// + /// Helper property to indicate whether the value of the object is Nil + /// + public bool IsNil { get; } + + /// + /// Helper property converts all values into an array + /// + public T[] ToArray + { + get + { + return IsNil + ? Array.Empty() + : new T[] { _value }.Concat(_others.ToArray).ToArray(); + } + } + + /// + /// Returns the value at the "head" of the + /// + public T Head => _value; + + /// + /// Returns the value of the other items in the + /// + public ListOf Tail => _others; + + #endregion + + #region Constructors + + /// + /// Default ctor - creates an object equivalent to + /// + private ListOf() + { + IsNil = true; + } + + /// + /// Alternate ctor - must supply both values as non-null + /// + /// The first value + /// The other values + private ListOf(T value, ListOf others) + { + ArgumentNullException.ThrowIfNull(value, nameof(value)); + ArgumentNullException.ThrowIfNull(others, nameof(others)); + _value = value; + _others = others; + IsNil = false; + } + + #endregion + + #region Methods + + /// + /// Static method returns the equivalent of an object with an empty array + /// + /// The newly created empty object + public static ListOf Nil() + { + return new ListOf(); + } + + /// + /// Static "constructor" to initialise the object with two non-null values + /// + /// The first value for the object + /// All other values + /// The newly created object containing the specified values + public static ListOf Cons(T value, ListOf others) + { + return new ListOf(value, others); + } + + #endregion + + public override string ToString() + { + return IsNil + ? "Nil" + : $"[{Head}, {Tail}]"; + } +} \ No newline at end of file diff --git a/Mark-CSharp/WhyFunctional/ListOfExtensions.cs b/Mark-CSharp/WhyFunctional/ListOfExtensions.cs new file mode 100644 index 0000000..d820a13 --- /dev/null +++ b/Mark-CSharp/WhyFunctional/ListOfExtensions.cs @@ -0,0 +1,236 @@ +using System.Numerics; + +namespace WhyFunctional; + +public static class ListOfExtensions +{ + /// + /// Returns the summation of the values contained within + /// + /// A values to be summed + /// + /// + /// The summation result of values held in + public static TResult Sum(this ListOf list) + where T : INumber, IAdditiveIdentity, IAdditionOperators + where TResult : INumber + { + return list.FoldR(Add, TResult.Zero); + } + + /// + /// Returns the product of the values contained in + /// + /// A values to be summed + /// + /// + /// The product of values held in + public static TResult Product(this ListOf list) + where T : INumber, IMultiplicativeIdentity, IMultiplyOperators + where TResult : INumber + { + return list.FoldR(Mul, TResult.One); + } + + /// + /// An addition function for use in operations + /// + /// The first value + /// The second value + /// + /// + /// The summation of and + private static TResult Add(T a, TResult b) + where T : INumber, IAdditiveIdentity, IAdditionOperators + where TResult : INumber + { + return a + b; + } + + /// + /// A multiplication function for use as a operation + /// + /// The first value + /// The second value + /// + /// + /// The product of and + private static TResult Mul(T a, TResult b) + where T : INumber, IMultiplicativeIdentity, IMultiplyOperators + where TResult : INumber + { + return a * b; + } + + /// + /// Provides a Fold-Right function to perform the required operation () on the , starting with the initial value + /// + /// The to be manipulated + /// The function manipulating the + /// The initial value for the function + /// + /// + /// The result of applying over the + private static TResult FoldR(this ListOf list, Func fn, TResult initial) + where T : notnull + where TResult : notnull + { + return list.IsNil + ? initial + : fn(list.Head, list.Tail.FoldR(fn, initial)); + } + + /// + /// Helper method to create a from an array of values + /// + /// The values to be added to the output + /// + /// A object, containing all values in + public static ListOf BuildList(this T[] values) + where T : notnull + { + return values.Length > 0 + ? ListOf.Cons(values[0], BuildList(values[1..])) + : ListOf.Nil(); + } + + /// + /// Provides a logical OR operation for two values + /// + /// The first value + /// The second value + /// The logical OR product + private static bool LogicalOr(bool a, bool b) => a || b; + + /// + /// Provides a logical AND operation for two values + /// + /// The first value + /// The second value + /// The logical AND product + private static bool LogicalAnd(bool a, bool b) => a && b; + + /// + /// Determines whether any of the are true + /// + /// A object with boolean values + /// True if any values are true, otherwise false + public static bool AnyTrue(ListOf values) => values.FoldR(LogicalOr, false); + + /// + /// Determines whether ALL of the are true + /// + /// A object with boolean values + /// True if ALL values are true, otherwise false + public static bool AllTrue(ListOf values) => values.FoldR(LogicalAnd, true); + + /// + /// Copies the contents of into a new + /// + /// The list to be copied + /// + /// A copy of + public static ListOf Copy(this ListOf list) + where T : notnull + { + return list.FoldR(ListOf.Cons, ListOf.Nil()); + } + + /// + /// Appends the contents of to , returning a new + /// + /// The first part of the + /// The second part of the + /// + /// A new object containing elements of both and + public static ListOf Append(this ListOf first, ListOf second) + where T : notnull + { + return first.FoldR(ListOf.Cons, second); + } + + /// + /// Helper method to increment a counter for the length of a + /// + /// N/A + /// The counter value + /// + /// The incremented value of the counter + private static int Count(T left, int right) => ++right; + + /// + /// Determine the length of the + /// + /// The object to be measured + /// + /// The number of objects in the + public static int Length(this ListOf list) where T : notnull => list.FoldR(Count, 0); + + /// + /// Helper method to return the double of + /// + /// The value to be doubled + /// + /// The equivalent of 2* + private static T Double(T value) where T : INumber => value + value; + + /// + /// Helper method to double the value of the current object + /// + /// The value of the current + /// The contents of the current + /// + /// A new instance of , with the value doubled + private static ListOf Double(T head, ListOf tail) where T : INumber => + ListOf.Cons(Double(head), tail); + + /// + /// A method to double the values contained within and return a new object + /// + /// The object to bew iterated over + /// + /// A new object with the contents of , with their respective values doubled + public static ListOf DoubleAll(this ListOf list) where T : INumber => + list.FoldR(Double, ListOf.Nil()); + + /// + /// A method to map the function over the contents of , returning a new object + /// + /// The to be manipulated + /// The function to manipulate the individual values + /// + /// + /// The new object with manipulated values + private static ListOf Map(this ListOf list, Func fn) + where T : notnull + where TResult : notnull + { + return list.FoldR((head, tail) => ListOf.Cons(fn(head), tail), ListOf.Nil()); + } + + /// + /// A refactored method to double the values contained within and return a new object, using the method + /// + /// The object to bew iterated over + /// + /// A new object with the contents of , with their respective values doubled + public static ListOf DoubleAllEx(this ListOf list) + where T : INumber => list.Map(Double); + + /// + /// Helper method to treble the input + /// + /// The value to be trebled + /// + /// The equivalent of 3* + private static T Triple(T value) where T: INumber => value + Double(value); + + /// + /// Method to treble all values within , returning a new object + /// + /// The values to be trebled + /// + /// AQ new , with values which are triple the original + public static ListOf TripleAll(this ListOf list) + where T : INumber => list.Map(Triple); +} \ No newline at end of file diff --git a/Mark-CSharp/WhyFunctional/Program.cs b/Mark-CSharp/WhyFunctional/Program.cs new file mode 100644 index 0000000..ba5ab49 --- /dev/null +++ b/Mark-CSharp/WhyFunctional/Program.cs @@ -0,0 +1,9 @@ +namespace WhyFunctional; + +internal static class Program +{ + public static void Main(string[] args) + { + Console.WriteLine("Hello, World!"); + } +} diff --git a/Mark-CSharp/WhyFunctional/WhyFunctional.csproj b/Mark-CSharp/WhyFunctional/WhyFunctional.csproj new file mode 100644 index 0000000..206b89a --- /dev/null +++ b/Mark-CSharp/WhyFunctional/WhyFunctional.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + +