'F# TypeShape - This code is not sufficiently generic

I'm trying to learn the TypeShape lib and playing with the IShapeMember per the snippet in the readme

type IShapeMember<'DeclaringType, 'Field> = abstract Get : 'DeclaringType -> 'Field abstract Set : 'DeclaringType -> 'Field -> 'DeclaringType

Here is my code in an effort to generalise a 'setter' function

let setMemberValue (value: 'b) (target: 'c) (shp: IShapeMember<'c>) =
    shp.Accept { new IMemberVisitor<'c,'c> with
        member x.Visit (m: ShapeMember<'c,'b>) = m.Set target value}

But this gives me the compile error: This code is not sufficiently generic. The type variable 'a could not be generalized because it would escape its scope.

(btw, that's not a type on the error, it indeed complains about variable 'a....)

I have tried all manner of Type wrangling but cannot get my head around what I'm doing wrong.

f#


Solution 1:[1]

I'm not a TypeShape expert, but I think the problem here is that you're not honoring the signature of the Visit function, which is:

type IMemberVisitor<'TRecord, 'R> =
    abstract Visit<'Field> : ShapeMember<'TRecord, 'Field> -> 'R

Note that the 'Field type parameter is determined by whoever calls Visit, not by you. Thus, your attempt to use the external value in the call to Set isn't sufficiently generic. Instead, you need a way to obtain an instance of 'Field from within the Visit function, so it works with whatever 'Field type the caller desires. This is easier said than done, but if you want a trivial example to start with, this will compile at least:

let visitor target =
    {
        new IMemberVisitor<'TRecord, 'R> with
            member x.Visit (m: ShapeMember<'TRecord,'Field>) =
                let value = Unchecked.defaultof<'Field>
                let target' = m.Set target value
                Unchecked.defaultof<'R>
    }

There's also an example of calling Set from within Visit here, but it is more complex. I don’t think there is any way of doing it that is both simple and useful.

Solution 2:[2]

let setMemberValue (value: 'b) (target: 'c) (shp: IShapeMember<'c>) =
    shp.Accept { new IMemberVisitor<'c,'c> with
        member x.Visit (m: ShapeMember<'c,'d>) = 
            if typeof<'b> = typeof<'a> then //or convert 'b -> 'd function
                m.Set target (box value :?> 'd)
            else 
                failwith "The supplied type does not match the type of the target field"
                }

Boxing and casting the supplied value to the type 'd did the trick. All wrapped in some form of type check in case the caller supplied value does not match that of the field being set

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 OrdinaryOrange