F# byref param not mutable - f#

I need to assign to a byref parameter, but, using F# 4.0 and .NET 4.5.2 on a Windows 8x64 box, I keep getting complaints that This value is not mutable. I can't change the signature, because I'm implementing a COM interface. Minimal broken example:
module Utils =
let bugFix1([<System.Runtime.InteropServices.Out>] ID : System.String byref) = ID <- "Hi!"
let bugFix1([<System.Runtime.InteropServices.Out>] ID : int byref) = ID <- 0
let bugFix1([<System.Runtime.InteropServices.Out>] ID : System.Guid byref) = ID <- System.Guid.NewGuid()
By this value, it definitely means ID, because it doesn't matter what I assign to ID. Note too, that it doesn't matter whether the type is blittable or not, or whether it is heap- or stack-allocated.
Is there some way do declare ID as mutable?

I think you have discovered another bug (or undocumented feature?). This happens simply because your parameter name is capitalized. Surprise! :-)
These variants will work (omitted [<Out>] for brevity):
let bugFix1(id : string byref) = id <- "Hi!"
let bugFix1(iD : string byref) = iD <- "Hi!"
But these will fail:
let bugFix1(Id : string byref) = Id <- "Hi!"
let bugFix1(ID : string byref) = ID <- "Hi!"
I have absolutely no idea why capitalization would matter. I would guess that this never arose before, because parameters always start with a lower-case letter by convention.
I intend to google some more and then file an issue.

Related

What does the warning "This construct causes code to be less generic than indicated by the type annotations" mean in F#

I have code generating this warning.
There are a few SO related posts, the closest is this one: This construct causes code to be less generic than indicated by the type annotations
but I don't see where it applies because my code is already in a function
so, the code in question is very simple:
let exists (key: 'a) =
r.Exists(string key)
let set (key: 'a) value =
r.Set((string key), value)
let get (key: 'a) =
r.Get(string key)
let setDefault (key: 'a) value =
if not (exists key) then
set key value
what I am trying to achieve is allow passing different enums, strings or even ints as a key, and, whatever the type passed, it would be converted to string (within reason obviously)
but when I use that code with an enum, I get the warning in the title.
So, I have two questions:
I don't really understand the warning. Can someone explain it to me?
How can I achieve passing various enums, or string and make a string key out of them?
You can reproduce this warning with an extremely simple test case, clearing up some of the noise from your example:
let f (x: 'a) =
string x
Looking at this, you may be confused, because the type of the string function is 'T -> string, but it's not that simple. To understand what's happening, you have to look at the implementation of the string function in FSharp.Core:
let inline anyToString nullStr x =
match box x with
| null -> nullStr
| :? System.IFormattable as f -> f.ToString(null,System.Globalization.CultureInfo.InvariantCulture)
| obj -> obj.ToString()
[<CompiledName("ToString")>]
let inline string (value: ^T) =
anyToString "" value
// since we have static optimization conditionals for ints below, we need to special-case Enums.
// This way we'll print their symbolic value, as opposed to their integral one (Eg., "A", rather than "1")
when ^T struct = anyToString "" value
when ^T : float = (# "" value : float #).ToString("g",CultureInfo.InvariantCulture)
when ^T : float32 = (# "" value : float32 #).ToString("g",CultureInfo.InvariantCulture)
when ^T : int64 = (# "" value : int64 #).ToString("g",CultureInfo.InvariantCulture)
when ^T : int32 = (# "" value : int32 #).ToString("g",CultureInfo.InvariantCulture)
when ^T : int16 = (# "" value : int16 #).ToString("g",CultureInfo.InvariantCulture)
when ^T : nativeint = (# "" value : nativeint #).ToString()
when ^T : sbyte = (# "" value : sbyte #).ToString("g",CultureInfo.InvariantCulture)
when ^T : uint64 = (# "" value : uint64 #).ToString("g",CultureInfo.InvariantCulture)
when ^T : uint32 = (# "" value : uint32 #).ToString("g",CultureInfo.InvariantCulture)
when ^T : int16 = (# "" value : int16 #).ToString("g",CultureInfo.InvariantCulture)
when ^T : unativeint = (# "" value : unativeint #).ToString()
when ^T : byte = (# "" value : byte #).ToString("g",CultureInfo.InvariantCulture)
This is using statically-resolved type parameters and using an explicit implementation for each of the listed types, so it is not really as generic as it seems from the type signature. In your case, what's happening is that it's inferring the most compatible type, and because your function is just typed as 'a, it's picking obj. So, because you're calling string and your input parameter is being forced into one of the types that the string function actually handles (which is actually in anyToString), and that's obj.
To make it work in your real-world scenario is actually pretty simple: Just make your functions inline and don't put a type on the parameter at all:
let inline exists key =
r.Exists(string key)
This will infer the type for the parameter and call the right version of string, and that will work with pretty much anything you want to pass it, including your enums.
The issue is from using string. If you look at the source for string, you'll see that it's inline, which means the compiler needs to determine the type at compile time. In your case, that means the generic type has to be resolved before string can be called, which in turn forces the compiler to pick something that will work - in this case, obj.
There are two simple ways to work around this - first, you can make your functions inline, which allows the compiler to defer this until the function using your string gets used. This will work in many cases, but can also potentially "push this off" to the consumer of your API later if they do the same thing you're doing. This would look like:
let inline set (key: 'a) value =
r.Set((string key), value)
The other option is to avoid the string operator, and use the fact that all objects in .NET include ToString, and call that instead:
let set (key: 'a) value =
r.Set(key.ToString()), value)
Either approach will avoid the warnings and keep the functions generic.

Compiler unable to resolve type from shared 'header' file

In Common.fs:
namespace Bug
module Common =
type SourceEntity = {
id : int
link : string
}
type ReleaseEntity = {
id : int
notes : string
}
In Release.fs
namespace Bug
open System
open Common
module Release =
let cache = new Collections.Generic.Dictionary<int, ReleaseEntity>()
let AddToCache(entity) =
cache.Add(entity.id, entity)
()
let AddRec() =
let entity : ReleaseEntity = {
id = 1
notes = "Notes"
}
AddToCache(entity)
In Source.fs
namespace Bug
open System
open Common
module Source =
let Cache = new Collections.Generic.Dictionary<int, SourceEntity>()
let AddToCache(entity) =
Cache.Add(entity.id, entity) <<=== E R R O R
()
let AddRec() =
let ent : SourceEntity = {
id = 1
releases = "Releases"
}
AddToCache(ent) <<=== E R R O R
Files included in above order in the Visual Studio project.
Error reported in Source.fs:
Error FS0001 This expression was expected to have type
'SourceEntity'
but here has type
'ReleaseEntity'
If the order of the two types in Common.fs are reversed, the error is reported in Release.fs where expected type is ReleaseEntity but has type SourceEntity.
Any ideas why this error is happening?
It's a clash (and shadowing) of record field names.
When you write entity.id in the body of Bug.Source.AddToCache, the compiler uses the fact that you're accessing the .id field to infer the type of entity. Which records have a field named id? Well, those two records do, but the compiler has to pick one. How? Easy: the last one takes precedence. This is called "shadowing".
In order to disambiguate the choice, just add a type annotation:
let AddToCache(entity) =
Cache.Add(entity.id, entity)
()
Wait, but why doesn't the compiler use the type of Cache.Add to infer the type of entity?
Well, this is just a limitation (or a feature?) of F#. The compilation is single-pass, type interference proceeds top down, left to right, without doublebacks. This allows the compiler to be very fast and very predictable (looking at you, Haskell).
But in this case it means that by the time the compiler sees that entity is used as parameter in Cache.Add, it has already decided what its type must be.
When you get a type error, try to think about how the compiler came to infer that particular type.
Here:
let AddToCache(entity) =
cache.Add(entity.id, entity)
()
Could the compiler know which type has would have an id field in entity?
If you had typed in
let entity = { id = 1; link = "" }
the compiler would infer that this is SourceEntity because only SourceEntity has those particular record fields. In cache.Add(entity.id, entity), the compiler has no other constraints to go by, other than it has to have an id field, so it picks the last matching type - and that is why you get the error.
If you refactor the common id field to
namespace Bug
module Common =
type SourceEntity = {
source_id : int
link : string
}
type ReleaseEntity = {
release_id : int
notes : string
}
you will find that the error disappears.
Solutions
All of the solutions involve constraining it to a known type.
The simplest is to add a type annotation:
let AddToCache(entity: SourceEntity) =
Another is to deconstruct it explicitly:
let { SourceEntity.id = id } = entity
Cache.Add(id, entity)
Another is to coerce the type - this isn't relevant here, but it may come to be useful down the road:
Cache.Add((entity :> SourceEntity).id, entity)
I'd recommend this article from F# for fun and profit on type inference for a nice explanation of the process.
P.S.
You actually only needed that one type annotation.
The rest can be inferred :)
module Source =
let Cache = new Collections.Generic.Dictionary<_, _>()
let AddToCache (entity: SourceEntity) =
Cache.Add(entity.id, entity)
()
let AddRec () =
let ent = {
id = 1
link = ""
}
AddToCache(ent)

How to receive a type that extends an interface without losing the original type

I have just started using F# and my brain is broken trying to figure out how to work with its types without having to resort to an OO type of programming.
Here is my situation I basically want to create a method where I provide the type and the Id and it returns to me the object on the database.
So basically this is what I get so far.
let client = MongoClient()
let database = client.GetDatabase("testdb")
let lowerCase (str : string) =
str.ToLower()
let nameOf (classType: Type) =
classType.Name
let nameTypeOf<'a> =
nameOf typeof<'a>
let getCollection<'a> =
let collectionName = nameTypeOf<'a> |> lowerCase
database.GetCollection<'a> collectionName
let dbSelect<'a> id =
let collection = getCollection<'a>
collection.Find(fun(x) -> x.Id = id).First()
So my problem is with the dbSelect, obviously it does not compile since x is generic, basically I wanted to create an interface with the Id and all my objects interface with it.
I do know how to do it using classes and inheritances, but I am avoiding having to use instanced classes outside interop with c# libraries. What would be the best functional way to do it, if there is any.
This is what I was eexpecting to call it with
type IDbObject =
abstract Id: string
type Item =
{
Id: string
Name: string
}
interface IDbObject with
member x.Id = x.Id
let item =
selectDb<Item> "5993592a35ce962b80da1e22"
Any help would be appreciated.
And if anyone want to point out how crappy my code is, any feedback is really appreciated
I don't think the solution here is much different from what you'd have in C#. You can constrain the generic type to use the interface members, getting something roughly like this:
let getCollection<'a when 'a :> IDbObject> () =
let collectionName = nameTypeOf<'a> |> lowerCase
database.GetCollection<'a> collectionName
let dbSelect<'a when 'a :> IDbObject> id =
let collection = getCollection<'a>()
collection.Find(fun (x : 'a) -> x.Id = id).First()
The type of dbSelect should be inferred to be string -> #IDbObject, and be coerced to string -> 'a at the call site.

Why am I not allowed to use reference cell as argument for byref parameter in let functions?

This doesn't work:
let increment(i: int byref) = i <- i + 1
let xxx = ref 0
increment(xxx) // this expression was expected to have type
// byref<int> but here has type int ref
But this works:
let incrementParam(a: int byref) = a <- a + 1
let mutable b = 30
incrementParam(&b)
as well as this:
type Incrementor =
static member Increment(i : int byref) =
i <- i + 1
let fff = ref 10
Incrementor.Increment(fff)
Because the spec says so. See Type Directed Conversions at Member Invocations (emphasis mine), especially the following:
Note: These type-directed conversions are primarily for interoperability with existing member-based .NET libraries and do not apply at invocations of functions defined in modules or bound locally in expressions.
To add some details to the reference that Daniel pointed out, the problem is that the type 'T ref is not the same as the type 'T byref and so the compiler needs to insert some conversion (to take the address).
I think this is only supported for members, because this is the main scenario (calling COM interop methods etc.) and because implicit conversions generally compilcate type inference. The type-directed conversions are an easier case where the compiler already knows the required type ('T byref). I suppose that, if this was allowed on functions, the compiler might infer that the type of argument should actually be 'T ref.
If you want to get the first sample to work, you have to explicitly construct 'T byref by taking the address of the contents field:
let increment(i: int byref) = i <- i + 1
let xxx = ref 0
increment(&xxx.contents)

Using NoRM to access MongoDB from F#

Testing out NoRM https://github.com/atheken/NoRM from F# and trying to find a nice way to use it. Here is the basic C#:
class products
{
public ObjectId _id { get; set; }
public string name { get; set; }
}
using (var c = Mongo.Create("mongodb://127.0.0.1:27017/test"))
{
var col = c.GetCollection<products>();
var res = col.Find();
Console.WriteLine(res.Count().ToString());
}
This works OK but here is how I access it from F#:
type products() =
inherit System.Object()
let mutable id = new ObjectId()
let mutable _name = ""
member x._id with get() = id and set(v) = id <- v
member x.name with get() = _name and set(v) = _name <- v
Is there an easier way to create a class or type to pass to a generic method?
Here is how it is called:
use db = Mongo.Create("mongodb://127.0.0.1:27017/test")
let col = db.GetCollection<products>()
let count = col.Find() |> Seq.length
printfn "%d" count
Have you tried a record type?
type products = {
mutable _id : ObjectId
mutable name : string
}
I don't know if it works, but records are often good when you just need a class that is basically 'a set of fields'.
Just out of curiosity, you can try adding a parameter-less constructor to a record. This is definitely a hack - in fact, it is using a bug in the F# compiler - but it may work:
type Products =
{ mutable _id : ObjectId
mutable name : string }
// Horrible hack: Add member that looks like constructor
member x.``.ctor``() = ()
The member declaration adds a member with a special .NET name that is used for constructors, so .NET thinks it is a constructor. I'd be very careful about using this, but it may work in your scenario, because the member appears as a constructor via Reflection.
If this is the only way to get succinct type declaration that works with libraries like MongoDB, then it will hopefuly motivate the F# team to solve the problem in the future version of the language (e.g. I could easily imagine some special attribute that would force F# compiler to add parameterless constructor).
Here is a pretty light way to define a class close to your C# definition: it has a default constructor but uses public fields instead of getters and setters which might be a problem (I don't know).
type products =
val mutable _id: ObjectId
val mutable name: string
new() = {_id = ObjectId() ; name = ""}
or, if you can use default values for your fields (in this case, all null):
type products() =
[<DefaultValue>] val mutable _id: ObjectId
[<DefaultValue>] val mutable name: string

Resources