F# Add New Item to Collections.Map? - f#

I want to create a map and a function to add item to that map.
This is what I did
let mymap = Map.empty
let myfunc nId nValue =
mymap = Map.add nId nValue ;;
But this produced following Error
This expression was expected to have type Map<'a,'b> but here has type Map<'c,'d> -> Map<'c,'d>
What did I do wrong?

Maps are immutable so you need to do let mutable mymap.
Also, = does comparison, you need <- for assignment, which is why you got the error.
Something like
let mutable mymap = Map.empty
let myfunc nId nValue =
mymap <- Map.add nId nValue mymap;;
is what you want

Apart from assigning mymap to a new value as in John's answer, you can do it using a more idiomatic FP way:
let mymap = Map.empty
let myfunc nId nValue =
Map.add nId nValue mymap
Where John's myfunc has a signature of 'a -> 'b -> unit because of the assignment, mine has a signature of 'a -> 'b -> Map<'a, 'b>, which is more common in FP to return a new Map instead of modifying an existing map (mymap).

Related

Extend F# Arrays with lookup for bigint

I would like to extend F# Arrays such that I can use arrays without converting to the finite int. Instead I want to work with bigint directly.
I was able to add a second length method to the array type as follows:
type 'T ``[]`` with
member this.LengthI: bigint =
bigint this.Length
member this.Item(index: bigint): 'T =
this.[int index]
However the Item method cannot be called with the .[ ] syntax.
Any ideas how this could be achieved? I this possible at all?
I strongly suspect this isn't possible for native arrays. You can verify yourself that you can overload indexed access just fine for other collections.
If you compile the following code:
let myArray = [| "a" |]
let myList = [ "a" ]
let arrayElement = myArray.[11111]
let listElement = myList.[22222]
and inspect the resulting IL, you'll see that while accessing the list element compiles to a regular virtual call, there is a special CIL instruction for accessing a native array element, ldelem.
//000004: let arrayElement = myArray.[11111]
IL_002c: call string[] Fuduoqv1565::get_myArray()
IL_0031: ldc.i4 0x2b67
IL_0036: ldelem [mscorlib]System.String
IL_003b: stsfld string '<StartupCode$51dff40d-e00b-40e4-b9cc-15309089d437>'.$Fuduoqv1565::arrayElement#4
.line 5,5 : 1,33 ''
//000005: let listElement = myList.[22222]
IL_0040: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1<string> Fuduoqv1565::get_myList()
IL_0045: ldc.i4 0x56ce
IL_004a: callvirt instance !0 class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1<string>::get_Item(int32)
IL_004f: stsfld string '<StartupCode$51dff40d-e00b-40e4-b9cc-15309089d437>'.$Fuduoqv1565::listElement#5
IL_0054: ret
I would guess that the same compiler logic that special-case array access to that single instruction also bypass any overload resolution involving extension methods and the like.
One way to circumvent this is to wrap the array in a custom type, where overloaded indexers will work as you expect. Making the wrapper type a struct should reduce the performance loss in most cases:
type [<Struct>] BigArray<'T>(array : 'T[]) =
member this.LengthI: bigint =
bigint array.Length
member this.Item
with get(index : int) = array.[index]
and set (index : int) value = array.[index] <- value
member this.Item
with get(index : bigint) = array.[int index]
and set (index : bigint) value = array.[int index] <- value
let bigArray = BigArray myArray
let bigArrayElement = bigArray.[0]
let bigArrayElement2 = bigArray.[bigint 0]
Another one is to upcast the array to the base System.Array class, on which you can then define the same overloaded operator. This removes the need to create a wrapper type and duplicate all members of 'T[], as you can just upcast/downcast the same array object as necessary. However, since the base class is untyped, you will lose type safety and have to box/unbox the elements when using the indexed access, which is quite ugly:
type System.Array with
member this.Item
with get (index : int) = (this :?> 'T[]).[index]
and set (index : int) (value : 'T) = (this :?> 'T[]).[index] <- value
member this.Item
with get(index : bigint) : 'T = (this :?> 'T[]).[int index]
and set(index : bigint) (value : 'T) = (this :?> 'T[]).[int index] <- value
let untypedArray = myArray :> System.Array
let untypedArrayElement = box untypedArray.[0] :?> string
let untypedArrayElement2 = box untypedArray.[bigint 0] :?> string

How can I dynamically unbox a value in F#?

I am trying to achieve the automatic/dynamic cast in the fourth line below:
let a = 1 // type: int
let b = box a // type: obj
b.GetType() // System.Int32, so it is perfectly aware what it is!
let c = unbox b // fails....
The following would would work in the final line above BUT would require me to know and explicitly mark ahead-of-time the primitive/value type that I am working with (which I am trying to avoid):
let c1:int = unbox b
let c2 = b :?> int
While b knows at run-time what it is, the compiler doesn't, because it's an obj.
If you know, at compile time, what it is, you can unbox it like this:
let a = 1
let b = box a
b.GetType()
let c = unbox<int> b
c is now an int.
unbox only does anything if the type can be explicitly or implicitly determined at compile time. Here it will implicitly (and wrongly) try to convert the object to a string, as that's how it is used in the subsequent line.
let a = 1
let b = box a
b.GetType()
let c = unbox b
printf "%s" c
This of course gives a runtime error because it is not a string.
There's no way to have unbox convert to "what it actually is under the hood", as there's no definite way of determining this at compile time. There may be another way to do what you're trying to do though, if you can provide more details.
If you're wanting to, say create a generic unboxed list from boxed objects, you can do something like this:
let addToList (l: 'a list) (o: obj) = // type annotations optional
let o' = unbox o // unboxes to generic type 'a
o'::l
let l = [1;2;3]
let b = box 4
let l' = addToList l b // l' is list<int>, not list<obj>
let l2 = [1.;2.;3.]
let b2 = box 4.
let l2' = addToList l2 b2 // l2' is list<float>
// but as above you still have to be careful
let lcrash = addToList l b2 // crash

How do I iterate over a hashtable in F#?

let dic = Environment.GetEnvironmentVariables()
dic
|> Seq.filter( fun k -> k.Contains("COMNTOOLS"))
fails to compile.
I've tried using Array.filter, Seq.filter, List.filter
I've tried getting just the dic.Keys to iterate over but F# doesn't seem to want me to coerce a KeyCollection into an IEnumerable.
I've tried upcasting the hashtable into an IEnumerable<KeyValuePair<string,string>>
How do I walk the hashtable that is returned from Environment.GetEnvironmentVariables() ?
Since Environment.GetEnvironmentVariables() returns a non-generic IDictionary and it stores key/value pairs in DictionaryEntry, you have to use Seq.cast first:
let dic = Environment.GetEnvironmentVariables()
dic
|> Seq.cast<DictionaryEntry>
|> Seq.filter(fun entry -> entry.Key.ToString().Contains("COMNTOOLS"))
See the relevant docs at https://msdn.microsoft.com/en-us/library/system.collections.idictionary(v=vs.110).aspx. Notice that entry.Key is of type obj, so one has to convert to string before checking string containment.
Instead of using high-order functions, sequence expression might be handy:
let dic = Environment.GetEnvironmentVariables()
seq {
for entry in Seq.cast<DictionaryEntry> dic ->
(string entry.Key), (string entry.Value)
}
|> Seq.filter(fun (k, _) -> k.Contains("COMNTOOLS"))
F# Seq can only operate with System.Collections.Generic.IEnumerable<_>. System.IDictionary returned by Environment.GetEnvironmentVariables is not generic but it implements non-generic System.Collections.IEnumerable and not System.Collections.Generic.IEnumerable<_>. System.Collections.IEnumerable does not contain type information and allows enumeration of the collection of boxed types i.e. instances of System.Object.
Anyway System.IDictionary can be enumerated as collection of System.Collections.DictionaryEntry objects so you can simply call Seq.cast on it. It will give you access to Key and Value properties, yet still boxed as objects, so you should unbox them too.
let dic = System.Environment.GetEnvironmentVariables()
dic
|> Seq.cast<System.Collections.DictionaryEntry>
|> Seq.filter( fun k -> (k.Key :?> string).Contains("COMNTOOLS"))
Alternatively you can use the following function
let asStringPairSeq (d : System.Collections.IDictionary) : seq<string * string> =
Seq.cast<System.Collections.DictionaryEntry> d
|> Seq.map (fun kv -> kv.Key :?> string, kv.Value :?> string)
System.Environment.GetEnvironmentVariables()
|> asStringPairSeq
|> Seq.filter (fun (k,v) -> k.Contains("COMNTOOLS"))

Newtonsoft cannnot deserialize an empty F# sequence?

Using Newtonsoft.Json, latest version (=6.0.6) I get the following error:
Cannot create and populate list type Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers+EmptyEnumerable`1[System.String]
However in a post it was announced that Newtonsoft.Json would fully support Fsharp types?
When I change the offending type to a regular array, everything works fine.
The code:
type Prescription () =
member val Id = "" with get, set
member val Status = new PrescriptionStatus() with get, set
member val Prescriber = new Prescriber() with get, set
member val Indications = [||] : string[] with get, set
When I change Indications to be:
member val Indications = Seq.empty : string seq with get, set
I run into the error.
Also, when I initialise what is in fact an enumerable as an array, it cannot be constructed:
member val Indications : string seq = [||] |> Array.toSeq with get, set
I guess the answer is, Newtonsoft.Json doesn't fully support F# types.
But F# doesn't make supporting them particularly easy. For instance, an empty seq defined with Seq.empty is not just an IEnumerable<T>, it's a particular enumerable implementation EmptyEnumerable<T>, and this seems to throw off serialization - most likely because there's no appropriate constructor on it. From the post you linked to:
To all future creators of immutable .NET collections: If your collection of T has a constructor that takes IEnumerable then Json.NET will automatically work when deserializing to your collection, otherwise you're all out of luck.
If you initialize your seq like this instead, perhaps the behaviour will be different:
member val Indications = Seq.ofArray [||] : string seq with get, set
But that's splitting hairs, the actual answer here is simple - don't serialize seqs. Just use concrete, well-behaved types like arrays. The simpler the type, the less likely it is to give you headaches when doing serialization or interop.
Setting JsonSerializerSettings.ObjectCreationHandling = ObjectCreationHandling.Replace will fix this error.
I liked the notion of using simple types like arrays, only I wanted to use the same DTOs also for mapping to IQueryable in Linq queries. So, in that respect, arrays were not an option.
Luckily with some testing, it was simple:
#load ".\Scripts\load-project.fsx"
#time
open System
open System.Collections.Generic
open Newtonsoft.Json
[<CLIMutable>]
type Test1 =
{
Strings : string seq
}
type Test2 () =
member val Strings = Seq.empty : IEnumerable<string> with get, set
type Test3 () =
member val Strings = Seq.empty : String seq with get, set
type Test4 () =
member val Strings : IEnumerable<string> = Seq.empty : IEnumerable<string> with get, set
type Test5 () =
member val Strings : IEnumerable<string> = [] |> List.toSeq : IEnumerable<string> with get, set
type Test6 () =
member val Strings = [] |> List.toSeq : string seq with get, set
let test1 = { Strings = Seq.empty }
let test2 = new Test2 ()
let test3 = new Test3 ()
let test4 = new Test4 ()
let test5 = new Test5 ()
let test6 = new Test6 ()
let json1 = JsonConvert.SerializeObject(test1)
let json2 = JsonConvert.SerializeObject(test2)
let json3 = JsonConvert.SerializeObject(test3)
let json4 = JsonConvert.SerializeObject(test4)
let json5 = JsonConvert.SerializeObject(test5)
let json6 = JsonConvert.SerializeObject(test6)
let deserialized1 = JsonConvert.DeserializeObject<Test1>(json1) // Fails
let deserialized2 = JsonConvert.DeserializeObject<Test2>(json2) // Fails
let deserialized3 = JsonConvert.DeserializeObject<Test3>(json3) // Fails
let deserialized4 = JsonConvert.DeserializeObject<Test4>(json4) // Fails
let deserialized5 = JsonConvert.DeserializeObject<Test5>(json5) // Passes
let deserialized6 = JsonConvert.DeserializeObject<Test5>(json6) // Passes
So, as long as you construct your sequence using a type that has a recognisable constructor, for example a list, the object can be deserialised. Strangely enough, initializing the sequence as an array and then converting it to a sequence, like with the list to sequence example (which passes), fails.

Casting an object to a generic type without supplying the parameters

Say I have a code like this
let a = new List<int>()
let b = a :> obj :?> List<obj>
It throws an exception saying that it can't do it since one is List<int> while I'm trying to make it an List<obj>.
I understand why that's a problem. It can't just magically create an interface for me that replaces all int types with obj, but what CAN I do here?
I have an object and I know that's it's a List of something. How can I access the elements and just not care about their type?
My concrete example doesn't use Lists so I require a general, not a List specific, solution.
In case of lists you can use System.Collections.IList to access elements
open System.Collections
open System.Collections.Generic
let x = List<int>()
let y: IList = downcast (x :> obj)
This approach can also be generalized: make your generic classes implement non-generic interface:
type IT =
abstract Value: obj
type T<'a>(a: 'a) =
member val Value = a;
interface IT with
member this.Value = upcast this.Value
If this is not an option (i.e. because you cannot make changes in classes) you can always resort to reflection
type T<'a>(a: 'a) =
member val Value = a;
type Action =
static member Do(a: T<_>) = printfn "%A" a.Value
let v = T(10)
let mi = typeof<Action>.GetMethod("Do").MakeGenericMethod(v.GetType().GetGenericArguments().[0])
mi.Invoke(null, [|v|])

Resources