F#+ (FSharpPlus) Documentation
repository·master·Indexed 21 days ago
https://github.com/fsprojects/fsharpplusA functional programming enhancement library for F# that leverages generic programming to reduce boilerplate and provide advanced functional abstractions. It extends FSharp.Core and the BCL with non-intrusive patterns, including the Control namespace's Invokables for representing generic functions and the FSharpPlus.TypeLevel project for debugging type providers.
What's inside F#+
- F#+ (FSharpPlus) is a base library designed to enhance F# by providing advanced functional programming capabilities. It uses generic programming techniques to reduce boilerplate code while maintaining compatibility with existing F# patterns through consistent naming conventions and signatures. It is designed to 'enhance' rather than 'replace' standard F# patterns, allowing developers to adopt it incrementally.
How overloaded static members and interfaces work in F#+
masterF#+ uses static members and interface implementations to provide a seamless experience for end-users, particularly regarding generic abstractions:
- Default Overloads: The library provides default overloads (fallback mechanisms) to ensure end-users can use functions easily. While the library may internally duplicate code to improve compile times and type inference, the end-user sees a complete set of specialized and fallback overloads.
- Monadic Bind Convention: When creating new abstractions, F#+ follows the convention of using well-known operators as static members. For example, instead of a named
bindmethod, types implement the>>=static member. This allows the operator to be used even without the F#+ library and increases compatibility with other 3rd-party types. - Interface Precision: The library is careful with interface overloads. It distinguishes between providing an overload for an explicit type (like
seq<_>) versus all types implementing an interface (likeIEnumerable<_>) to avoid incorrect default behaviors (e.g., not treating everyIEnumerableas a Monad).
How F#+ handles generic functions and modules
masterF#+ follows specific patterns for organizing generic functions and their type-specific counterparts to ensure consistency and ease of use:
- Generic vs. Specific Naming: Generic functions use standard F# naming conventions (e.g.,
mapinstead offmap). For every generic function, there should ideally be non-generic counterparts in specific modules (e.g.,Option.map3andResult.map3corresponding to a genericmap3). - Module Organization:
- Generic operators and functions are placed in the
FSharpPlus.Operatorsmodule for easy access. - If a function or operator might conflict with existing F# functionality or other libraries, it is placed in a module that requires an explicit
openstatement. - New types or collections are typically defined in their own files, using modules with
let-bound functions to remain idiomatic.
- Generic operators and functions are placed in the
- Operator Usage: Unary and binary operators are kept to a minimum in the auto-opened
FSharpPlus.Operatorsmodule to prevent namespace clashes. Users can also find these operators in manually opened modules.
- Generic vs. Specific Naming: Generic functions use standard F# naming conventions (e.g.,
Understand F#+ design philosophy and extension patterns
masterF#+ is designed to be a non-intrusive, non-opinionated extension of
FSharp.Coreand the BCL. When using the library, keep the following principles in mind:- Non-intrusive: F#+ avoids changing existing F# language or
FSharp.Corefunctionality. New functionality is typically isolated in specific modules/namespaces that require anopenstatement. - Consistency over naming collisions: Because F# lacks type classes, F#+ uses creative naming to maintain consistency while avoiding collisions with built-in functions. For example:
mapis used for generic mapping, but for Dictionaries, usemapValues.minimumis used for collections to avoid collision with the built-inmin(which compares two values).
- Zip and Lift behaviors:
map2andzipin collections typically act pairwise.lift2is provided for applicative behavior (cross-product), which is the generic counterpart for non-collection types.- F#+
zipfunctions are designed to be safer than F# core'szip, often matching the behavior of.zipShortestto avoid errors when collection lengths differ.
- Non-intrusive: F#+ avoids changing existing F# language or
How Invokables and Control abstractions work
masterThe
Controlnamespace uses a pattern called Invokables to represent generic functions and abstractions (similar to type classes or traits in other languages).Mental Model
Since F# (targeting CIL) does not have first-class support for Higher Kinds, F#+ uses generic type parameters as labels to communicate intent. For example, a signature like
('T -'U) -> 'Functor<'T> -> 'Functor<'U>uses the labelFunctorto indicate that the types must satisfy the Functor abstraction.Implementation Details
An abstraction (like
Monoid) is implemented via concrete types (Invokables) such asPlusandZero. These types provide:- Concrete overloads: Static methods for specific BCL or
FSharp.Coretypes (e.g., string concatenation, list concatenation). - An inline invokable method: Uses SRTP (Static Resolution via Type Parameters) to dispatch to the correct overload.
- Default implementations: Provided as
inlineto reduce boilerplate for the end-user. For example, if you implementBindandReturnfor a Monad, F#+ can provideJoinautomatically via default implementations.
Usage Tip
Invokers are designed to have signatures similar to the generic functions they represent. You can often use an Invoker in place of a function to write more generic code by passing a type instead of a specific function.
- Concrete overloads: Static methods for specific BCL or
Install F#+ via NuGet
masterYou can add F#+ to your F# projects using the NuGet package manager. The library is available on NuGet under the name
FSharpPlus.# Use the dotnet CLI to add the package to your project dotnet add package FSharpPlusUse FSharpPlus.TypeLevel for debugging type providers
masterThe
FSharpPlus.TypeLevelproject is designed to speed up the development cycle when debugging type providers. Instead of rebuilding a large project every time a change is made, you can use this small, dedicated project to isolate and test type-level logic.To run type-level tests, you must compile the project with the compiler constant
TYPELEVEL_DEBUGenabled. Note that these tests are only active during development and are excluded from the final package.