Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Behavior of F# "unmanaged" type constraint

Tags:

F# supports a type constraint for "unmanaged". This is not the same as a value type constraint like "struct" constraints. MSDN notes that the behavior of the unmanaged constraint is:

The provided type must be an unmanaged type. Unmanaged types are either certain primitive types (sbyte, byte, char, nativeint, unativeint, float32, float, int16, uint16, int32, uint32, int64, uint64, or decimal), enumeration types, nativeptr<_>, or a non-generic structure whose fields are all unmanaged types.

This is a very handy constraint type when doing platform invocation, and more than once I wish C# had a way of doing this. C# does not have this constraint. C# does not support all constraints that can be specified in CIL. An example of this is an enumeration. In C#, you cannot do this:

public void Foo<T>(T bar) where T:enum

However, the C# compiler does honor the "enum" constraint if it comes across it in another library. Jon Skeet is able to use this to create his Unconstrained Melody project.

So, my question is, is F#'s "unmanaged" constraint something that can be represented in CIL, like an enum constraint and just not exposed in C#, or is it enforced purely by the F# compiler like some of the other constraints F# supports (like Explicit Member Constraint)?

like image 823
vcsjones Avatar asked Dec 29 '14 18:12

vcsjones


People also ask

How do you find F end behavior?

To determine its end behavior, look at the leading term of the polynomial function. Because the power of the leading term is the highest, that term will grow significantly faster than the other terms as x gets very large or very small, so its behavior will dominate the graph.

What is a behavior of function?

The end behavior of a function f describes the behavior of the graph of the function at the "ends" of the x-axis. In other words, the end behavior of a function describes the trend of the graph if we look to the right end of the x-axis (as x approaches +∞ ) and to the left end of the x-axis (as x approaches −∞ ).

What is the end behavior of the graph of the polynomial function?

The end behavior of a polynomial function is the behavior of the graph of f(x) as x approaches positive infinity or negative infinity. The degree and the leading coefficient of a polynomial function determine the end behavior of the graph.


2 Answers

I've got some feedback, beware that I don't know F# nearly well enough. Please edit where I goof. Getting to the basics first, the runtime does not actually implement the constraints that F# supports. And supports more than what C# supports. It has just 4 types of constraints:

  • must be a reference type (class constraint in C#, not struct in F#)
  • must be a value type (struct constraint in C# and F#)
  • must have a default constructor (new() constraint in C#, new in F#)
  • constrained by type.

And the CLI specification then sets specific rules on how these constraints can be valid on a specific type parameter type, broken down by ValueType, Enum, Delegate, Array and any other arbitrary type.

Language designers are free to innovate in their language, as long as they abide by what the runtime can support. They can add arbitrary constraints by themselves, they have a compiler to enforce them. Or arbitrarily choose to not support one that the runtime supports because it doesn't fit their language design.

The F# extensions work fine as long as the generic type is only ever used in F# code. So the F# compiler can enforce it. But it cannot be verified by the runtime and it will not have any effect at all if such a type is consumed by another language. The constraint is encoded in the metadata with F# specific attributes (Core.CompilationMapping attribute), another language compiler knows beans what they are supposed to mean. Readily visible when you use the unmanaged constraint you like in an F# library:

namespace FSharpLibrary

type FSharpType<'T when 'T : unmanaged>() =
    class end

Hope I got that right. And used in a C# project:

class Program {
    static void Main(string[] args) {
        var obj = new Example();   // fine
    }
}
class Foo { }
class Example : FSharpLibrary.FSharpType<Foo> { }

Compiles and executes just fine, the constraint is not actually applied at all. It can't be, the runtime doesn't support it.

like image 122
3 revs Avatar answered Oct 07 '22 02:10

3 revs


So, opening a small sample in ILDasm, we see the following F# code

open System.Collections

type Class1<'T when 'T : unmanaged> =
   class end

type Class2<'T> =
    class end

type Class3<'T when 'T :> IEnumerable> =
    class end

becomes the following IL

.class public auto ansi serializable beforefieldinit FSharpLibrary.Class1`1<T>
       extends [mscorlib]System.Object
{
  .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 ) 
} // end of class FSharpLibrary.Class1`1

.class public auto ansi serializable beforefieldinit FSharpLibrary.Class2`1<T>
       extends [mscorlib]System.Object
{
  .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 ) 
} // end of class FSharpLibrary.Class2`1

.class public auto ansi serializable beforefieldinit FSharpLibrary.Class3`1<([mscorlib]System.Collections.IEnumerable) T>
       extends [mscorlib]System.Object
{
  .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 ) 
} // end of class FSharpLibrary.Class3`1

Notably, Class2 has an unconstrained generic parameter, and perfectly matches Class1 even though T is constrained to unmanaged in Class1. By contrast, Class3 does not match this given pattern, and we can clearly see the explicit :> IEnumerable constraint in IL.

In addition, the following C# code

public class Class2<T>
{ }

public class Class3<T>
    where T : IEnumerable
{ }

Becomes

.class public auto ansi beforefieldinit CSharpLibrary.Class2`1<T>
       extends [mscorlib]System.Object
{
} // end of class CSharpLibrary.Class2`1

.class public auto ansi beforefieldinit CSharpLibrary.Class3`1<([mscorlib]System.Collections.IEnumerable) T>
       extends [mscorlib]System.Object
{
} // end of class CSharpLibrary.Class3`1

Which, with the exception of the F#-generated constructors (.ctors) and Serializable flags, matches the F# generated code.

With no other references to Class1 Thus means that the compiler is not, at the IL level, taking into account the unmanaged constraint, and leave no futher references to its presence in the compiled output.

like image 42
David Avatar answered Oct 07 '22 02:10

David