Skip to content

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Oct 16, 2025

Summary

Fixes #[issue_number]

This PR removes the misleading "Anonymous records are nominal" subsection from the F# anonymous records documentation, as recommended by @T-Gro in the issue discussion.

Problem

The documentation incorrectly stated that anonymous records are nominal types, when in fact they exhibit structural typing behavior. The provided examples in the section didn't actually prove nominality.

As demonstrated in the issue, anonymous records with identical structure are compatible:

type AR1 = {| X: int |}
type AR2 = {| X: int |}

let ar1 : AR1[] = [| |]
let ar2 : AR2[] = ar1 // fine - AR2 is identical to AR1

This is characteristic of structural typing, not nominal typing. In contrast, named records with the same structure are incompatible because they truly are nominal types.

Changes

  1. Removed the entire "Anonymous records are nominal" subsection - This section was misleading and added type-theory complexity without providing practical value to developers.

  2. Updated the limitations section - Changed the explanation from "they are nominal types" to "they require exact field matching" to more accurately describe why anonymous records don't support structural subtyping.

The documentation now flows naturally into the "Anonymous records use structural equality and comparison" section, which correctly describes their behavior.

Validation

  • ✅ Markdownlint passes
  • ✅ Document structure and flow verified
  • ✅ All references to "nominal" removed from the article
Original prompt

This section details on the original issue you should resolve

<issue_title>Anonymous records are not nominal</issue_title>
<issue_description>### Type of issue

Outdated article

Description

The paragraph:

Anonymous records are nominal types. They are best thought of as named record types (which are also nominal) that do not require an up-front declaration.

is misleading ‒ the code goes to show that anonymous records with different members are incompatible, but I am not sure what that is supposed to prove ‒ int * int is also incompatible with string * string and I haven't heard one call tuples nominal.

This should be the real proof:

type R1 = { X: int }
type R2 = { X: int }

let r1 : R1[] = [| |]
let r2 : R2[] = r1 // error - type mismatch

type AR1 = {| X: int |}
type AR2 = {| X: int |}

let ar1 : AR1[] = [| |]
let ar2 : AR2[] = ar1 // fine - AR2 is identical to AR1

Two declarations with a different name but same structure being equivalent is exactly what a structural system exhibits, as opposed to a nominal one.

Page URL

https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/anonymous-records

Content source URL

https://github.com/dotnet/docs/blob/main/docs/fsharp/language-reference/anonymous-records.md

Document Version Independent Id

7c5955cd-60ba-12a6-8661-d9b9b7700cf1

Platform Id

178aabe3-cc05-5195-37e1-d1bd63900af6

Article author

@KathleenDollard

Metadata

  • ID: b69dfedf-d03a-1733-5c06-7b2000bad772
  • PlatformId: 178aabe3-cc05-5195-37e1-d1bd63900af6
  • Service: dotnet-fsharp

Related Issues</issue_description>

Comments on the Issue (you are @copilot in this section)

@BillWagner @T-Gro @KathleenDollard Thoughts? @T-Gro This issue is correct - the examples does not prove much about nominality. With regular records, the nominality comes from the fully qualified name.

For anonymous records, nominality is from using the exact same set of fields and field types.

I think this section is not adding much by speaking about nominal types (as opposed to e.g. positional types, tuples) - we might end up better removing that sub-section alltogether.

let a = {| X = 1; Y = false |}
let b = {| Y = true; X = 5 |}
// We can compare a and b. They are same by using same fields


let tup1 = (1,true)
let tup2 = (false,5)
// We cannot compare two tuples, since the order is switched - tuples are positional types

But as written above, even though an explanation is possible, I do not see much value of describing it here. It's type-theory interesting, not end-user interesting I would say.

Fixes #46062


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.


Internal previews

📄 File 🔗 Preview link
docs/fsharp/language-reference/anonymous-records.md docs/fsharp/language-reference/anonymous-records

@Copilot Copilot AI changed the title [WIP] Fix misleading statements about anonymous records Remove misleading nominal types section from F# anonymous records documentation Oct 16, 2025
@Copilot Copilot AI requested a review from BillWagner October 16, 2025 12:53
Copilot finished work on behalf of BillWagner October 16, 2025 12:53
@BillWagner BillWagner marked this pull request as ready for review October 16, 2025 21:27
@BillWagner BillWagner requested review from a team as code owners October 16, 2025 21:27
@Copilot Copilot AI review requested due to automatic review settings October 16, 2025 21:27
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR removes the misleading "Anonymous records are nominal" subsection from the F# anonymous records documentation, as it incorrectly described the type system behavior of anonymous records. The documentation previously stated that anonymous records are nominal types, when they actually exhibit structural typing behavior—two anonymous record type aliases with identical structure are compatible, which is characteristic of structural typing.

Key changes:

  • Removed the entire misleading subsection explaining nominality
  • Updated the pattern matching limitation explanation to use more accurate terminology

Anonymous records do not support pattern matching, unlike named records. There are three reasons:

1. A pattern would have to account for every field of an anonymous record, unlike named record types. This is because anonymous records do not support structural subtyping – they are nominal types.
1. A pattern would have to account for every field of an anonymous record, unlike named record types. This is because anonymous records do not support structural subtyping – they require exact field matching.
Copy link

Copilot AI Oct 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a frontmatter key ai-usage: ai-assisted to this file if AI was used to assist with these documentation changes, as per the .NET documentation guidelines.

Copilot generated this review using guidance from repository custom instructions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IEvangelist What's your thinking here? I think this change is below the bar.

Copy link
Member

@BillWagner BillWagner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is ready for final review.

@BillWagner BillWagner requested review from a team and IEvangelist October 16, 2025 21:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Anonymous records are not nominal

2 participants