Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there already or can I declare a more pipe friendly upcast?

Tags:

f#

I want to be able to just

let upcast'<'T,'TResult when 'T :> 'TResult> (y:'T) = y |> upcast

However, that then constrains 'T to be 'TResult instead of it being something that can be cast to 'TResult

I know I can

  1. |> fun x -> x :> 'TResult

  2. |> fun x -> upcast x

  3. |> fun x -> x :> _

but then if I'm doing anything else on that line I have to go back and put () around the fun x -> upcast x or it thinks what I'm doing is part of the fun x function.

can I define or does there exist a way to be able to

|> upcast |> doesn't work

|> ( ( :> ) 'TResult) doesn't work and is messy

edit In response to Thomas Petricek - minimal failing auto-upcast sample:

module Test =
    let inline f'<'t>():IReadOnlyCollection<'t> =
        List.empty
        |> ResizeArray
        |> System.Collections.ObjectModel.ReadOnlyCollection
        |> fun x -> x :> IReadOnlyCollection<_>

    let inline f<'t> () :IReadOnlyCollection<'t> =
        List.empty
        |> ResizeArray
        |> System.Collections.ObjectModel.ReadOnlyCollection
like image 570
Maslow Avatar asked Nov 08 '18 18:11

Maslow


2 Answers

As far as I know, specifying the kind of constraint between 'T and 'TResult is not possible. There is a related question about this with links to more information and a feature request.

That said, I wonder why do you need this? The F# compiler is able to insert upcasts automatically, even when using pipes, so if you want to do this as part of a longer pipe, it should not be needed. Here is a simple illustration:

type Animal = interface end
type Dog = inherit Animal 

let makeDog () = { new Dog }
let consumeAnimal (a:Animal) = 0

makeDog () |> consumeAnimal

I guess you might need pipe-able upcast if you wanted to have it at the end of the pipeline, but then I'd just do the upcast on a separate line. Or is your question motivated by some more complicated cases where the implicit upcast does not work?

EDIT 1: Here is a minimal example using ReadOnlyCollection and IReadOnlyList which works:

let foo () : System.Collections.ObjectModel.ReadOnlyCollection<int> = failwith "!"
let bar (x:System.Collections.Generic.IReadOnlyList<int>) = 0

foo() |> bar

EDIT 2: To comment on the update - the problem here is that automatic upcasts are only inserted when passing arguments to functions, but in the second example, the type mismatch is between the result of the pipe and the return type of the function. You can get that to work by adding an identity function of type IReadOnlyCollection<'T> -> IReadOnlyCollection<'T> to the end of the pipe:

let inline f<'t> () :IReadOnlyCollection<'t> =
    List.empty
    |> ResizeArray
    |> System.Collections.ObjectModel.ReadOnlyCollection
    |> id<IReadOnlyCollection<_>>

This works, because now the upcast is inserted automatically when passing the argument to the id function - and this then returns a type that matches with the return type of the function.

like image 192
Tomas Petricek Avatar answered Oct 06 '22 02:10

Tomas Petricek


much simpler and unexpected

let inline f2<'t>() : IReadOnlyCollection<'t> = 
    List.empty
    |> ResizeArray
    |> System.Collections.ObjectModel.ReadOnlyCollection
    :> _
like image 45
Maslow Avatar answered Oct 06 '22 02:10

Maslow