Your ITransform definition ITransform no better than a function. You could use the straightforward signature function Item<'a,'b> -> Item<'x,'y> , it would work the same way.
The reason for using the interface is that every time you call a method, you can have different common parameters. But this, in turn, means that general parameters cannot be fixed on the interface itself. They must be in the method:
type ITransform = abstract Apply<'a, 'b, 'x, 'y> : Item<'a,'b> -> Item<'x,'y>
Or you can simply refuse them at all, the compiler will lift them from the signature:
type ITransform = abstract Apply : Item<'a,'b> -> Item<'x,'y>
Now tmap compiles fine: even if the interface itself is not shared, its Apply method exists, and therefore it can have different common arguments for each call.
However, now you have another problem: implementing such an interface in mkStringify not so simple. Now that Apply is completely general, its implementation cannot return certain types, such as string . You cannot have your own cake and eat it too: the interface is a “promise” for the consumer and a “demand” for the performer, therefore, if your consumer expects that he can do “something,” then the developer must oblige and implement “everything”.
To fix this, take a step back and think about your problem: what exactly do you want to achieve? What do you want to convert to what? So far it seems to me that you are trying to force the first argument of all Item to string , keeping the second argument intact. If this is the goal, then the definition of ITransform obvious:
type ITransform = abstract Apply : Item<'a,'b> -> Item<string,'b>
This reflects the idea: the first argument of the incoming Item can be any, and it is converted to string , and the second argument can be any, and it remains untouched.
tmap and mkStringify will compile with this definition.
If this is not your goal, please describe it and we can find another solution. But keep in mind the note associated with the pie: if you want tmap work for any type, then ITransform executors ITransform also support any type.
Update
From the discussion in the comments, it became obvious that the description of the real problem is as follows: the conversion function should convert the first argument of Item to something else and leave the second argument intact. And "something else" will be the same for both Items .
In this case, the implementation becomes clear: the interface itself must fix the “something else” part for output, and the method must accept any types as input:
type ITransform<'target> = abstract Apply : Item<'a, 'b> -> Item<'target, 'b>
Using this definition, all three tmap , mkStringify and mkDuplicate functions will be compiled. We found a common language: enough promise to the user of the interface and not too demanding on the implementation of the interface.
Having said that, I think that you really do not need the interface here, it is redundant. The reason you cannot use a function is because the function will lose its universality when passed by value and therefore will not be applicable to various types of arguments. This, however, can be defeated by passing the function twice. In both cases, he will lose his timidity, but he will lose it in different ways, i.e. Each time will be created with different arguments. Yes, it is inconvenient to pass the same function twice, but it is still less syntactic than the interface:
let inline tmap f1 f2 ({y = yi; z = zi}) = { y = f1 yi z = f2 zi } let stringify x = let f (Item(a,b)) = Item (sprintf "%A" a, b) tmap ffx stringify a