fslang-suggestions icon indicating copy to clipboard operation
fslang-suggestions copied to clipboard

Downcast/Upcast operators and keywords as functions similar to infix operators

Open Swoorup opened this issue 2 years ago • 10 comments

I propose we allow both upcast (:>, upcast) and downcast (:?>, downcast) operators as functions similar to infix operators. For example.

let localityEvents = events |> Vector.map (:?> LocalityEvent)
let events = localityEvents |> Vector.map (:> IEvent)
let asIEvent = (:> IEvent)

// keywords
let localityEvents: Vector<LocalityEvent> = events |> Vector.map downcast
let events: Vector<IEvent> = localityEvents |> Vector.map upcast

The existing way of approaching this problem in F# is ...

let localityEvents = events |> Vector.map (fun event -> event :?> LocalityEvent)
let events = localityEvents |> Vector.map (fun localityEvent -> localityEvent :> IEvent)

// keywords
let events: Vector<IEvent> = localityEvents |> Vector.map (fun localityEvent -> upcast localityEvent)
let localityEvents: Vector<LocalityEvent> = events |> Vector.map (fun event -> downcast event)

Pros and Cons

The advantages of making this adjustment to F# are

  • Alignment with other infix operators and also box/unbox
  • Easy way to perform casting when needed.
  • It doesn't mask the casting operation. Code is still easily greppable to see where downcasting is perform.

The disadvantages of making this adjustment to F# are.

  • One more way to do the same thing.

Extra information

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

Related suggestions: (put links to related suggestions here)

Affidavit (please submit!)

Please tick this by placing a cross in the box:

  • [X] This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • [X] I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • [X] 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.

Please tick all that apply:

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

Swoorup avatar Sep 02 '21 12:09 Swoorup

((-) 1) means fun x -> 1 - x, not fun x -> x - 1. Similarly, ((:?>) LocalityEvent) would be fun x -> LocalityEvent :?> x which does not make sense. Instead, we could follow Seq.cast. Maybe we can have (List/Array/Seq/Vector).upCast<_, IEvent> or (List/Array/Seq/Vector).downCast<_, LocalityEvent>.

Happypig375 avatar Sep 02 '21 13:09 Happypig375

@Happypig375 updated. They aren't parameters in the normal sense, but together with type parameter they would behave as functions

Swoorup avatar Sep 02 '21 13:09 Swoorup

I think this makes sense for downcast and upcast but as @Happypig375 said above it's inconsistent semantics for :> and :?>.

Frassle avatar Sep 03 '21 13:09 Frassle

If the use case is for map, I do not see how introducing more special syntax would be better than upCast<_, ...> or downCast<_, ...>.

Happypig375 avatar Sep 03 '21 13:09 Happypig375

You'll have to litter your codebase for every single collection types that exists in the wild, since most of the operators act as functions I don't see why we couldn't do the same for this. Use case is wherever casting is involved, that includes custom codebase, not just well known libraries.

@Frassle the inconsistently has been addressed by adjusting the position of the braces.

Swoorup avatar Sep 03 '21 13:09 Swoorup

@Frassle the inconsistently has been addressed by adjusting the position of the braces.

Yes but that's also kinda inconsistent, in that no other operator supports it.

Frassle avatar Sep 03 '21 13:09 Frassle

You'll have to litter your codebase for every single collection types that exists in the wild

It's the responsibility of the collection types to implement collection functions that match List/Array/Seq module signatures. FSharpPlus can also add hypergeneric cast functions on top of map if needed.

since most of the operators act as functions I don't see why we couldn't do the same for this. Use case is wherever casting is involved, not just map.

The syntax of partially applying operators does not make sense with casting. You cannot have the operator without supplying the type.

Happypig375 avatar Sep 03 '21 14:09 Happypig375

One way forward for (:?> int) can be adding Haskell's syntax of partial application of infix operators at the same time, e.g. (1 +) -> (fun x -> 1 + x) and (+ 1) -> (fun x -> x + 1) This would provide much greater value. Unfortunately, (+ 1) is already interpreted as use of a prefix operator today.

Happypig375 avatar Sep 03 '21 14:09 Happypig375

Another way forward is to merge into #186 and #506 and write (_ :?> int).

Happypig375 avatar Sep 04 '21 15:09 Happypig375

I'm not inclined to this feature

  • upcast and downcast are almost at the level of being deprecated in F# and almost never used in samples or tutorial cost.
  • The subtlty of (:> int) as a kind of partial application of a type to get a casting function is just too extreme for my liking. Just write it out explicitly.

Upcasting and downcasting is just de-emphasised in F# and occurs rarely enough that it seems better to be explicit.

Of the two parts, it's more possible that I could be convined to allow upcast and downcast as first-class functions, sort of resurrecting them back from the edge of deprecation :)

dsyme avatar Jun 16 '22 16:06 dsyme