fsharp / fsharp/fslang-suggestions

Add additional trigonometric functions to `FSharp.Core.Operators`

Open
#1,256 6 comments 1 reaction 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.