fsharp / fsharp/fslang-suggestions
Add additional trigonometric functions to `FSharp.Core.Operators`
- Dominant language
- No language data
- Stars
- 373
- Forks
- 21
- PR merge metrics
- No merged PRs in 30d
Description
I propose we support additional and a more complete collection of trigonometric functions in [FSharp.Core.Operators](https://fsharp.github.io/fsharp-core-docs/reference/fsharp-core-operators.html). Specifically, add all of the derived trigonometric functions found in this document: https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/keywords/derived-math-functions
Note: It is possible that there is an existing way to do what I'd like as described below and that I have missed it in my research and someone will comment and let me know.
1. I haven't seen a method that is as simple and straightforward as simply adding the additional trigonometric functions.
2. If there is, I would be happy to hear it!
The existing way of approaching this problem in F# is that these functions need to be manually defined, but this is difficult to integrate with overloading the existing trigonometric functions for new numerical types.
For example, say that I want to implement a `Dual` number type and provide trigonometric functions. For the existing trigonometric functions in `FSharp.Core.Operators`, such as `sin` and `cos`, this is easy to do.
```fsharp
type Dual = { Real: float; Dual: float } with
static member Sin {Real = a; Dual = b} = { Real = sin a; Dual = b * cos a }
static member Cos {Real = a; Dual = b} = { Real = cos a; Dual = b * (- sin a) }
let dual a b = { Real = a; Dual = b }
// This allows very nice integration with the `sin` and `cos` operators.
let testWithFloat = sin 2.0 // note that F# doesn't have implicit type conversion of int to float for `sin`
let testWithDual = sin (dual 1 2) // this could be sin (1 + 2e) or sin (1 + 2d) if NumericalLiteral supported other characters
```
However, I need to complete the dual type by implementing secant, cosecant, etc. Since these are not built-in to F#, I have to implement them as normal members, but this gets awkward because I now need to add the `AutoOpen` attribute to keep from having to qualify each use.
```fsharp
[]
type Dual = { Real: float; Dual: float } with
static member (/) ... // all the implementations of this overload are left out for brevity
static member Sin {Real = a; Dual = b} = { Real = sin a; Dual = b * cos a }
static member Cos {Real = a; Dual = b} = { Real = cos a; Dual = b * (- sin a) }
static member sec (x: Dual) = 1 / cos x // this `/` is an overload for Dual
let dual a b = { Real = a; Dual = b }
let test1 = sin 2.0
let test2 = sin (dual 1 2)
let test3 = sec (dual 1 2)
let test4 = Dual.sec (dual 1 2) // this would be required if AutoOpen wasn't provided
```
The downsides to this `AutoOpen` approach are:
1. Auto-opening the `Dual` type auto-opens all the members, so now `Sin`, `Cos`, etc. pollute where `Dual` is opened.
3. Needing to use auto-open to get an implementation of `sec` to behave as `sin` and `cos` constrains the design of the `Dual` type. For example, I'd like to do something like:
```fsharp
[]
type Dual = {Real: float; Dual: float} with
override this.ToString() = $"{this.Real.ToString()} + {this.Dual.ToString()}e"
let inline dual a b = {Real = a; Dual = b}
type Dual with
static member Sin {Real = a; Dual = b} = dual (sin a) (b * cos a)
static member Cos {Real = a; Dual = b} = dual (cos a) (b * (- sin a))
static member sec (x: Dual) = 1 / cos x
let test1 = sec (dual 1 2) // this is an error: "The value or constructor `sec` is not defined."
let test2 = Dual.sec (dual 1 2) // this works but is awkward
```
But note that this *does not* auto-open the type extensions, so now `Dual.sec` is required. The attribute is also not allowed to annotate the type extension. I.e., this is not allowed:
```fsharp
[]
type Dual = {Real: float; Dual: float} with
override this.ToString() = $"{this.Real.ToString()} + {this.Dual.ToString()}e"
let inline dual a b = {Real = a; Dual = b}
[] // this is an error: "Attributes cannot be applied to type extensions."
type Dual with
static member Sin {Real = a; Dual = b} = dual (sin a) (b * cos a)
static member Cos {Real = a; Dual = b} = dual (cos a) (b * (- sin a))
static member sec (x: Dual) = 1 / cos x
```
4. It becomes even more awkward when implementing other functions, such as the overload for `tan`. For example:
```fsharp
let private fsec x = 1.0 / cos x
[]
type Dual = {Real: float; Dual: float} with
override this.ToString() = $"{this.Real.ToString()} + {this.Dual.ToString()}e"
let inline dual a b = {Real = a; Dual = b}
type Dual with
static member Sin {Real = a; Dual = b} = dual (sin a) (b * cos a)
static member Cos {Real = a; Dual = b} = dual (cos a) (b * (- sin a))
static member Tan {Real = a; Dual = b} = dual (tan a) (b * (fsec a)**2)
static member sec (x: Dual) = 1 / cos x
```
Although `sec` is not available outside of the `Dual` scope without doing `Dual.sec`, despite having `AutoOpen` on the `Dual` type, it is available *inside* the `Dual` type. But because `sec` is not built-in for `float`, I need to define it. If I instead change the above `fsec` to `sec`, this now conflicts with `sec` in the definition of `Tan`.
## Pros and Cons
The advantages of making this adjustment to F# are that numerical code for both `float` *and* new numerical types via overloads becomes much more streamlined by providing a complete collection of trigonometric functions. This helps simplify F# numerical code in competition with other such competitors in the data science and scientific computing space of Python, Julia, MATLAB, etc.
The disadvantages of making this adjustment to F# are basically none other than increasing some very minor code maintenance. The primary issue is that the `System.Math` library in .NET does not provide these additional functions. Since my main concern is F#, my proposal is two-pronged:
1) Add the additional trigonometric functions to `FSharp.Core.Operators`, implementing them naively using the existing "intrinsic" functions or some other way.
2) Make a .NET suggestion to expand the trigonometric function covered in `System.Math`. See [here](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Math.cs) and [here](https://github.com/dotnet/runtime/blob/main/src/coreclr/classlibnative/float/floatdouble.cpp). If that is ever approved and implemented, then update `FSharp.Core.Operators` to use these implementations. If it isn't approved, then stick with the solution in (1).
## Extra information
Estimated cost (XS, S, M, L, XL, XXL):
Related suggestions: (put links to related suggestions here)
## Affidavit (please submit!)
Please tick this by placing a cross in the box:
* [x] This is not a question (e.g. like one you might ask on [stackoverflow](http://stackoverflow.com)) and I have searched stackoverflow for discussions of this issue
* [x] I have [searched both open and closed suggestions on this site](http://github.com/fsharp/fslang-suggestions/issues) and believe this is not a duplicate
* [x] This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.
Please tick all that apply:
* [x] This is not a breaking change to the F# language design
* [x] I or my company would be willing to help implement and/or test this
## For Readers
If you would like to see this issue implemented, please click the :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the FSharp.Core.Operators documentation and compare the requested derived functions with System.Math.cs and floatdouble.cpp, which the issue identifies as relevant implementation points. Determine the operator surface and implementation approach, then verify that the complete requested collection is available for existing and overloaded numeric types.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- fsharp
- Domain
- api
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100