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

Default Type Annotation #1374

Open
5 of 6 tasks
daveyostcom opened this issue Jul 7, 2024 · 6 comments
Open
5 of 6 tasks

Default Type Annotation #1374

daveyostcom opened this issue Jul 7, 2024 · 6 comments

Comments

@daveyostcom
Copy link
Contributor

daveyostcom commented Jul 7, 2024

I propose we allow default type annotation

Consider this classic example:

let token cancellationTokenSource =
  cancellationTokenSource.Token

// Error FS0072 : Lookup on object of indeterminate type based on information prior
// to this program point. A type annotation may be needed prior to this program point
// to constrain the type of the object. This may allow the lookup to be resolved.

The type has to be annotated somehow:

let token (cancellationTokenSource: CancellationTokenSource) =
  cancellationTokenSource.Token
let token cancellationTokenSource =
  (cancellationTokenSource: CancellationTokenSource).Token
let token cancellationTokenSource =
  let (cancellationTokenSource: CancellationTokenSource) = cancellationTokenSource
  cancellationTokenSource.Token

This is clutter, and F# doesn’t like clutter.

I propose a default type annotation syntax, which allows extracting type annotation clutter from code, moving it out of the way, to top of file perhaps, or to a module. For example:

type cancellationTokenSource: CancellationTokenSource

let token cancellationTokenSource =
  cancellationTokenSource.Token
type cts: CancellationTokenSource

let token cts = cts.Token
let token cts =
  type cts: CancellationTokenSource
  cts.Token

A default type annotation

  • is shadowed explicitly by type annotation
    type cts: CancellationTokenSource
    
    let length (cts: string) = cts.Length
  • is shadowed implicitly by type inferencing
    type cts: CancellationTokenSource
    
    let length cts =
      String.length cts   // cts is inferred to be a string

Default type annotation is a convenience feature. Implicit shadowing

  • allows the addition of default type annotations to library modules without breaking existing code
  • allows local usage to work as intended without worry of interference from an external convenience default

Alternatives

Another name for the feature might be “type annotation hint”.

The overloading of the type keyword does not seem to me a problematic addition to the grammar. I chose type because it is already a keyword directly related to the feature, and because it is short and monosyllabic. Conceivable alternatives that I can think of are annotation or annotate or hint, which suffer from various problems. Using any of those keywords seems infeasible at this late stage.

Perhaps there is some other concise syntax or modification to the proposed syntax that I have not thought of.

There is no existing way of approaching this problem in F#

Also, as far as I can tell, it does not exist in other languages.

Pros and Cons

Advantages

Significant reduction in clutter. It seems to me that the impossibility of inferencing the type of a in a.B is the most common source of type annotation clutter and is solved by the proposal.

Disadvantages

Reading and understanding code without the benefit of a smart interactive editor (which shows the type of a name with little effort) can be more difficult where a default type annotation name does not match the intended type, but I observe that

  • the readability problem is easily mitigated by using a name that matches the type
  • the readability problem already abounds with existing type inference
  • situations where code is read without a smart interactive editor, such as on paper, or in plain text in a PDF document or web page, will continue to become more and more rare

Extra information

Estimated cost (XS, S, M, L, XL, XXL):

XS?

Related suggestions: (put links to related suggestions here)

Affidavit (please submit!)

Please tick these items by placing a cross in the box:

  • This is not a question (e.g. like one you might ask on StackOverflow) and I have searched StackOverflow for discussions of this issue
  • This is a language change and not purely a tooling change (e.g. compiler bug, editor support, warning/error messages, new warning, non-breaking optimisation) belonging to the compiler and tooling repository
  • 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
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate

Please tick all that apply:

  • This is not a breaking change to the F# language design
  • 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 👍 emoji on this issue. These counts are used to generally order the suggestions by engagement.

@SchlenkR
Copy link

SchlenkR commented Jul 7, 2024

Feels like a shortcut on the highway to obfuscation :) Honestly:

My opinion in general is that when information becomes relevant for program understanding, it should be as local as possible, and type annotations fulfil that. This proposal would introduce a delocalisation that contradicts this.

In addition, I think it is very unusual for identifier names to have a meaning beyond their documentary character 1. It could be a hard and annoying process getting one's brain trained on such a concept. Also, what should happen when refactoring the name of that identifier?

Further, it seems that this syntax addition adresses a problem that might not occur often, since inference works very well for non-overloaded and non-dot-notated stuff.


Footnotes

  1. Yes, there are exceptions for that rule that come to mind: CE builder instance names. but in that case, they are more recognised as a keyword-level thing, not as value-level identifier.

@bartelink
Copy link

For the specific 'problem' of CancellationTokenSource, having some global aliases, i.e. placing a type CancellationToken = System.Threading.CancellationToken in namspace global can remove some noise.

You could do that but name it CTS - the win is that people can hover over it or press F12

Semi-related blog article re new C# 12 features in this direction https://devblogs.microsoft.com/dotnet/refactor-your-code-using-alias-any-type/

@vzarytovskii
Copy link

vzarytovskii commented Jul 7, 2024

I don't think it "removes the clutter", it adds a completely new (global) language construct, which uses existing one (type) in a different unfamiliar way. As well as adds a "new" source for the type inference, which has a special treatment.

I personally dislike the proposal, and don't think we should be adding it in the language, will add the "probably not", @dsyme, @cartermp, @abelbraaksma if you feel we should do it, please remove.

Besides, you can already do that in the signature file.

@cartermp
Copy link
Member

cartermp commented Jul 7, 2024

This would probably not be allowed on the basis that:

  1. There's no type inference for member definitions like this unless it's on the right-hand side of a pipeline
  2. There's no precedent for having a name of a binding influence the type of the binding

Furthermore, by using type abbreviations it's possible to reduce the clutter like @bartelink suggests:

open System.Threading

type CTS = CancellationTokenSource

let f (cts: CTS) = cts.Token

@dsyme
Copy link
Collaborator

dsyme commented Jul 9, 2024

I considered this feature for F# 1.0. The context was that in an earlier project (a theorem prover) I had been really surprised what a huge difference to readability and succinctness it had made to be able to declare that particular patterns of identifiers had particular types. It requires discipline to use such a feature well, and it makes more sense in the context of "mathematical" style of development - specifications, model implementations, proofs, tensor programming etc - but I think it's an under-explored are of typed-programming language design.

That said, I don't think it makes sense for F# as a general purpose programming language that needs to consider software engineering needs in teams and large codebases. It is the kind of feature which can be very powerful for the individual developer.

So I agree this is a "no" for F#, though I appreciate the suggestion

@teo-tsirpanis
Copy link

I also have thought of this feature in the past. By the way I have used dummy inline functions for more concise type annotations.

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

No branches or pull requests

7 participants