F# type provider - “only return generated types”

2020-02-25 08:28发布

Trying to encode type-level peano numbers using a type provider:

namespace TypeProviderPlayground

open System
open Microsoft.FSharp.Core.CompilerServices
open System.Runtime.CompilerServices

[<assembly: TypeProviderAssembly()>]
do()

type Z = class end
type 'a S = class end
type N = class end

[<TypeProvider>]
type PeanoProvider(s: TypeProviderConfig) =
    let invalidate = Event<_,_>()
    interface ITypeProvider with
        member x.ApplyStaticArguments(typeWithoutArguments, typeNameWithArguments, staticArguments) =
            let n : int = unbox staticArguments.[0]
            [1..n] |> List.fold (fun s _ -> typedefof<S<_>>.MakeGenericType [| s |]) typeof<Z>
        member x.GetNamespaces() = 
            let ns = 
                { new IProvidedNamespace with
                    member x.GetNestedNamespaces() = [||]
                    member x.GetTypes() = [||]
                    member x.ResolveTypeName t =
                        if t = "N"
                            then typeof<N>
                            else null
                    member x.NamespaceName = "Peano" }
            [| ns |]
        member x.GetStaticParameters t =
            let p = 
                { new Reflection.ParameterInfo() with
                    member z.Name = "number"
                    member z.ParameterType = typeof<int> }
            [| p |]

        [<CLIEvent>]
        member x.Invalidate = invalidate.Publish
        member x.Dispose() = ()
        member x.GetInvokerExpression(syntheticMethodBase, parameters) = 
            raise <| NotImplementedException()

The N type is just a dummy, otherwise I couldn't get it to go through the type provider. Consumer code:

open TypeProviderPlayground

[<Generate>]
type S<'a> = Peano.N<5>

And I get this error:

error FS3152: The provider 'TypeProviderPlayground.PeanoProvider' returned a non-generated type
'TypeProviderPlayground.S`1[[TypeProviderPlayground.S`1[[TypeProviderPlayground.S`1[[TypeProviderPlayground.S`1[[TypeProviderPlayground.S`1[[TypeProviderPlayground.Z, TypeProviderPlayground, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]], TypeProviderPlayground, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]], TypeProviderPlayground, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]], TypeProviderPlayground, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]], TypeProviderPlayground, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]' 
in the context of a [<Generate>] declaration. Either remove the [<Generate>] declaration or adjust the type provider to only return generated types. 

Which says that the type was correctly constructed (Z S S S S S) but for some reason the compiler won't accept it as a "generated type".

If I remove the [<Generated>] attribute I get some other error telling me to add it.

Does this mean that type providers will only work on dynamically emitted types (which seems a weird requirement at first blush)?

Also, if I do:

[<Generate>]
type WW<'a> = Peano.N<5>

I get an error saying that WW'1 was expected but S'1 was returned. Why does the returned type (by the type provider) have to match the type name I declare in the consumer?

2条回答
Juvenile、少年°
2楼-- · 2020-02-25 09:00

There are a few important things to realize about type providers. First of all, there are two kinds of provided types:

  1. Generated types are real .NET types that get embedded into the assembly that uses the type provider (this is what the type providers that wrap code generation tools like sqlmetal use)
  2. Erased types are simulated types which are represented by some other type when the code is compiled.

Just as a heads-up, the mechanisms for controlling this distinction are still somewhat up in the air. In the preview, you need to use the [<Generate>] attribute in the assembly into which generated types are being embedded, and you should not use the [<Generate>] attribute when using an erased provided type. I believe (but can't remember for sure off hand) that on the provided end generated-ness is determined based on the type's Assembly property.

Also, keep in mind that you don't necessarily want to use actual types (e.g. via typeof<X>) when implementing the API - you'll frequently want to use custom types derived from System.Type. There are a lot of invariants that must be satisfied among the different methods. The raw type provider API is not easy to use - I'd suggest waiting for some examples to be released which use a nicer API wrapper (which I hope should take place within the next few weeks).

Having said that, from a quick look here are at least a few things in your current approach which look wrong to me:

  1. The type that you're returning from ApplyStaticArguments doesn't have the same name as the argument typeNameWithArguments. Presumably this is why you're getting the error mentioning the type names.
  2. You're trying to use a type abbreviation which creates a generic type (e.g. WW<'a>) from a non-generic type (e.g. S<S<S<S<S<Z>>>>>).
查看更多
甜甜的少女心
3楼-- · 2020-02-25 09:03

Forgot to update on this: indeed what was missing was the 'erased' type flag (TypeProviderTypeAttributes.IsErased) in my 'exported' type. I put my experiments on github.

查看更多
登录 后发表回答