Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to instantiate a generic record with explicit type parameters?

I want to explicitly provide type parameters when I instantiate a generic record. To put in in another words, given a RecordType<'T1, 'T2, 'T3>, I want create an instance of RecordType<'T1, 'T2, 'T3> with some fixed 'T1, 'T2 and 'T3 by specifying those generic parameters. Is there a way to do that in F#?

I see three cases where it's useful:

  1. Instantiating a record when there's more than one generic type with the same name

    Assume that we have following record definitions:

    type SimpleGenericRecord<'T1, 'T2> = {
        f1 : 'T1 -> 'T2
    }
    
    type SimpleGenericRecord<'T> = {
        f1 : 'T -> 'T
    }
    

    I easily construct instances of SimpleGenericRecord<'T> which was defined last:

    let record = {
        f1 = fun (x: int) -> 0
    } 
    
    let record1 = {
        SimpleGenericRecord.f1 = fun (x: int) -> 0
    }
    

    Following attempts to create SimpleGenericRecord<int, int> give compile errors:

    let record2 = {
        SimpleGenericRecord<int, int>.f1 = fun (x: int) -> 0
    }
    
    let record3 = {
        SimpleGenericRecord<_, _>.f1 = fun (x: int) -> 0
    }
    

    I know that having the same record name for two types might be not the best idea, nevertheless, I think that language should give me a way to use both types.

  2. Documenting record types

    F# reference says:

    Don't use the DefaultValue attribute with record fields. A better approach is to define default instances of records with fields that are initialized to default values and then use a copy and update record expression to set any fields that differ from the default values.

    Following that piece of advice, I would like to define default instances of records and, since they are part of a public API, document their types.

  3. Helping in type inference

    Generic parameters of a record type could be used to infer types of record's values.

    Assume that I have:

    type RecordWithSomeComplexType<'T> = {
        t1 : int -> System.Collections.Generic.Dictionary<int, 'T> // some long type signature
    }
    

    and I want to instantiate it. If I don't provide any type annotations, record values will be as as general as possible, e.g.

    let record4 = {
        RecordWithSomeComplexType.t1 = failwith "Intentionally failing"
    }
    

    has type

    int -> System.Collections.Generic.Dictionary<int, obj>
    

    I can force a record to be of a certain type (e.g. RecordWithSomeComplexType<string>), but in that case I need to write full type of certain value, e.g.

    let failing = {
        RecordWithSomeComplexType.t1 = 
            failwith "Intentionally failing"  :> int -> System.Collections.Generic.Dictionary<int, string> 
            // I don't want to provide a full type of a value here
    }
    

    If compiler knew that I want to RecordWithSomeComplexType<string>, it could infer value's signature.

like image 637
Tomasz Maczyński Avatar asked Aug 25 '16 22:08

Tomasz Maczyński


1 Answers

You can add type annotation pretty much anywhere

let record2 : SimpleGenericRecord<_, _> = {
    f1 = fun (x: int) -> 0
}

// alternative
let record2 =
  ({
    f1 = fun (x: int) -> 0
  } : SimpleGenericRecord<_, _>)

And for the longer case you can write an alias type to ease things

type Alias<'T> = int -> System.Collections.Generic.Dictionary<int, 'T>

let record4 = {
  t1 = (failwith "Intentionally failing" : Alias<string>)
}

Note that record4 evaluation will raise the exception instantly because it isn't delayed

like image 190
Sehnsucht Avatar answered Nov 15 '22 07:11

Sehnsucht