Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

2 Ideas in 1 discussion: Extending records and Type pattern #18294

afshin29 started this conversation in Ideas
Discussion options

Note: I'm just sharing an idea, I don't know if it's possible to be implemented or if it's worth the difficulties of implementation.

Extending Records

When a user wants to create an order the client app sends this (simplified) form model:

type CartItem = {
 ProductId: int
 Quantity: int
}
type CreateOrderFormModel = {
 CurrencyCode: string
 UsePoints: bool
 UseWallet: bool
 DiscountCode: string option
 ClearCart: bool
 Items: CartItem list
}

When an admin operator wants to create an order for a customer the admin panel client sends this (simplified) form model:

type AdminCartItem = {
 ProductId: int
 Quantity: int
 PriceOverwrite: decimal option
}
type CreateAdminOrderFormModel = {
 UserId: string
 CurrencyCode: string
 UsePoints: bool
 UseWallet: bool
 Items: AdminCartItem list
}

Unfortunately, there is no way to extend/inherit a base record type without going to the OOP side of the F#, ideally something like this would be nice:

type CartItem = {
 ProductId: int
 Quantity: int
}
abstract type BaseOrderFormModel = {
 CurrencyCode: string
 UsePoints: bool
 UseWallet: bool
 Items: CartItem list
}
type CreateOrderFormModel extend BaseOrderFormModel = {
 DiscountCode: string option
 ClearCart: bool
}
type AdminCartItem extend CartItem = {
 PriceOverwrite: decimal option
}
type CreateAdminOrderFormModel extend BaseOrderFormModel = {
 UserId: string
 Items: AdminCartItem list
}

The extend keyword would just copy the base record fields, and we would still get two separate sealed classes. Any field of the base record can be overwritten by simply reusing its name and assigning a different type to it.

Type Pattern

Consider the following valid F# code:

type T0 = { Name : string }
type T1 = {
 Name : string
 Age : int
}
type T2 = {
 Name : string
 Location : string
}
let inline getName(a : 'T when 'T : (member Name : string)) = a.Name
let a = { Name = "a" }
let b = { Name = "b"; Age = 22 }
let c = { Name = "c"; Location = "home" }
printfn $"Name: {getName a}"
printfn $"Name: {getName b}"
printfn $"Name: {getName c}"

If we want a function that would extract the Name out of any type that has a Name field, we would write the getName function, which is very ugly to look at.

It would be great if we could do this for example:

let getName(a :##{| Name: string |}) = a.Name

which says give me any type (named or anonymous) that has a Name field with the type string.

Or this:

let getName(a :##T0) = a.Name

which says give me any type (named or anonymous) that has all of the fields of the type T0, and possibly more without necessarily being inherited from it.

In that case when I want to write a function that would validate the currency code, I could write this:

let validateCurrency(model:##{| CurrencyCode: string |}) : Result<string, string> =
 // ...

and if I want to validate order items, I could write this:

let validateOrderItems(model:##{| Items:##CartItem |}) : Result<string, string> =
 // ...

and if I want to validate the UseWallet for a customer, I know it must be compatible with BaseOrderFormModel:

let validateUserWallet(model:##BaseOrderFormModel) : Result<string, string> =
 // ...

and if I want to validate a customer for admin order creation, I know it must be a CreateAdminOrderFormModel:

let validateOrderCustomer(model: CreateAdminOrderFormModel) : Result<string, string> =
 // ...

and if I want to validate a discount code, I know it must be a CreateOrderFormModel:

let validateDiscountCode(model: CreateOrderFormModel) : Result<string, string> =
 // ...

and so on.

Finally, if I want to group a bunch of validation functions together and pass them to a workflow (which would then try to get the first function that would return an error), maybe I could do something like this:

let orderValidations: (CreateOrderFormModel -> Result<string, string>) seq = seq {
 validateCurrency
 validateOrderItems
 validateUserWallet
 validateOrderDiscountCode
 // ...
}
let adminOrderValidations: (CreateAdminOrderFormModel -> Result<string, string>) seq = seq {
 validateCurrency
 validateOrderItems
 validateUserWallet
 validateOrderCustomer
 // ...
}

The good news is that the validateCurrency function is not tied to any specific model so anywhere in the app that I need to validate a currency I can use this function (meaning I can include this function in any validation group as long as the model has a CurrencyCode field of string). The validateUserWallet requires a ##BaseOrderFormModel so I can happily reuse it in two different workflows with two different models (because both models extend that base type). The same goes for validateOrderItems. And finally, the validateOrderDiscountCode and validateOrderCustomer can only be included in validation groups that have their specific required models.

As you can see the 2 ideas are somewhat related to each other. I know that currently having these features are (mostly) possible if we go to the OOP side of F# (with heavy use of object inheritance) because I have done a similar approach in C#, but it would be nice if we didn't have to (going to that side kind of defeats the purpose of using F# in the first place).

You must be logged in to vote

Replies: 2 comments 6 replies

Comment options

Maybe https://github.com/fsharp/fslang-suggestions/discussions would be a better place for this discussion?

Anyway, I think that fsharp/fslang-suggestions#1253 would address your first "extending records" suggestion, although it wouldn't be through inheritance.

Your second suggestion looks like fsharp/fslang-suggestions#11, which has been declined.

You must be logged in to vote
6 replies
Comment options

I think the issue 1253 is for record instantiation, my suggestion is for record type definition.

fsharp/fslang-suggestions#1253 (comment) does mention types and gives an example with records that is exactly what you are suggesting:

Types?

How about type syntax? e.g.

type A = {
 X: int
 Y: int
}
type B = {
 Z: int
}
type C = {
 ...A;
 ...B;
 Extra: string
}
let a = { X = 1; Y = 2 }
let b = { Z = 3 }
let c = { ...a; ...b; Extra = "four" }

Spreading record types into other record types seems tempting, but note this would not give rise to subtyping.

As for your second suggestion: yes, I understand. That kind of "duck typing" is exactly what is meant by row polymorphism in fsharp/fslang-suggestions#11.

Comment options

I'm new to F# (❤️), I hope I'm making sense.

Welcome :)

Maybe we should add some kind of pinned post under discussions here to make it easier for people to find the language suggestions repo for language-suggestion-related things.

Comment options

baronfel Feb 6, 2025
Collaborator

@brianrourkeboll it's possible to make an issue template that just points to an entirely different repo - checkout the new issue list in dotnet/sdk/issues for an example of what that can look like

Comment options

Yeah, this repo already has that for issues:

- name: F# Language Suggestions
url: https://github.com/fsharp/fslang-suggestions
about: Language features discussions here.

It just doesn't have anything obvious under https://github.com/dotnet/fsharp/discussions or https://github.com/dotnet/fsharp/discussions/new/choose.

Comment options

...You can create discussion category form templates (https://github.com/orgs/community/discussions/2838), but it doesn't look like you can easily redirect with a link like you can with issue templates.

Comment options

Yes, this should go to suggestions repo

You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

AltStyle によって変換されたページ (->オリジナル) /