FsControl
[ARCHIVED] FsControl in now included in FSharpPlus https://fsprojects.github.io/FSharpPlus
Install / Use
/learn @gusty/FsControlREADME
FsControl 
A library that enhances the F# coding experience by providing the following two innovations:
-
A mechanism for defining standalone generic functions within F# similar to Haskell's typeclasses (once their defining module is in scope). These generic functions are resolved at compile time to an implementation type using .Net's static class method overloading mechanism, .Net type extension facility, and F#'s type inferencing. For example, a generic definition of <code>Map</code> may be made so as to automatically resolve to <code>List.map</code>, <code>Array.map</code>, <code>Seq.map</code>, or whatever the provided mappable (Functor) value's implementation of <code>Map</code> resolves to at compile time.
-
The provision of a set of generic standalone function definitions together with their implementation over a set of .NET and F# core types. Some of these functions are abstractions ported from Haskell but adapted to the F#/.NET world. Other functions offer a solution to normalize common function calls over different Types which represent the same abstraction but mainly due to historical reasons have different names and signatures.
NOTE: FsControl is part of FSharpPlus now.
This project is no longer maintained here.
Getting Started
-
Download binaries from Nuget, use the latest version (2.x)
-
Open an F# script file or the F# interactive and reference the library
#r @"C:\Your path to the binaries\FsControl.dll";;
Ignore warnings about F# metadata.
- Now you can create generic functions, here's an example with <code>map</code> (fmap for Haskellers, Select for C-sharpers):
let inline map f x = FsControl.Map.Invoke f x;;
Static constraints will be inferred automatically.
- Test it with .NET / F# primitive types:
map string [|2;3;4;5|];;
// val it : string [] = [|"2"; "3"; "4"; "5"|]
map ((+) 9) (Some 3);;
// val it : int option = Some 12
- You can also create your own type with a method <code>Map</code>:
type Tree<'t> =
| Tree of 't * Tree<'t> * Tree<'t>
| Leaf of 't
static member Map (x:Tree<'a>, f) =
let rec loop f = function
| Leaf x -> Leaf (f x)
| Tree (x, t1, t2) -> Tree (f x, loop f t1, loop f t2)
loop f x
By adding the static member <code>Map</code> we say that we're making <code>Tree</code> an instance of <code>Map</code>.
- Try mapping over your new type:
let myTree = Tree(6, Tree(2, Leaf 1, Leaf 3), Leaf 9);;
map ((*) 10) myTree;;
// val it : Tree<int> = Tree (60,Tree (20,Leaf 10,Leaf 30),Leaf 90)
Generic functions may be seen as an exotic thing in F# that only saves a few key strokes (<code>map</code> instead of <code>List.map</code> or <code>Array.map</code>) still they allow you to reach a higher abstraction level, using ad-hoc polymorphism.
But more interesting is the use of operators. You can't prefix them with the module they belong to, well you can but then it's no longer an operator. As an example many F# libraries define the bind operator <code>(>>=)</code> but it's not generic so if you use two different types which are both monads you will need to prefix it e.g. <code>State.(>>=)</code> and <code>Reader.(>>=)</code> which defeats the purpose of having an operator.
Here you can easily define a generic bind operator:
let inline (>>=) x f = Bind.Invoke x f
Or if you do Railway Oriented Programming you can finally have your generic Kleisli composition (fish) operator:
let inline (>=>) f g x = Bind.Invoke (f x) g
Also when working with combinators, the generic applicative functor (space invaders) operator is very handy:
let inline (<*>) x y = Apply.Invoke x y
Of course they are already defined in the FsControl.Operators module and they work with primitive and user defined types.
Next steps:
- Have a look at the sample files adjust the path of the binaries and run the .fsx scripts.
- Before creating your own library of generic functions be aware that FsControl.Operators is a lightweight module with some operators and functions used mainly to test the project. Also take the time to visit F#+ which is a library that re-export all those functions and also provides more derived operators, builders and other interesting stuff.
- In the rare case that you are not interested in the generic stuff but want to re-use specific implementations many methods in FsControl are defined as extension methods and some have a C# friendly signature.
- Keep reading the doc.
How does it works
Technically this is a base library with a collection of generic methods overloaded for .NET and F# core types but extensible to other types at the same time.
There are basically two Types involved in these overloads:
-
The type that will implement the abstraction. This will be a “real” type, for example <code>List</code> or <code>Tree</code>. We may refer to this type as the type or as the instance-type, since it represents an instance of the abstraction. At the same time we can classify these types in primitive types and custom types. By primitive types we refer to existing types in the .NET framework.
-
The type that represent the abstraction: Examples of these types are <code>Map</code>, <code>Bind</code>, <code>Append</code>, etc. This will be a "dummy" type implemented as a static class with an overloaded method (usually with the same name as the type) and an entry point method called 'Invoke'. From now on and in order to differentiate from the type-instance we will call this type the method-class.
For Haskellers this 'method-class' abstraction is similar to Haskell's Type-Classes but with a single method.
For OOP-ers it may compare to interfaces or abstract classes but with a single method, early binding (compile-time) and without dependencies on the assembly where the interface is defined.
FsControl contains overloads mainly for primitive types, but the generic functions will resolve to any type (a user-defined type) having a member with a matching signature. This makes possible to use some libraries that don't depend on FsControl, as long as the signature is the right one it will work.
How to use FsControl
You may find hard to understand how to use FsControl, the best is to have a look at the source code, if you just want to use the generic functions for primitive types open <code>FsControl.Operators</code> module.
The purpose of the overloads is to associate primitive types with method-classes, here we can have three different scenarios:
- Add a new method-class and instance-types for existing types.
This is the most complex scenario, to define a new method-class is not straightforward, there will be some guidelines but at the moment the best is to have a look at the source code.
- Add a new type and make it an instance of an existing method-class.
There are 2 ways:
a) You can have a look at the signature of the method you want to implement in the source code, which will follow this convention:
static member [inline] [MethodName] (arg1:Type, [more args], output[:ReturnType], mthd[:MethodClassName]) =
Implementation
To find the exact signature you need to look at the source code of the method-class you are interested.
Here's an example:
In the source code for <code>Map</code> (in Functor.fs) the <code>option</code> instance is defined like this:
[<Extension>]static member Map (x:option<_>, f, [<Optional>]impl:Map) = Option.map f x
So you can create a type <code>Tree</code> and add an instance for the existing method-class <code>Map</code> this way:
// Define a type Tree
type Tree<'a> =
| Tree of 'a * Tree<'a> * Tree<'a>
| Leaf of 'a
// add an instance for Map (Functor)
static member Map (x:Tree<_>, f, impl) =
let rec loop f (t:Tree<'a>) =
match t with
| Leaf x -> Leaf (f x)
| Tree (x, t1, t2) -> Tree (f x, loop f t1, loop f t2)
loop f x
b) Some methods accept also a 'clean signature' without the unused parameters <code>output</code> and <code>impl</code>. You can find a list of these methods below, in the section "How can I make my classes FsControl-ready?". This way it doesn't require to reference FsControl binaries.
- Add an instance for an existing Type of an existing method-class:
We can’t do this. This is only possible if we have control over the source code of either the instance-type or the method-class. The fact that the association must be done either in the instance-class or in the method-class is due to both a technical limitation <code>(1)</code> and a conceptual reason <code>(2)</code>.
- <code>(1)</code> Extensions methods are not taken into account in overload resolution.
- <code>(2)</code> It may lead to a bad design practice, something similar happens in Haskell with Type Classes (see orphan instances).
Anyway if you find a situation like this you can either wrap the type you're interested in or "shadow" the generic function.
How can I make my classes FsControl-ready?
An easy way to make classes in your project callable from FsControl without referencing FsControl DLLs at all is to use standard signatures for your methods. Here's a list of the standard signatures available at the mome
Related Skills
node-connect
349.9kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
109.8kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
349.9kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
349.9kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
