如何获得F#签名-Visual Studio或vs代码

时间:2019-01-30 18:05:34

标签: visual-studio f#

我希望能够在我的代码中显式地编写类型签名。

VS代码将(最终)生成某种重影签名,但是我实际上想明确地获取这些生成的签名并键入代码。

有什么想法吗?我可以使用FSI,但这可能是一项繁琐的技术。

理想情况下,我会右键单击并“生成签名” ..尽管这并不总是适合人们的编码风格...我倾向于编写代码;

   let f : int -> string = 
      fun i -> i.ToString()

1 个答案:

答案 0 :(得分:3)

您可以使用Compiler Services SDK获取F#函数的类型。这将需要为您的项目编写一个自定义分析器,但是它应该是可重用的组件,您可以在实现后将其集成到开发过程中。解决每个函数的类型签名的基本步骤是:

  1. 创建F#类型检查器(FSharpChecker)实例。
  2. 加载您的项目选项(FSharpProjectOptions)。
  3. 解析并检查每个文件(FSharpChecker.parseAndCheckFileInProject)。
  4. 从每个类型检查器结果(FSharpCheckFileAnswer)中检索声明列表。
  5. 为每个声明打印类型签名(FSharpType)。

以下是我汇总的一个快速解决方案:

#r @"FSharp.Compiler.Service.25.0.1\lib\net45\FSharp.Compiler.Service.dll"
#r @"FSharp.Compiler.Service.ProjectCracker.25.0.1\lib\net45\FSharp.Compiler.Service.ProjectCracker.dll"

open Microsoft.FSharp.Compiler.SourceCodeServices
open System
open System.IO

type Namespace =
    {
        Name: string
        XmlDoc: System.Collections.Generic.IList<string>
    }    

type Declaration =
| Namespace of Namespace * Declaration list
| Module of FSharpEntity * Declaration list
| Class of FSharpEntity * Declaration list
| Interface of FSharpEntity * Declaration list
| Enum of FSharpEntity * Declaration list
| Record of FSharpEntity * Declaration list
| Union of FSharpEntity * Declaration list
| Function of FSharpMemberOrFunctionOrValue
| Binding of FSharpMemberOrFunctionOrValue    

let checker = FSharpChecker.Create(1, true)

let getProject projectFile =
    ProjectCracker.GetProjectOptionsFromProjectFile(projectFile)

let private isNamespace (declaration: FSharpImplementationFileDeclaration) =
    match declaration with
    | FSharpImplementationFileDeclaration.Entity (entity, children) -> entity.IsNamespace
    | _ -> false

let rec private getDeclaration nsSoFar (declaration: FSharpImplementationFileDeclaration) =
    [
        match declaration with
        | FSharpImplementationFileDeclaration.Entity (entity, children) ->
            if entity.IsNamespace then
                if children.Length = 1 && children.Head |> isNamespace
                then match nsSoFar with
                     | Some ns -> yield! getDeclaration (Some <| sprintf "%s.%s" ns entity.DisplayName) children.Head
                     | None -> yield! getDeclaration (Some entity.DisplayName) children.Head
                else match nsSoFar with
                     | Some ns -> 
                        let nsEntity = {Name = sprintf "%s.%s" ns entity.DisplayName; XmlDoc = entity.XmlDoc}
                        yield Namespace (nsEntity, children |> List.collect (getDeclaration nsSoFar))
                     | None -> 
                        let nsEntity = {Name = entity.DisplayName; XmlDoc = entity.XmlDoc}
                        yield Namespace (nsEntity, children |> List.collect (getDeclaration nsSoFar))
            elif entity.IsClass then
                yield Class (entity, children |> List.collect (getDeclaration nsSoFar))
            elif entity.IsInterface then
                yield Interface (entity, children |> List.collect (getDeclaration nsSoFar))            
            elif entity.IsEnum then
                yield Enum (entity, children |> List.collect (getDeclaration nsSoFar))
            elif entity.IsFSharpModule then
                yield Module (entity, children |> List.collect (getDeclaration nsSoFar))
            elif entity.IsFSharpRecord then
                yield Record (entity, children |> List.collect (getDeclaration nsSoFar))
            elif entity.IsFSharpUnion then
                yield Union (entity, children |> List.collect (getDeclaration nsSoFar))
            else 
                ()                        

        | FSharpImplementationFileDeclaration.MemberOrFunctionOrValue (func, _, _) ->
            if func.IsValCompiledAsMethod
            then yield Function func
            else yield Binding func
        | _ -> ()    
    ]    

let getDeclarations (project: FSharpProjectOptions) file =
    async {
        let source = File.ReadAllText file
        let! (parseResults, checkResults) = checker.ParseAndCheckFileInProject(file, 1, source, project)

        return 
            match checkResults with
            | FSharpCheckFileAnswer.Succeeded checkInfo -> 
                match checkInfo.ImplementationFile with
                | Some implementation -> implementation.Declarations |> List.collect (getDeclaration None)
                | None -> failwithf "No Implementation Available for File %s" file
            | error -> failwithf "Error Checking File %s:\r\n%A" file error
    }    

let getDeclarationsForScript file =
    async {
        let source = File.ReadAllText file        
        let! (project, _) = checker.GetProjectOptionsFromScript(file, source)
        return! getDeclarations project file
    } 

然后,如果我们有一个名为“ Test.fsx”的示例脚本文件,其内部具有类似于您的示例的功能(let f i = sprintf "%d" i,则可以这样打印该函数的签名:

let getTypeName (t: FSharpType) =
    t.Format(FSharpDisplayContext.Empty).Replace("Microsoft.FSharp.Core.", "")

let rec printFunctionSignatures declarations =
    for declaration in declarations do
        match declaration with
        | Namespace (_, ds) -> printFunctionSignatures ds
        | Module (_, ds) -> printFunctionSignatures ds
        | Function f -> f.FullType |> getTypeName |> printfn "%s: %s" f.DisplayName
        | _ -> () // Handle all the other cases

getDeclarationsForScript "Test.fsx" 
|> Async.RunSynchronously
|> printFunctionSignatures

这会标出:

f: int -> string