I'm developing a Discord bot in F# and trying to use the Discord.Net library. While following the guide on slash commands, I tried to copy this example from C# to F#:
var guildCommand = new Discord.SlashCommandBuilder()
.WithName("list-roles")
.WithDescription("Lists all roles of a user.")
.AddOption("user", ApplicationCommandOptionType.User, "The users whos roles you want to be listed", isRequired: true);
Copying the code, I get this:
#r "nuget: Discord.Net,3.15.0"
open Discord
let testCommandBuilder =
(new SlashCommandBuilder())
.WithName("list-roles")
.WithDescription("Lists all roles of a user.")
.AddOption(
"user",
ApplicationCommandOptionType.User,
"The users whos roles you want to be listed",
isRequired = true
)
I get no errors (usually red squiggly lines) in my editor for this code. However, when I run this snippet using dotnet fsi
(Microsoft (R) F# Interactive version 12.8.102.0 for F# 8.0
), I get this error:
error FS0505: The member or object constructor 'AddOption' does not take 3 argument(s). An overload was found taking 1 arguments.
Checking the documentation for AddOption, I see that that the method takes 3 required parameters (name
, type
, and description
) while the rest either have default values or are optional. The source code shows this as well. The VSCode F# extension seems to confirm this, as I see the following when typing the parameters:
Upon more experimenting, I found that if I included all the parameters with default values in C#, the code compiles:
#r "nuget: Discord.Net,3.15.0"
open Discord
let testCommandBuilder =
(new SlashCommandBuilder())
.WithName("list-roles")
.WithDescription("Lists all roles of a user.")
.AddOption(
"user",
ApplicationCommandOptionType.User,
"The users whos roles you want to be listed",
isAutocomplete = false,
options = ([] |> ResizeArray<SlashCommandOptionBuilder>),
channelTypes = ([] |> ResizeArray<ChannelType>),
nameLocalizations = Map.empty,
descriptionLocalizations = Map.empty,
choices = [||]
)
However, I'm not sure why this is necessary if the parameters show up as optional in the parameters. Likewise, if I try to construct an equivalent F# example, I don't need to specify values for the default parameters explicitly:
open System.Runtime.InteropServices
type Example() =
member this.Function(x: string, y: int, z: string, [<Optional; DefaultParameterValue(5)>] a: int, ?b: bool) = a
let x = (new Example()).Function(x = "hi", y = 0, z = "hi")
printfn "%d" x // prints 5
How do I call the AddOption
function without specifying values for all the optional parameters with default values?
EDIT:
To test this further, I created a Github Repository with a simple fsproj
file
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Discord.Net" Version="3.15.0" />
</ItemGroup>
</Project>
and the exact same code as the first F# snippet in Program.fs
, with the #r
directive removed. I set up Github Actions to run this with the following workflow:
name: Build
on:
push:
branches:
- main
jobs:
build_204:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.204'
- name: Version setup / checks
run: |
dotnet new globaljson --sdk-version 8.0.204 --force
dotnet --version
- run: |
dotnet run
build_300:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.300'
- name: Version setup / checks
run: |
dotnet new globaljson --sdk-version 8.0.300 --force
dotnet --version
- run: |
dotnet run
For versions up to and including .NET SDK 8.0.204, the build fails. For versions of .NET SDK 8.0.300 and higher, the build succeeds. What change was originally causing this issue?
OK, I think I know why this is failing to compile for you. The problem is the choices
parameter array at the end of the C# method's signature:
public SlashCommandBuilder AddOption(string name, ApplicationCommandOptionType type,
string description, bool? isRequired = null, bool? isDefault = null, bool isAutocomplete = false, double? minValue = null, double? maxValue = null,
List<SlashCommandOptionBuilder> options = null, List<ChannelType> channelTypes = null, IDictionary<string, string> nameLocalizations = null,
IDictionary<string, string> descriptionLocalizations = null,
int? minLength = null, int? maxLength = null, params ApplicationCommandOptionChoiceProperties[] choices)
Calling a C# method that contains both a parameter with a default value and a parameter array is not well supported from F# (at least until recently). You can see open issues about this here and here. One easy workaround is to pass an explicit value for the parameter array:
let testCommandBuilder =
(new SlashCommandBuilder())
.WithName("list-roles")
.WithDescription("Lists all roles of a user.")
.AddOption(
"user",
ApplicationCommandOptionType.User,
"The users whos roles you want to be listed",
isRequired = true,
choices = Array.empty
)
It looks like this limitation was lifted in the latest version of the F# compiler (fsc --version
= 12.8.300.0), although I didn't see it described in any release notes and the issues I referenced above are still open. (The version of .NET 8 installed is irrelevant, I think. It looks like the difference is actually in the F# compiler, which is part of the SDK. This may explain why your builds above show different behavior, although I don't know much about how GitHub actions work.)