Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow named fields in active patterns #1341

Open
smoothdeveloper opened this issue Dec 13, 2023 · 5 comments
Open

allow named fields in active patterns #1341

smoothdeveloper opened this issue Dec 13, 2023 · 5 comments

Comments

@smoothdeveloper
Copy link
Contributor

I'm trying to use a library which exposes active patterns instead of the data types with their internal definition.

https://github.com/fsprojects/FSharp.Data/blob/8a6688f34abede0a80306e6c802601ef74edf473/src/FSharp.Data.Html.Core/HtmlActivePatterns.fs

The experience isn't as good as direct access to the representation, but it could be improved:

I suggest we allow active patterns to define field names:

module HtmlActivePatterns =
    let (|HtmlElement(name,attributes,elements)|HtmlText(content)|HtmlComment(content)|HtmlCData(content)|) (node: HtmlNode) =
        match node with
        | HtmlNode.HtmlText content -> HtmlText(content)
        | HtmlNode.HtmlComment content -> HtmlComment(content)
        | HtmlNode.HtmlCData content -> HtmlCData(content)
        | HtmlNode.HtmlElement (name, attributes, elements) -> HtmlElement(name, attributes, elements)

Here, I defined the field names explicitly (this is just a suggested syntax).

Possibly, if those aren't defined, but the value given is a plain identifier like in the code above, it would put the equivalent of nameof identifier for the name.

Possibly, we would issue a warning (on by default in new projects), when the names are not defined.

If we don't want to change the representation, we may make this work like C# value tuple with names work.

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-tuples#tuple-field-names

The deconstruction syntax would allow partial deconstruction, using the usual syntax with explicit field names instead of positional one.

We may also allow the constructor syntax to specify the names (and have a check that enforces consistency):

module HtmlActivePatterns =
    let (|HtmlElement|HtmlText|HtmlComment|HtmlCData|) (node: HtmlNode) =
        match node with
        | HtmlNode.HtmlText content -> HtmlText(content=content)
        | HtmlNode.HtmlComment content -> HtmlComment(content=content)
        | HtmlNode.HtmlCData content -> HtmlCData(content=content)
        | HtmlNode.HtmlElement (name, attributes, elements) -> HtmlElement(name=name, attributes=attributes, elements=elements)
@auduchinok
Copy link

I think that might complicate active patterns too much. What could be done instead is to allow anonymous records in patterns. This would naturally extend an existing feature without requiring a proper design like the one that would be needed for adding fields to active patterns.

@smoothdeveloper
Copy link
Contributor Author

smoothdeveloper commented Dec 13, 2023

allow anonymous records in patterns

I'd like to be able to deconstruct those partially, but there is still the concern of active patterns being ingrained for the kind of thing that my suggestion touches on, it is right in the guidelines:

https://learn.microsoft.com/en-us/dotnet/fsharp/style-guide/component-design-guidelines#hide-the-representations-of-discriminated-unions-for-binary-compatible-apis-if-the-design-of-these-types-is-likely-to-evolve

Given this less than ideal situation, I'm feeling partial deconstruction, and ability label fields is orthogonal to "pattern match anonymous records".

I've spent some time considering how anonymous and named records are used in context of DU while I was pondering on sharing this suggestion (idea if there would be a possibility to use record syntax for DU case which has all fields named, but it could be ambiguous); it still feels (to me) this suggestion, and the infrastructure to deal with named fields without changing the representation (like C# for tuples), could be a good fit to active patterns, especially in the context they are recommended for in terms of ABI, but tend to pale in terms of tooling & analysis support compared to matching the concrete representation.

I could see this infrastructure as being useful in other contexts, and maybe, already present in context of non generative type providers.

edit: I'll add that for context of binary compatibility that active patterns are advertised for, I feel not allowing names, and only using positional is actually problematic.

@auduchinok
Copy link

I'd like to be able to deconstruct those partially,

That's exactly what you can do with record patterns.

@smoothdeveloper
Copy link
Contributor Author

I'll consider an update to the design guideline, in meantime this suggestion or other enhancements to active pattern comes up, I think the recommendation with active pattern should be that they ideally return a record to allow partial and named deconstruction.

The issue with record is then they don't allow positional, which is also what people may want.

This is something only DU allow (picking named or positional, and named allowing partial deconstruction), and somehow, it feels active pattern should allow the same.

@smoothdeveloper
Copy link
Contributor Author

Active patterns act a bit like a tailored callback which is a predicate, conceptually close to a function signature (type signatures, fun lambdas; delegates, or method members);

those things "ideally always" need to give the possibility to attach a label on top of the positional type/value.

C# allows this kind of "extra meta info" on the tuples, F# has the anonymous record types (but their il representation is like a record AFAIK), CompiledNameAttribute, non generative type providers, where some kind of "name possibly only in metadata, not IL name of members" situation occurs.

We could change the underlying representation of active pattern to an anonymous DU, and have a syntax to define the name, which works like DU, but defined in the banana clips to meet this suggestion.

I also don't understand how the design guideline around it helps with binary compatibility / hiding representation. Having an active pattern layer sounds like having a specific "visitor pattern" implemented, but this thing can also change over time like a DU (but with no naming of fields possible, so far);

a visitor pattern that is manually implemented though (the impl. body of the active pattern is a visitor pattern, but which only allows a single call, and no naming of arguments), can have use for repeated calls, or submitting several patterns, rather than a single return value, to help maintain binary compatibility / hiding representation.

In context of pattern matching, and using active patterns for binary compat, as we know, it would just match the first case that is in the match area, and allow to add a different case (possibly named the same, but different set of fields) for context of API evolving, but more possibilities to match the same thing added by the API author.

This could specifically be a feature in the language around DUs (a way to add a visitor layer API, that is hand written, but attached to the type), that would generate visitor interface with extensible set of callbacks, that are possibly "API or representation"-change resistant, encapsulated in the active pattern concept of F# but maybe some other ways as well (to vend an interface of type "visitor pattern").

I think by now, this suggestion is just an area of reflection about active patterns, representation, and expressivity of the match (allowing or not named and positional, etc.); if the specific suggestion in the top post doesn't meet interest, maybe we can transfer the issue as a discussion.

Aside, along your line of reasoning, maybe F# can have a special syntax for the mighty "anything deconstructor" that would express the match as an anonymous record? It is separate usefulness than what I'm after (active patterns should enable labels on the values, similar as some think the same should apply to fun lambdas or function signatures), but it could be something to consider:

Rather than adding "one more syntax specific to anonymous record", we can consider if there is something we can do with "anonymous record" syntax in the context of pattern match, so it could unify different syntaxes by supporting it on more things than anonymous record, but be the path towards a more general pattern matching.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants