Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create an empty list of a specific runtime type

Tags:

list

f#

Given a the .Net Type, say typeof<string>, at runtime how does one created the equivalent of string list = []?

My motivation is that when using FSharpValue.MakeRecord to create a record based on values parsed the values need to be passed as a obj[]. I've been up casting the arguments using box and that has worked except for with lists. The issue I've encountered is that a empty untyped list list can't be boxed and then unboxed. The specific error returned is:

System.InvalidCastException: Unable to cast object of type 
'Microsoft.FSharp.Collections.FSharpList`1[System.Object]' 
to type 
'Microsoft.FSharp.Collections.FSharpList`1[System.String]'.

An empty typed list can be boxed and unboxed so I've tried to find a way to cast a list to a runtime type, e.g. a Type returned by typeof<> but with no luck.

type Test = {Names : string list}
// fails
let genericList = []
{Names = unbox (box genericList)}

//works
let typedList : string list = []
{Names = unbox (box typedList)}

//works
let genericNonEmptyList = ["Bill"]
{Names = unbox (box genericNonEmptyList)}
like image 776
jbeeko Avatar asked Dec 20 '16 23:12

jbeeko


People also ask

How do you create an empty list?

You can create an empty list using an empty pair of square brackets [] or the type constructor list() , a built-in function that creates an empty list when no arguments are passed. Square brackets [] are commonly used in Python to create empty lists because it is faster and more concise.

How do you create an empty list in darts?

To create an empty list, use [] for a growable list or List. empty for a fixed length list (or where growability is determined at run-time). The created list is fixed-length if length is provided. The list has length 0 and is growable if length is omitted.


3 Answers

Using reflection you can get the List module and call the generic empty method:

open System
open System.Reflection

let emptyList (t:Type) =
    Assembly.GetAssembly(typeof<_ list>)
        .GetType("Microsoft.FSharp.Collections.ListModule")
        .GetMethod("Empty", BindingFlags.Static ||| BindingFlags.Public)
        .MakeGenericMethod(t)
        .Invoke(null, [||])

use as follows:

let itemType = typeof<string>
let emptyStringList = emptyList(itemType) :?> string list

If you are calling that quite often, consider caching (reduces execution time by ~1/3):

let emptyList =
    let empty =
        Assembly.GetAssembly(typeof<_ list>)
            .GetType("Microsoft.FSharp.Collections.ListModule")
            .GetMethod("Empty", BindingFlags.Static ||| BindingFlags.Public)
    fun (t:Type) -> empty.MakeGenericMethod(t).Invoke(null, [||])
like image 177
CaringDev Avatar answered Nov 04 '22 11:11

CaringDev


Let me add one more alternative answer - although both of the existing methods work, they rely on understanding how F# represents lists. In the first case, you need to know there is Empty method and in the second case, you need to know there is a union case called Empty.

I generally prefer to do this by defining a helper type and using reflection over my custom type:

type ListHelper =
  static member Empty<'T>() : list<'T> = []

let makeEmpty =
  let empty = typeof<ListHelper>.GetMethod("Empty")
  let emptyArr : obj[] = [| |]
  fun ty -> empty.MakeGenericMethod([| ty |]).Invoke(null, emptyArr)

This gives you quite simple function that can cache the MethodInfo (you could even use Expression to pre-compile and cache the invocations) and does not rely on clever tricks.

like image 21
Tomas Petricek Avatar answered Nov 04 '22 10:11

Tomas Petricek


@CaringDev's answer using .NET reflection is fine, but you can also use the F#-specific reflection module to create instances of union cases:

let empty ty = 
    let uc = 
        Reflection.FSharpType.GetUnionCases(typedefof<_ list>.MakeGenericType [|ty|]) 
        |> Seq.filter (fun uc -> uc.Name = "Empty") 
        |> Seq.exactlyOne
    Reflection.FSharpValue.MakeUnion(uc, [||])
like image 37
kvb Avatar answered Nov 04 '22 11:11

kvb