Skip to content

Commit 9880456

Browse files
authored
Merge branch 'master' into gusty-dict-applicative
2 parents 25c00cd + e4d5843 commit 9880456

File tree

210 files changed

+6378
-2546
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

210 files changed

+6378
-2546
lines changed

.config/dotnet-tools.json

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,17 @@
22
"version": 1,
33
"isRoot": true,
44
"tools": {
5-
"fake-cli": {
6-
"version": "5.19",
5+
"fable": {
6+
"version": "4.5.0",
77
"commands": [
8-
"fake"
8+
"fable"
99
]
1010
},
11-
"fsharp.formatting.commandtool": {
12-
"version": "7.2.9",
11+
"fsdocs-tool": {
12+
"version": "19.1.1",
1313
"commands": [
1414
"fsdocs"
1515
]
16-
},
17-
"fable": {
18-
"version": "3.1.15",
19-
"commands": [
20-
"fable"
21-
]
2216
}
2317
}
2418
}

.github/workflows/dotnetcore.yml

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
name: .NET Core
22

3+
env:
4+
# Stop wasting time caching packages
5+
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
6+
# Disable sending usage data to Microsoft
7+
DOTNET_CLI_TELEMETRY_OPTOUT: true
8+
# GitHub Packages Feed settings
9+
GITHUB_FEED: https://nuget.pkg.github.com/fsprojects
10+
GITHUB_USER: fsprojects
11+
#GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12+
313
on:
414
push:
515
branches: [ master ]
616
pull_request:
717
branches: [ master ]
8-
918
jobs:
1019
build:
1120

@@ -14,12 +23,78 @@ jobs:
1423
steps:
1524
- uses: actions/checkout@v2
1625
- name: Setup .NET Core
17-
uses: actions/setup-dotnet@v1
26+
uses: actions/setup-dotnet@v3
1827
with:
19-
dotnet-version: 5.0.100
28+
dotnet-version: |
29+
8.0.x
30+
7.0.x
2031
- name: Restore
2132
run: git submodule update --init --recursive
2233
- name: Build with dotnet
2334
run: dotnet build build.proj --configuration Release
35+
- name: Set Timezone
36+
uses: szenius/[email protected]
37+
with:
38+
timezoneWindows: "Nepal Standard Time"
2439
- name: Test with dotnet
2540
run: dotnet test build.proj -v n
41+
42+
package:
43+
runs-on: windows-latest
44+
45+
steps:
46+
- uses: actions/checkout@v2
47+
- name: Setup .NET Core
48+
uses: actions/setup-dotnet@v3
49+
with:
50+
dotnet-version: |
51+
8.0.x
52+
7.0.x
53+
6.0.x
54+
- name: Restore
55+
run: git submodule update --init --recursive
56+
- name: Extract branch name
57+
shell: bash
58+
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
59+
id: extract_branch
60+
- name: Setup Version Suffix
61+
shell: pwsh
62+
run: |
63+
$buildId = $env:GITHUB_RUN_NUMBER.PadLeft(5, '0');
64+
$versionSuffixPR = "PR${{ github.event.pull_request.number }}-$buildId";
65+
$branchName = "${{ steps.extract_branch.outputs.branch }}".Replace("_","").Replace("/","-");
66+
$versionSuffixBRANCH = "$($branchName)-CI$($buildId)"
67+
$env:VersionSuffix = if ("${{ github.event.pull_request.number }}") { $versionSuffixPR } else { $versionSuffixBRANCH }
68+
Write-Output "##[set-output name=version_suffix]$($env:VersionSuffix)"
69+
id: version_suffix
70+
- name: Package
71+
run: dotnet pack build.proj --version-suffix ${{ steps.version_suffix.outputs.version_suffix }}
72+
- name: Upload Artifacts
73+
uses: actions/upload-artifact@v2
74+
with:
75+
name: nupkg
76+
path: ./bin/nupkg/*.nupkg
77+
#- name: Push to GitHub Feed
78+
# shell: bash
79+
# run: |
80+
# for f in ./bin/nupkg/*.nupkg
81+
# do
82+
# curl -vX PUT -u "$GITHUB_USER:$GITHUB_TOKEN" -F package=@$f $GITHUB_FEED
83+
# done
84+
85+
docs:
86+
runs-on: windows-latest
87+
steps:
88+
- uses: actions/checkout@v2
89+
- name: Setup .NET Core
90+
uses: actions/setup-dotnet@v3
91+
with:
92+
dotnet-version: |
93+
8.0.x
94+
7.0.x
95+
6.0.x
96+
5.0.x
97+
- name: Restore
98+
run: git submodule update --init --recursive
99+
- name: Build All Docs
100+
run: dotnet msbuild -target:AllDocs build.proj

.github/workflows/fable.yml

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,28 @@ jobs:
1313

1414
steps:
1515
- uses: actions/checkout@v2
16-
- name: Setup .NET Core
17-
uses: actions/setup-dotnet@v1
18-
with:
19-
dotnet-version: 5.0.100
2016
- name: Restore
2117
run: git submodule update --init --recursive
18+
- name: Remove global json
19+
run: rm global.json
20+
- name: Set target framework to net6 instead of net8
21+
uses: Mudlet/xmlstarlet-action@master
22+
with:
23+
args: edit --inplace --update "/Project/PropertyGroup/TargetFrameworks" --value "netstandard2.0;netstandard2.1;net6.0" ./src/FSharpPlus/FSharpPlus.fsproj
24+
- name: Setup .NET Core
25+
uses: actions/setup-dotnet@v3
26+
with:
27+
dotnet-version: |
28+
8.0.x
29+
7.0.x
30+
6.0.x
2231
- name: Restore tools
2332
run: dotnet tool restore
33+
- name: Create global.json
34+
working-directory: tests/FSharpPlusFable.Tests
35+
run: mv fable3-global.json global.json
36+
- name: Install fable
37+
run: dotnet tool install --global Fable --version 3.7.22
2438
- name: Use Node.js
2539
uses: actions/setup-node@v1
2640
with:
@@ -30,24 +44,64 @@ jobs:
3044
run: npm install
3145
- name: Run Fable tests
3246
working-directory: tests/FSharpPlusFable.Tests
33-
run: dotnet fable . --outDir bin --runScript ./bin
34-
35-
testFable3SubsetOnCore:
47+
run: fable . --outDir bin --runScript ./bin
48+
49+
testfable4:
3650
runs-on: ubuntu-latest
3751

3852
steps:
3953
- uses: actions/checkout@v2
54+
- name: Restore
55+
run: git submodule update --init --recursive
56+
- name: Remove global json
57+
run: rm global.json
4058
- name: Setup .NET Core
41-
uses: actions/setup-dotnet@v1
59+
uses: actions/setup-dotnet@v3
60+
with:
61+
dotnet-version: |
62+
8.0.x
63+
7.0.x
64+
6.0.x
65+
- name: Restore tools
66+
run: dotnet tool restore
67+
- name: Install jq
68+
uses: dcarbone/[email protected]
69+
- name: Install fable
70+
run: |
71+
version=`cat ./.config/dotnet-tools.json | jq --raw-output '.tools.fable.version'`
72+
dotnet tool install --global Fable --version $version
73+
- name: Use Node.js
74+
uses: actions/setup-node@v1
4275
with:
43-
dotnet-version: 5.0.100
76+
node-version: '18.x'
77+
- name: Install npm dependencies
78+
working-directory: tests/FSharpPlusFable.Tests
79+
run: npm install
80+
- name: Run Fable tests
81+
working-directory: tests/FSharpPlusFable.Tests
82+
run: fable . --outDir bin --runScript ./bin
83+
84+
testFable3SubsetOnCore:
85+
runs-on: ubuntu-latest
86+
87+
steps:
88+
- uses: actions/checkout@v2
4489
- name: Restore
4590
run: git submodule update --init --recursive
91+
- name: Remove global json
92+
run: rm global.json
93+
- name: Setup .NET Core
94+
uses: actions/setup-dotnet@v3
95+
with:
96+
dotnet-version: |
97+
8.0.x
98+
7.0.x
99+
6.0.x
46100
- name: Restore tools
47101
run: dotnet tool restore
48-
- name: Run tests (Fable2 subset but on .net)
49-
working-directory: tests/FSharpPlusFable.Tests
50-
run: dotnet run -c Fable
102+
# - name: Run tests (Fable2 subset but on .net)
103+
# working-directory: tests/FSharpPlusFable.Tests
104+
# run: dotnet run -c Fable
51105
- name: Run tests (Fable3 subset but on .net)
52106
working-directory: tests/FSharpPlusFable.Tests
53107
run: dotnet run -c Fable3

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,6 @@ tests/FSharpPlusFable.Tests/node_modules/
213213
tests/FSharpPlusFable.Tests/FSharpPlusFable
214214
/node_modules
215215
*.fs.js
216+
217+
tests/Benchmarks/BenchmarkDotNet.Artifacts
218+
tests/benchmarks/BenchmarkDotNet.Artifacts

.travis.yml

Lines changed: 0 additions & 15 deletions
This file was deleted.

DEVELOPER_GUIDE.md

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,71 @@
11
# DEVELOPER GUIDE
22

3+
### General Goals / Philosophy:
34

5+
- Consistency: the design of this library should maximize consistency, this is relation between group of functions, argument types, types and abstractions they represent. By doing this, it's easier for the user to guess what are the expectations instead of relaying all the time to check docs and/or source code. Therefore naming should be carefully considered in order achieve this goal.
6+
- Non intrusive: all additions to this library should not change existing functionality of F# language and FSharp.Core. In the cases when it is needed, it must be done in a specific module/namespace that forces to be `open`.
7+
- Non opinionated: as much as possible F#+ is not trying to force the user to solve problems in a specific way, instead it tries to give generic ways to approach a problem from different angles. Specific ways of using this library should be eventually defined by the Organization/Team/User that use F#+ to solve problems in a specific way and in order to achieve that goal. A small set of functions, type aliases can be added on top of F#+ for that purpose, but the library must be as neutral as possible.
8+
- Trust users: F#+ doesn't apply the principle of "removing functions that could be used in a wrong way", it is assumed the user has enough skills to decide what's a good use and what is a bad use of a specific function. It is not the intent of F#+ maintainers and contributors to educate users by hiding stuff, that's not the goal of this library, though there's a lot to learn, but for that there are general guidelines and normally there should be also specific ones for each Organization/Team/User. This doesn't mean that documentation will be poorly maintain.
9+
- Coding style: There is no specific set of rules, but it should stick to general coding conventions with some relaxation in order be able to align code in places where this is convenient. For instance when there's a big set of overloads, this makes it easier to read but also easier to edit the code with multiline editor support. The best advice is to try to copy existing style. For signatures, try to follow [FSharp.Core](https://learn.microsoft.com/en-us/dotnet/fsharp/style-guide/) design, i.e. `'T` not `'a`.
10+
- Naming commits: Use a very short and descriptive sentence in imperative mood, as if completing the sentence "If applied, this commit will ...". For more details [use this](https://cbea.ms/git-commit) as a reference.
11+
- PR's: try to keep them as atomic as possible, if a PR is found that touches many unrelated areas it will be asked to split in different PRs. A typical example is a PR that adds a specific functionality but also fix bugs, or typos in the docs. If we eventually revert that functionality, the other changes will be reverted as well which might not be desired. Draft PRs are encouraged and feel free to ask for advice while in the writing process. Use the same naming convention as for commits.
12+
- This is F#: although F#+ contains some abstractions inspired in libraries used in other languages, those concepts are translated to F# standards and eventually adapted to fit better in F#.
413

5-
### Definition of a type class
14+
### Extensions
615

7-
The concept of type class is a type system construct enabling ad-hoc polymorphism.
16+
This library defines many extensions for different types.
17+
Some of these functions are conceptually connected, in that they represent an abstraction and sometimes they have indeed a generic version of the function which doesn't require typing the type as module prefix.
18+
Note that in these cases, normally the names are the same but this is not a rule, there are cases where the name differs to avoid collisions or confusion with other functions, for example:
819

9-
in F#+, due to the constraint of targetting CIL, which doesn't have first class support for type class definition and implementation, those are not defined as F# types; instead, they are referred to in generic type parameters in the various type signatures that forms the idioms of this library implementation.
20+
- `map` is a generic function, and F#+ has many types implementing it, but for dictionaries this correspond to `mapValues`
21+
- `min` is a non-generic function operating in collections, but its generic counterpart is `minimum` to avoid collision with the built-in `min` function (minimum between two values).
1022

11-
Due to that, they don't carry any effect on implementation as they are just arbitrary labels for the generic type parameters, useful to the developers and users of the library.
23+
So, what this means is, F#+ doesn't provide generic functions based on names, although in many cases names are the same, we need to take into account:
1224

13-
The concrete implementation of them on the other hand is done, minimally, by defining a concrete type per function.
25+
1. F# core has some inconsistencies as it's not a type classes based library. A type-class based library is not necessarily something that implements a trick for type-class, but something that is designed as if we had support for them, it means capturing some generic concepts and making them clear in the names chosen for every function.
26+
2. But F#+ is not attempting to fix F# core but to build on top in a non-intrusive way, and try to re-use all the consistent concepts, idioms and de-facto naming conventions from F# core as much as possible without increasing the inconsistency level already there. Keep in mind, in F#+ **we try to expand FSharp.Core functionalities, but we are trying not to be intrusive**, as stated before: F#+ builds upon FSharp, using generic programming techniques to help avoid boiler plate code. However, by naming conventions and signatures it can be seen to 'enhance' rather than 'replace' existing patterns as much as possible.
1427

15-
For example, one of the simplest type classes `Monoid` has it's core implementation split under two types `Plus` and `Zero`, called _Type Class Implementation Function Types_ in this document.
28+
So the solution sometimes require some creativity like thinking new names that make it clear what the function does without departing too far from existing naming conventions.
1629

17-
Those _Type Class Implementation Function Types_ define static methods of several kinds:
30+
Another interesting case is the `zip` related functions:
1831

19-
* concrete overloads, for exemple Monoid's `Plus` will have overloads of a `Plus` method (named the same as the type defining those) for each of the BCL and FSharp.Core types that ought to support the `Monoid` type class, e.g. one to concatenate two strings, one to concatenate two lists, one to concatenate two arrays, and so on and so forth.
20-
* an _invoker_ method, that should always be defined as `inline`, which embeds the SRTP machinery that is used to pick the right overload (whether defined in F#+ or outside of it for implementing type class)
21-
* a default or set of default implementations, those may need to be defined in a type extension due to F# compiler implementation details to enable being picked up correctly for types that end up supporting the type class when such type class come with default implementation expressed in terms of composing several functions, those are also always defined as `inline`.
32+
- For collections like types, in F# core it's normal to find `zip` / `map2` functions, which acts pairwise. But another possible implementation is the applicative zip which works cross product.
33+
- Here we define 2 generic functions, `lift2` which corresponds always to the applicative instance, so normally in non-collections it will correspond to the non-generic `map2` but since collections already have (or at least it's expected to) a `map2` acting pairwise, in those cases only F#+ provides a `lift2` extension and that's what's used for its generic counterpart.
34+
- The other provided generic function is `zip` which is available mainly for collection like types and although they match the non-generic name, note that the behaviour is not exactly the same, because F# core throws errors for list and arrays when the number of elements are different. In that sense, in fact, it matches `.zipShortest` when defined, otherwise it matches `.zip`.
35+
36+
So these zip related functions are a good example of how F#+ does its best to fix FSharp.Core inconsistencies in a non-intrusive way.
37+
38+
39+
### Abstractions
40+
41+
The abstractions represented in the Control namespace of this library are a set of types that in fact represents generic functions, these types are referred to as Invokables.
42+
43+
These Invokables are organized in such a way that resemble `static interfaces`, `type-classes`, `concepts` or `traits` in other languages where they have a type system construct enabling higher kinds and ad-hoc polymorphism.
44+
45+
In F#+, due to the constraint of targeting CIL which doesn't have first class support for Higher Kinds, we can't enforce that a specific type parameter belongs to a specific abstraction. This way generic type parameters are normally given the name of the abstraction, in the various type signatures that forms the idioms of this library implementation. As an example, the signature for generic `map` is written as `('T -'U) -> '``Functor<'T>``-> '``Functor<'U>`` ` which will be rendered as `('T -'U) -> 'Functor<'T> -> 'Functor<'U>` where `Functor` is the name of the abstraction and all types which align with a Functor can go there.
46+
47+
Due to that, Invokables don't carry any effect on implementation as they are just arbitrary labels for the generic type parameters, useful to communicate the intention to the developers and users of the library. Still F# type system will normally show some constraints at the method level, but these constraints are sometimes not very helpful in communicating the related abstraction.
48+
49+
The concrete implementation of these abstractions on the other hand is done, minimally, by defining a concrete type (an Invokable) per function.
50+
51+
For example, one of the simplest abstractions `Monoid` has its core implementation split under two types `Plus` and `Zero`.
52+
53+
Those Invokers contains static methods of several kinds:
54+
55+
* concrete overloads, for example Monoid's `Plus` will have overloads of a `Plus` method (named the same as the type defining those) for each of the BCL and FSharp.Core types that ought to support the `Monoid` type class, e.g. one to concatenate two strings, one to concatenate two lists, one to concatenate two arrays, and so on and so forth.
56+
* an _invokable_ method, that should always be defined as `inline`, which embeds the SRTP dispatcher machinery that is used to pick the right overload (whether defined in F#+ or outside of it)
57+
* a default or set of default implementations, those may need to be defined in an intrinsic type extension due to F# compiler implementation details to enable being picked up correctly for types that end up supporting the abstraction when such abstraction comes with a default implementation expressed in terms of composing several functions, those are always defined as `inline`.
2258

23-
### Implementation idioms
2459

2560
#### Concrete implementations
2661

62+
F#+ should add as much concrete implementations for primitive types (types coming from F#+ dependencies which are mainly BCL types and FSharp.Core types) as possible, since the end user won't be able to add them later.
63+
2764
#### Defaults
2865

66+
The goal of default implementations is to allow users of the library to write less code, as an example F#+ expects users to add `Bind` , `Return` to their specific monad types, but not force them to add `Join` although if they do, the code might be more efficient.
67+
Defaults are not intended to be used by developers of F#+, as F#+ should afford writing more code in order to maximize usability of the library. You can read this as "the principle is to allow user to write less boilerplate, and because of that we have to put some boilerplate inside F#+".
68+
2969
#### Invoker
70+
71+
The signature of the Invoker should ideally be the same as the generic function. This will allow Invokers to be used in lieu of functions in scenarios where passing a type leads to more generic code than passing a function.

0 commit comments

Comments
 (0)