f# - 如何使用 FSharp.Compiler.Services 的结果
问题描述
我正在尝试构建一个类似于 FsBolero (TryWebassembly)、Fable Repl 以及更多使用 Fsharp.Compiler.Services 的系统。
所以我希望实现我的目标是可行的,但我遇到了一个问题,我希望这只是我在软件开发领域缺乏经验的结果
我正在实现一项服务,使用户能够在域系统的上下文中编写自定义算法 (DSL)。
要编译的代码是完全正确的 F# 代码的纯原始字符串。
示例 DSL 算法如下所示:
let code = """
module M
open Lifespace
open Lifespace.LocationPricing
let alg (pricing:LocationPricing) =
let x=pricing.LocationComparisions.CityLevel.Transportation
(8.*x.PublicTransportationStation.Data+ x.RailwayStation.Data+ 5.*x.MunicipalBikeStation.Data) / 14.
"""
该代码通过 CompileToDynamicAssembly 正确编译。我还通过 -r Fsc 参数提供了对我的域 *.dll 的正确引用。
这就是我的问题,因为接下来我有生成的动态程序集并想要调用该算法。当 arg 是 LocationPricing 类型并且来自主/托管项目参考时,我使用 f.Invoke(null, [|arg|]) 进行反射(还有其他方法吗?)。
调用不起作用,因为我有错误:
无法将 LocationPricing 转换为 LocationPricing
我在尝试使用 F# 交互服务时遇到了同样的问题,错误类似:
无法将 [A]LocationPricing 转换为 [B]LocationPricing
我知道我在上下文中有两个相同的 dll,而 F# 确实有外部别名语法来解决它。
但是其他提到的公共系统以某种方式处理了这个问题,或者我做错了。
我将查看 Bolero 和 FableRepl 的代码,但肯定需要一些时间来理解其中的缺陷。
更新:完整代码(Azure 函数)
namespace AzureFunctionFSharp
open System.IO
open System.Text
open Microsoft.Azure.WebJobs
open Microsoft.Azure.WebJobs.Extensions.Http
open Microsoft.AspNetCore.Http
open Microsoft.AspNetCore.Mvc
open Microsoft.Extensions.Logging
open FSharp.Compiler.SourceCodeServices
open Lifespace.LocationPricing
module UserCodeEval =
type CalculationResult = {
Value:float
}
type Error = {
Message:string
}
[<FunctionName("UserCodeEvalSampleLocation")>]
let Run([<HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)>] req: HttpRequest, log: ILogger , [<Blob("ranks/short-ranks.json", FileAccess.Read)>] myBlob:Stream)=
log.LogInformation("F# HTTP trigger function processed a request.")
// confirm valid domain dll location
// for a in System.AppDomain.CurrentDomain.GetAssemblies() do
// if a.FullName.Contains("wrometr.lam.to.ranks") then log.LogInformation(a.Location)
// let code = req.Query.["code"].ToString()
// replaced just to show how the user algorithm can looks like
let code =
"""
module M
open Lifespace
open Lifespace.LocationPricing
open Math.MyStatistics
open MathNet.Numerics.Statistics
let alg (pricing:LocationPricing) =
let x= pricing.LocationComparisions.CityLevel.Transportation
(8.*x.PublicTransportationStation.Data+ x.RailwayStation.Data+ 5.*x.MunicipalBikeStation.Data) / 14.
"""
use reader = new StreamReader(myBlob, Encoding.UTF8)
let content = reader.ReadToEnd()
let encode x = LocationPricingStore.DecodeArrayUnpack x
let pricings = encode content
let checker = FSharpChecker.Create()
let fn = Path.GetTempFileName()
let fn2 = Path.ChangeExtension(fn, ".fsx")
let fn3 = Path.ChangeExtension(fn, ".dll")
File.WriteAllText(fn2, code)
let errors, exitCode, dynAssembly =
checker.CompileToDynamicAssembly(
[|
"-o"; fn3;
"-a"; fn2
"-r";@"C:\Users\longer\azure.functions.compiler\bin\Debug\netstandard2.0\bin\MathNet.Numerics.dll"
"-r";@"C:\Users\longer\azure.functions.compiler\bin\Debug\netstandard2.0\bin\Thoth.Json.Net.dll"
// below is crucial and obtained with AppDomain resolution on top, comes as a project reference
"-r";@"C:\Users\longer\azure.functions.compiler\bin\Debug\netstandard2.0\bin\wrometr.lam.to.ranks.dll"
|], execute=None)
|> Async.RunSynchronously
let assembly = dynAssembly.Value
// get one item to test the user algorithm works in the funtion context
let arg = pricings.[0].Data.[0]
let result =
match assembly.GetTypes() |> Array.tryFind (fun t -> t.Name = "M") with
| Some moduleType ->
moduleType.GetMethods()
|> Array.tryFind (fun f -> f.Name = "alg")
|>
function
| Some f -> f.Invoke(null, [|arg|]) |> unbox<float>
| None -> failwith "Function `f` not found"
| None -> failwith "Module `M` not found"
// end of azure function, not important in the problem context
let res = req.HttpContext.Response
match String.length code with
| 0 ->
res.StatusCode <- 400
ObjectResult({ Message = "No Good, Please provide valid encoded user code"})
| _ ->
res.StatusCode <-200
ObjectResult({ Value = result})
**更新:更改数据流** 为了继续前进,我辞职在两个地方都使用域类型。相反,我在域组装中执行所有逻辑,并且只将原语(字符串)传递给反射调用。每次我对每个 Azure 函数调用进行编译时,缓存仍然有效,我也很惊讶。我也将使用 FSI 进行实验,理论上它应该比反射更快,但会增加将参数传递给评估的负担
解决方案
在您的示例中,在动态编译的程序集中运行的代码和调用它的代码需要共享一个 type LocationPricing
。您看到的错误通常意味着您以某种方式最终在调用动态编译代码的进程和实际运行计算的代码中加载了不同的程序集。
很难确切说明为什么会发生这种情况,但是您应该能够通过查看当前 App Domain 中加载的程序集来检查是否确实如此。假设您的共享程序集是MyAssembly
. 你可以运行:
for a in System.AppDomain.CurrentDomain.GetAssemblies() do
if a.FullName.Contains("MyAssembly") then printfn "%s" a.Location
如果您使用的是 F# 交互服务,则解决此问题的一个技巧是启动 FSI 会话,然后将交互发送到从正确位置加载程序集的服务。沿着这些思路:
let myAsm = System.AppDomain.CurrentDomain.GetAssemblies() |> Seq.find (fun asm ->
asm.FullName.Contains("MyAssembly"))
fsi.EvalInteraction(sprintf "#r @\"%s\"" myAsm.Location)
推荐阅读
- json - Scala / Play / Joda - 找不到类型的 Json 序列化程序(字符串,org.joda.time.DateTime)
- javascript - 如何在浏览器中使用 SOCKS5 协议从 JavaScript 连接到服务器?
- xamarin - Xamarin 中的自适应字体帮助
- python - 当我使用线程 QTextEdit 时处理
- angular - 使用可观察的 ngIf 显示/隐藏表单提交按钮
- docker - 在不提供 -i 的情况下是否有 docker -t 选项的用例?
- android - 如何在 Firebase 中添加列
- sql - 为什么我的触发器在我的 Oracle 数据库上不起作用?
- node.js - IBM Cloud Functions:如何在 NodeJS 中要求“node-geocoder”
- html - 如何从日期选择器中提取日历?