F# Adding value to map result in KeyNotFoundException - f#

type bytesLookup = Map<byte,int list>
type lookupList = bytesLookup list
let maps:bytesLookup = Map.empty
let printArg arg = printfn(Printf.TextWriterFormat<unit>(arg))
let array1 = [|byte(0x02);byte(0xB1);byte(0xA3);byte(0x02);byte(0x18);byte(0x2F)|]
let InitializeNew(maps:bytesLookup,element,index) =
maps.Add(element,List.empty<int>)(*KeyNotFoundException*)
maps.[element]
let MapArray (arr:byte[],maps:bytesLookup ) =
for i in 0..arr.Length do
match maps.TryFind(arr.[i]) with
| Some(e) -> i::e
| None -> InitializeNew(maps,arr.[i],i)
MapArray(array1,maps);
printArg( maps.Count.ToString())
Exception
System.Collections.Generic.KeyNotFoundException: The given key was not
present in the dictionary. at
Microsoft.FSharp.Collections.MapTreeModule.find[TValue,a](IComparer1
comparer, TValue k, MapTree2 m) at
Microsoft.FSharp.Collections.FSharpMap2.get_Item(TKey key) at
FSI_0012.MapArray(Byte[] arr, FSharpMap2 maps) in Script1.fsx:line 16
at .$FSI_0012.main#() in Script1.fsx:line 20
In the function I'm trying to initialize a new element in the map with a list of int. I also try to push a new int value into the list at the same time.
What am I doing wrong?

F# Map is an immutable data structure, the Add method doesn't modify the existing data structure, it returns a new Map with the additions you've requested.
Observe:
let ex1 =
let maps = Map.empty<byte, int list>
maps.Add(1uy, [1]) // compiler warning here!
maps.[1uy]
Two things about this code:
It throws System.Collections.Generic.KeyNotFoundException when you run it
It gives you a compiler warning that the line maps.Add... should have type unit but actually has type Map<byte,int list>. Don't ignore the warning!
Now try this:
let ex2 =
let maps = Map.empty<byte, int list>
let maps2 = maps.Add(1uy, [1])
maps2.[1uy]
No warning. No exception. Code works as expected, returning the value [1].

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

Generic function to read from a `.json` file

So, I can easily write an arbitrary type to JSON with Newtonsoft.Json:
type X = {
Number: decimal
Sequence: decimal
NumList: decimal list
}
let createItem (n, s, nL) =
{Number = n;
Sequence = s;
NumList = nL}
let items =
[
(1M, 1M, [1M; 2M; 3M])
(2M, 2M, [2M; 4M; 6M])
(3M, 3M, [3M; 6M; 9M])
]
|> List.map createItem
open Newtonsoft.Json
open System.IO
let writeToJson (path: string) (obj: 'a) : unit =
let serialized = JsonConvert.SerializeObject(obj)
File.WriteAllText(path, serialized)
writeToJson "xList.json" items
How can I write a function generic enough that I can read a JSON file? In other words, I'd like something like:
let readFromJson (path: string) (t: 'T) =
let convertToQr = File.ReadAllText(path)
Newtonsoft.Json.JsonConvert.DeserializeObject<t list>(convertToQr)
where the second argument is the Type of the object in path, but I don't know how to do that. If I try to use this function as is, I get a compiler error.
How can I declare in the second argument above the type of the thing that is in path? Can I?
Generic parameters, when explicitly defined, are written in angle brackets immediately after function name, before regular parameters:
let readFromJson<'T>(path: string) =
let convertToQr = File.ReadAllText(path)
Newtonsoft.Json.JsonConvert.DeserializeObject<'T list>(convertToQr)
Usage:
readFromJson<string> "/some/file.json"
Alternatively, you can specify the return type of your function, and let the compiler infer all generic parameters and arguments for you:
let readFromJson(path: string) : 't list =
let convertToQr = File.ReadAllText(path)
Newtonsoft.Json.JsonConvert.DeserializeObject(convertToQr)
Here, the compiler knows that the generic argument of DeserializeObject must be 't list, because its result is being returned from readFromJson, and the result type of readFromJson is explicitly declared to be 't list. Similarly, just by noticing a generic type in the function definition, the compiler will infer that the function has one generic parameter.
In a similar way, you can let the compiler infer the required type when you call the function:
// call inferred to readFromJson<string>, because that's the required return type
let s: string list = readFromJson "/some/file.json"

Extracting the values from an IGroupedObservable

Reactive.Linq's GroupBy leaves you with an IObservable<IGroupedObservable<'TKey, 'TValue>>. How do you get the values from the IGroupedObservable? The key is accessible by x.Key, so I suppose the values could be projected by some transformation of sorts.
This is roughly what I want to do:
open System.Reactive.Linq
let doStuffWithEvenNumbers i = i*10
let doStuffWithOddNumbers i = i*3
let numbers = Observable.Range(0, 10)
let groups = numbers.GroupBy(fun i -> i % 2 = 0)
let subscription1 = groups.Where(fun i -> i.Key).Subscribe(doStuffWithEvenNumbers)
let subscription2 = groups.Where(fun i -> not i.Key).Subscribe(doStuffWithOddNumbers)
Of course, the two let subscriptionX = lines won't compile, since I need to get from IGroupedObservable<bool, int> to int.
IGroupedObservable<'TKey, 'TValue> extends IObservable<'TValue>, that's how you get to the values. In your case you can do that in many ways:
// you can use SelectMany to 'flatten' the observable
groups.Where(fun i -> i.Key).SelectMany(fun o -> o :> IObservable<int>).Subscribe(doStuffWithEvenNumbers)
Note that Subscribe call take an Action, whereas in your case you defined the method as a Func. You need to remove its returned value for the call to work.

How to get cell data from Excel in F# (with appropriate typing)

I am trying to read some data from excel. The value is being stored as an obj type, but when I try to cast it to an int or double type I get a type error.
My code:
open System
open System.Data
#r "Microsoft.Office.Interop.Excel"
open Microsoft.Office.Interop
let xl = new Excel.ApplicationClass()
let wb = xl.Workbooks.Open(#"Y:\Test.xlsx")
let Sheet1 = wb.Worksheets.["Sheet1"] :?> Excel.Worksheet
let tr= Sheet1 .Cells.[1,3]:?> int
Error:
System.InvalidCastException: Specified cast is not valid.
at <StartupCode$FSI_0018>.$FSI_0018.main#() in Y:\Script2.fsx:line 20
Stopped due to error
Try casting your cell selection to a Range first, then do the value type casting after you've got the cell value:
let xl = new Excel.ApplicationClass()
let wb = xl.Workbooks.Open(#"Y:\Test.xlsx")
let sheet1 = wb.Worksheets.["Sheet1"] :?> Excel.Worksheet
let cell = sheet1.Cells.[1,3] :?> Excel.Range
let value = int (string cell.Value2)
You can take more care with the final casting step if you are expecting varying data.

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.

Resources