Skip to content
Merged
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
19 changes: 19 additions & 0 deletions .github/workflows/spelling.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Spelling

permissions:
contents: read

on: [pull_request]

env:
CLICOLOR: 1

jobs:
spelling:
name: Spell Check with Typos
runs-on: ubuntu-latest
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v4
- name: Spell Check Repo
uses: crate-ci/[email protected]
44 changes: 26 additions & 18 deletions Documentation/src/case-studies/calendar.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This case study is taken from [Component programming with ranges](https://wiki.dlang.org/Component_programming_with_ranges) by By H. S. Teoh written for [D](https://dlang.org/).

We shall use as example the classic task of laying out a yearly calendar on the console, such that given a particular year, the program will print out a number of lines that displays the 12 months in a nice grid layout, with numbers indicating each day within the month. Something like this:
We shall use as example the classic task of laying out a yearly calendar on the console, such that given a particular year, the program will print out a number of lines that displays the 12 months in a nice grid layout, with numbers indicating each day within the month. Something like this:

```
January February March
Expand Down Expand Up @@ -36,7 +36,7 @@ We shall use as example the classic task of laying out a yearly calendar on the
23 24 25 26 27 28 29 27 28 29 30 25 26 27 28 29 30 31
30 31
```

While intuitively straightforward, this task has many points of complexity.

Although generating all dates in a year is trivial, the order in which they must be processed is far from obvious. Since we're writing to the console, we're limited to outputting one line at a time; we can't draw one cell of the grid and then go back up a few lines, move a few columns over, and draw the next cell in the grid. We have to somehow print the first lines of all cells in the top row, followed by the second lines, then the third lines, etc., and repeat this process for each row in the grid. Of course, we could create an internal screen buffer that we can write to in arbitrary order, and then output this buffer line-by-line at the end, but this approach is not as elegant because it requires a much bigger memory footprint than is really necessary.
Expand All @@ -45,13 +45,13 @@ In any case, as a result of this mismatch between the structure of the calendar

With this level of complexity, writing our calendar program using the traditional ad hoc way of resolving structure conflicts will certainly result in very complex, hard-to-understand, and bug-prone code. There would not be much hope of getting any reusable pieces out of it.

Nonetheless the end result will look pretty simple, and it will be completley streamable.
Nonetheless the end result will look pretty simple, and it will be completely streamable.

## Create the date-range

There are obviously many ways to achieve this, you could write your own Implementation of `IEnumerable<T>` with a hand written `IEnumerator<T>` or you could simply write a function and take advantage of `yield` to create the iterator.

We want to avoid to write such a function and take advantage of the numerous generators availaible in Funcky.
We want to avoid to write such a function and take advantage of the numerous generators available in Funcky.

`Sequence.Successors` creates an infinite sequence of days, starting with January first of the given year. We take all the values in the same year, so this sequence should yield all the days of a year.

Expand All @@ -60,7 +60,7 @@ return Sequence.Successors(JanuaryFirst(year), NextDay)
.TakeWhile(IsSameYear(year));
```

Most of these helper function are straight forward, but `IsSameYear` might be a bit special if you havent worked with curried functions before.
Most of these helper function are straight forward, but `IsSameYear` might be a bit special if you haven't worked with curried functions before.

The function `IsSameYear` takes one parameter and returns a function which takes another parameter, this is also called the curried form of a function, there is also the `Functional.Curry` and `Functional.Uncurry` functions which can transform between both forms without the need to write them both. `IsSameYear(2000)` returns a function which always returns `true` if the Date is from the year 2000. That way of using functions might come in handy a lot more often than you think.

Expand All @@ -76,7 +76,7 @@ private static Func<DateOnly, bool> IsSameYear(int year)
=> day.Year == year;
```

This makes the the main body of the code semantic very easy to understand. All the helper functions are trivially to understand on it's own too.
This makes the main body of the code semantic very easy to understand. All the helper functions are trivially to understand on its own too.

## Group this into months

Expand All @@ -91,7 +91,7 @@ return Sequence.Successors(JanuaryFirst(year), NextDay)
There are two things we should consider here though.

1.) GroupBy is like the SQL GROUP BY and can rearrange elements, and therefore is not a lazy Extension function.
2.) GroupBy would also Group all days from a different year into the same 12 montly buckets.
2.) GroupBy would also Group all days from a different year into the same 12 monthly buckets.

Often that is exactly what you want, but in this case if we think of an endless stream of days this is not what we need at all. The days do not need to be rearranged, all days in the same month are next to each other and if we find a second January, we would like to have a new 13th bucket.

Expand Down Expand Up @@ -147,12 +147,12 @@ private static IEnumerable<string> LayoutMonth(IEnumerable<DateOnly> month)
}

yield return $"{string.Empty,WidthOfAWeek}";
}
}
```

This is a fine way to do this, it is very easy to read, but the foreach is really annoying. We already have an `IEnumerable<T>`, we just want to add it between the first and the last line. But with yield you are often limited to very procedural constructs.

Often you can avoid that, for simple cases we have Sequence.Return, Concat and other helpers, in this case though the nicest way is probaly creating an ImmutableList, because the syntax allows to combine ranges and single items elegantly.
Often you can avoid that, for simple cases we have Sequence.Return, Concat and other helpers, in this case though the nicest way is probably creating an ImmutableList, because the syntax allows to combine ranges and single items elegantly.

```cs
private static IEnumerable<string> LayoutMonth(IEnumerable<DateOnly> month)
Expand All @@ -162,7 +162,7 @@ private static IEnumerable<string> LayoutMonth(IEnumerable<DateOnly> month)
.Add(new string(' ', WidthOfAWeek));
```

Let's dive into our helper functions. First we take a look at the name of the month. The only noteworthy detail is the very functional mindest seen in the solution to the centering problem. It uses a pattern match to fill in the missing spaces: it is not very efficent, but easy to understand. The recursion will be very short because our lines are only 21 characters wide.
Let's dive into our helper functions. First we take a look at the name of the month. The only noteworthy detail is the very functional mindset seen in the solution to the centering problem. It uses a pattern match to fill in the missing spaces: it is not very efficient, but easy to understand. The recursion will be very short because our lines are only 21 characters wide.


```cs
Expand All @@ -187,7 +187,7 @@ internal static class StringExtensions

We have already seen the heart of `FormatWeeks` in the yield solution, but now it is a separate function. `FormatWeeks` again needs 2 very simple helper functions, the first one projects the week of the year, the other one will format a sequence of days.

The sequence of days can be either a complete week, or a partial week from the beginning or the end of the month. But because of the way we construct these sequences, there always is at least one element in it.
The sequence of days can be either a complete week, or a partial week from the beginning or the end of the month. But because of the way we construct these sequences, there always is at least one element in it.

```cs
private static IEnumerable<string> FormatWeeks(IEnumerable<DateOnly> month)
Expand All @@ -211,13 +211,13 @@ We are almost done with `FormatMonth`, now we really format the week, each day h
```cs
private static string FormatWeek(IGrouping<int, DateOnly> week)
=> PadWeek(week.Select(FormatDay).ConcatToString(), week);

private static string FormatDay(DateOnly day)
=> $"{day.Day,WidthOfDay}";
```

We can ignore the full weeks, because they are already 21 characters long. How do we distinguish the beginning of the month from the end? The week at the end of the month must start with the first day of the week. So we pad accordingly from the left or the right.

```cs
private static string PadWeek(string formattedWeek, IGrouping<int, DateOnly> week)
=> StartsOnFirstDayOfWeek(week)
Expand All @@ -241,7 +241,7 @@ Now we have an `IEnumerable<IEnumerable<string>>`, where the inner one is still

## Layouting the months together.

At this point we could print a list of months, we just would need to join all the lines together and it would look like this. (shortend to 2 months)
At this point we could print a list of months, we just would need to join all the lines together and it would look like this. (shortened to 2 months)

```
January
Expand Down Expand Up @@ -275,7 +275,7 @@ To do this lazily we use `Chunk`.

`Chunk` is supported with .NET 6, before that Funcky has nearly identical Replacement. (The return type is slightly different)

Chunk is grouping a sequnce into multiple sequnces of the same length. In our case we want to make the sequence of 12 months in to 4 sequences of length 3.
Chunk is grouping a sequence into multiple sequences of the same length. In our case we want to make the sequence of 12 months in to 4 sequences of length 3.

```cs
const MonthsPerRow = 3;
Expand All @@ -288,18 +288,22 @@ private static string CreateCalendarString(int year)
.Chunk(MonthsPerRow);
```

That means we have now an `IEnumerable<IEnumerable<IEnumerable<string>>>` where in the outermost `IEnumerable` we group 3 months together respectivly.
That means we have now an `IEnumerable<IEnumerable<IEnumerable<string>>>` where in the outermost `IEnumerable` we group 3 months together respectively.

Why does that help us with the layout? To create the first line of our final layout, we need the first lines of each month, and then the second, and so on. So we need to group the months together.

One of these chunks now looks like this:

<!-- spellchecker:off -->

```
Januar | 1 2 3 4 5 | 6 7 8 9 10 11 12 | 13 14 15 16 17 18 19 | 20 21 22 23 24 25 26 | 27 28 29 30 31 |
Februar | 1 2 | 3 4 5 6 7 8 9 | 10 11 12 13 14 15 16 | 17 18 19 20 21 22 23 | 24 25 26 27 28 29 |
März | 1 | 2 3 4 5 6 7 8 | 9 10 11 12 13 14 15 | 16 17 18 19 20 21 22 | 23 24 25 26 27 28 29 | 30 31 |
```

<!-- spellchecker:on -->

That actually already looks a lot like what we want, but we want the months on the top not on the left.

It also looks like a matrix, and on a matrix the operation to flip the rows and the columns (on a diagonal symmetry) is called transpose.
Expand All @@ -322,6 +326,8 @@ private static string CreateCalendarString(int year)

After this transforamtion our chunk of 3 months looks like this:

<!-- spellchecker:off -->

```
Januar | Februar | März
1 2 3 4 5 | 1 2 | 1
Expand All @@ -332,7 +338,9 @@ After this transforamtion our chunk of 3 months looks like this:
| | 30 31
```

I think it is obvious that at this point, we are done. We just have to join the chunks together to have our final output.
<!-- spellchecker:on -->

I think it is obvious that at this point, we are done. We just have to join the chunks together to have our final output.

```cs
private static string CreateCalendarString(int year)
Expand All @@ -347,4 +355,4 @@ private static string CreateCalendarString(int year)
.JoinToString(Environment.NewLine);
```

For a working solution with all the details, you should take a look at the [complete code in Program.cs](./calendar-code.md)
For a working solution with all the details, you should take a look at the [complete code in Program.cs](./calendar-code.md)
14 changes: 7 additions & 7 deletions Documentation/src/functional-helpers/unit-type.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ public abstract class SimpleAlgebraicDatatype
{
SomeValue = someValue;
}

public string SomeValue { get; }

public override TResult Match<TResult>(
Func<Variant1, TResult> variant1,
Func<Variant1, TResult> variant1,
Func<Variant2, TResult> variant2)
=> variant1(this);
}
Expand All @@ -45,11 +45,11 @@ public abstract class SimpleAlgebraicDatatype
{
SomeValue = someValue;
}

public int SomeValue { get; }

public override TResult Match<TResult>(
Func<Variant1, TResult> variant1,
Func<Variant1, TResult> variant1,
Func<Variant2, TResult> variant2)
=> variant2(this);
}
Expand Down Expand Up @@ -125,7 +125,7 @@ See [ActionToUnit](./action-to-unit.md) for an explanation.

## Example 2 - the "switch expression must return something" dilemma:

The following two code snippes do **not** comple:
The following two code snippets do **not** compile:

```csharp
// Error [CS0201]: Only assignment, call, increment, decrement, and new object expressions can be used as a statement.
Expand All @@ -136,7 +136,7 @@ variant switch
_ => throw new Exception("Unreachable"),
};

// Error [CS0029]: Cannot implicitly convert type 'thorw-expression' to 'void'
// Error [CS0029]: Cannot implicitly convert type 'throw-expression' to 'void'
// Error [CS9209]: A value of type 'void' may not be assigned.
_ = variant switch
{
Expand Down Expand Up @@ -176,4 +176,4 @@ _ = variant switch
};
```

If you cannot use the discard syntax (`_ =`), simply use `var _` or similar, and ignore the variable after.
If you cannot use the discard syntax (`_ =`), simply use `var _` or similar, and ignore the variable after.
2 changes: 1 addition & 1 deletion Documentation/src/introduction.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Functional programming is the oldest of the three major programming paradigms, none the less it is the last which gets wide spread usage. Even in languages like C++, Java or C# we want to use a functional style of programming.

Linq is the first Monad which got wide spread use in C#, and most C# programmers were not even aware of it beeing a monad, which probably helped.
Linq is the first Monad which got wide spread use in C#, and most C# programmers were not even aware of it being a monad, which probably helped.

[Mark Seemann] points out that "Unfortunately, Maybe implementations often come with an API that enables you to ask a Maybe object if it's populated or empty, and a way to extract the value from the Maybe container. This misleads many programmers \[...]"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<Metadata>
<Identity Id="Funcky.Analyzers.9ec5e9ed-a68b-4a0c-a9f7-c4019c7c2a94" Version="1.0" Language="en-US" Publisher="bruderer"/>
<DisplayName>Funcky.Analyzers</DisplayName>
<Description xml:space="preserve">This the funcky diagnostic extension for the .NET Compiler Platform ("Roslyn") to interactivly debug the analyzers in a VS instance.</Description>
<Description xml:space="preserve">This the funcky diagnostic extension for the .NET Compiler Platform ("Roslyn") to interactively debug the analyzers in a VS instance.</Description>
</Metadata>
<Installation>
<InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[15.0,)" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public sealed class AdjacentGroupByTest
private const int DaysInALeapYear = 366;
private const int MonthsInAYear = 12;
private const int February = 1;
private const int DaysInFebruraryInLeapYears = 29;
private const int DaysInFebruaryInLeapYears = 29;

[Fact]
public void AdjacentGroupByIsEnumeratedLazily()
Expand Down Expand Up @@ -117,5 +117,5 @@ private static IAsyncEnumerable<int> DaysInMonthsOfAYear()

private static IAsyncEnumerable<int> DaysInMonthsOfALeapYear()
=> DaysInMonthsOfAYear()
.Select((value, index) => index == February ? DaysInFebruraryInLeapYears : value);
.Select((value, index) => index == February ? DaysInFebruaryInLeapYears : value);
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ public Task MergeASequenceOfSequences()
{
var sequence1 = AsyncSequence.Return(1, 2, 4, 7);
var sequence2 = AsyncSequence.Return(3, 5, 6, 8);
var mergable = ImmutableList<IAsyncEnumerable<int>>.Empty.Add(sequence1).Add(sequence2);
var mergeable = ImmutableList<IAsyncEnumerable<int>>.Empty.Add(sequence1).Add(sequence2);
var expected = AsyncEnumerable.Range(1, 8);

return AsyncAssert.Equal(expected, mergable.Merge());
return AsyncAssert.Equal(expected, mergeable.Merge());
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Funcky.Extensions;
public static partial class AsyncEnumerableExtensions
{
/// <summary>
/// Interleaves the elements of multiple sequences by consuming the heads of each subsequence in the same order as the given subsequences. This repeats until all the sequences are completley consumed.
/// Interleaves the elements of multiple sequences by consuming the heads of each subsequence in the same order as the given subsequences. This repeats until all the sequences are completely consumed.
/// </summary>
/// <typeparam name="TSource">The type of the elements in the source sequences.</typeparam>
/// <param name="source">first sequence.</param>
Expand All @@ -17,7 +17,7 @@ public static IAsyncEnumerable<TSource> Interleave<TSource>(this IAsyncEnumerabl
=> ImmutableList.Create(source).AddRange(otherSources).Interleave();

/// <summary>
/// Interleaves the elements of a sequence of sequences by consuming the heads of each subsequence in the same order as the given subsequences. This repeats until all the sequences are completley consumed.
/// Interleaves the elements of a sequence of sequences by consuming the heads of each subsequence in the same order as the given subsequences. This repeats until all the sequences are completely consumed.
/// </summary>
/// <typeparam name="TSource">The type of the elements in the source sequences.</typeparam>
/// <param name="source">the source sequences.</param>
Expand Down
3 changes: 1 addition & 2 deletions Funcky.Async/Funcky.Async.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<AnalysisModeReliability>All</AnalysisModeReliability>
<EnablePackageValidation>true</EnablePackageValidation>
<NoWarn>$(NoWarn);RS0026</NoWarn><!-- RS0026: Do not add multiple overloads with optional parameters -->
<PolySharpIncludeRuntimeSupportedAttributes>true</PolySharpIncludeRuntimeSupportedAttributes>
</PropertyGroup>
<PropertyGroup>
<RootNamespace>Funcky</RootNamespace>
Expand All @@ -37,8 +38,6 @@
<Compile Include="..\Funcky\Internal\UnsafeEither.cs" Link="Internal\UnsafeEither.cs" />
<Compile Include="..\Funcky\Internal\Validators\WindowWidthValidator.cs" Link="Internal\Validators\WindowWidthValidator.cs" />
<Compile Include="..\Funcky\Internal\Validators\ChunkSizeValidator.cs" Link="Internal\Validators\ChunkSizeValidator.cs" />
<Compile Include="..\Funcky\Compatibility\DynamicallyAccessedMemberTypes.cs" Link="Compatibility\DynamicallyAccessedMemberTypes.cs" />
<Compile Include="..\Funcky\Compatibility\DynamicallyAccessedMembersAttribute.cs" Link="Compatibility\DynamicallyAccessedMembersAttribute.cs" />
<Compile Include="..\Funcky\Internal\PartitionBuilder.cs" Link="Internal\PartitionBuilder.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
Loading
Loading