F# - 使 C# 库更加“F#”

发布于 2024-11-02 17:54:16 字数 2621 浏览 1 评论 0原文

当您无法访问源代码时,使 ac# 库变得更加 F#ish 的最佳方法是什么?在我看来,您似乎有两个选择:扩展方法或封装在模块中的函数

……或者是否有一种更简洁、更少“黑客”的方式来完成这样的任务?

EF Code First 就是一个例子。

在我开始将函数包装到模块中之前:

override x.OnModelCreating(modelBuilder:DbModelBuilder) =

    // ----------- FileUpload Configuration ----------- //

    // General
    modelBuilder.Entity<FileUpload>()
        .ToTable("Some")
        |> ignore

    // Key
    modelBuilder.Entity<FileUpload>()
        .HasKey(ToLinq(<@ fun z -> z.ID @>))
        |> ignore

    // Properties
    modelBuilder.Entity<FileUpload>()
        .Property(ToLinq(<@ fun z -> z.Path @>))
        |> ignore

    modelBuilder.Entity<FileUpload>()
        .Property(ToLinq(<@ fun z -> z.Extension @>))
        |> ignore

以及之后:

override x.OnModelCreating(modelBuilder:DbModelBuilder) =
    let finished = ignore

    // ----------- FileUpload Configuration ----------- //
    let entity = modelBuilder.Entity<FileUpload>()

    // General
    entity
        |> ETC.toTable "Some"

    // Key
    entity 
        |> ETC.hasKey(fun z -> z.ID) 
        |> finished

    // Properties
    entity
        |> ETC.property(fun z -> z.Path)
        |> finished

    entity
        |> ETC.property(fun z -> z.Extension)
        |> finished

后一个示例中使用的模块:

module ETC =
    let property (expr:'a -> string) (cfg:EntityTypeConfiguration<'a>) = 
        cfg.Property(ToLinq(<@ expr @>))

    let hasKey (expr:'a -> 'b) (cfg:EntityTypeConfiguration<'a>) = 
        cfg.HasKey(% expr)

    let hasForeignKey (expr: 'a -> 'b) (cfg:DependentNavigationPropertyConfiguration<'a>) = 
        cfg.HasForeignKey(% expr)

    let hasMany (expr: 'a -> ICollection<'b>) (cfg:EntityTypeConfiguration<'a>) = 
        cfg.HasMany(% expr)

    let hasRequired (expr: 'a -> 'b) (cfg:EntityTypeConfiguration<'a>) = cfg.HasRequired(ToLinq(<@ expr @>))

    let withRequired (expr: 'a -> 'a) (cfg:ManyNavigationPropertyConfiguration<'a,'a>) = 
        cfg.WithRequired(% expr)

    let willCascadeOnDelete (cfg:CascadableNavigationPropertyConfiguration) = 
        cfg.WillCascadeOnDelete()

    let isMaxLength(cfg:StringPropertyConfiguration) = 
        cfg.IsMaxLength()

    let toTable (s:string) (cfg:EntityTypeConfiguration<'a>) = 
        cfg.ToTable s

它可能看起来不是一个显着的改进,因为我还没有编写一个完整的示例(懒惰的我) - 但正如你所见,后者但我的问题是,将方法包装到函数中并将这些函数包装到模块中以提供更实用的方式来使用这些 C# 库是否是不好的做法?

Whats the best way to make a c# library more F#ish when you dont have access to the source code? At my point of view it seems like you have two options: Extension methods or Functions wrapped in Modules

...or is there a more neat and less "hackish" way to accomplish such a task?

An example could be EF Code First.

Before I started to make up functions wrapped into modules:

override x.OnModelCreating(modelBuilder:DbModelBuilder) =

    // ----------- FileUpload Configuration ----------- //

    // General
    modelBuilder.Entity<FileUpload>()
        .ToTable("Some")
        |> ignore

    // Key
    modelBuilder.Entity<FileUpload>()
        .HasKey(ToLinq(<@ fun z -> z.ID @>))
        |> ignore

    // Properties
    modelBuilder.Entity<FileUpload>()
        .Property(ToLinq(<@ fun z -> z.Path @>))
        |> ignore

    modelBuilder.Entity<FileUpload>()
        .Property(ToLinq(<@ fun z -> z.Extension @>))
        |> ignore

and after:

override x.OnModelCreating(modelBuilder:DbModelBuilder) =
    let finished = ignore

    // ----------- FileUpload Configuration ----------- //
    let entity = modelBuilder.Entity<FileUpload>()

    // General
    entity
        |> ETC.toTable "Some"

    // Key
    entity 
        |> ETC.hasKey(fun z -> z.ID) 
        |> finished

    // Properties
    entity
        |> ETC.property(fun z -> z.Path)
        |> finished

    entity
        |> ETC.property(fun z -> z.Extension)
        |> finished

The module used in the latter example:

module ETC =
    let property (expr:'a -> string) (cfg:EntityTypeConfiguration<'a>) = 
        cfg.Property(ToLinq(<@ expr @>))

    let hasKey (expr:'a -> 'b) (cfg:EntityTypeConfiguration<'a>) = 
        cfg.HasKey(% expr)

    let hasForeignKey (expr: 'a -> 'b) (cfg:DependentNavigationPropertyConfiguration<'a>) = 
        cfg.HasForeignKey(% expr)

    let hasMany (expr: 'a -> ICollection<'b>) (cfg:EntityTypeConfiguration<'a>) = 
        cfg.HasMany(% expr)

    let hasRequired (expr: 'a -> 'b) (cfg:EntityTypeConfiguration<'a>) = cfg.HasRequired(ToLinq(<@ expr @>))

    let withRequired (expr: 'a -> 'a) (cfg:ManyNavigationPropertyConfiguration<'a,'a>) = 
        cfg.WithRequired(% expr)

    let willCascadeOnDelete (cfg:CascadableNavigationPropertyConfiguration) = 
        cfg.WillCascadeOnDelete()

    let isMaxLength(cfg:StringPropertyConfiguration) = 
        cfg.IsMaxLength()

    let toTable (s:string) (cfg:EntityTypeConfiguration<'a>) = 
        cfg.ToTable s

It might not look like a noteable improvement since I havent made up a full example (lazy me) - But as you see then the latter is much more "functional" and looks more clean than the first.. But my question is whether it's bad practice to just wrap methods into functions and wrap those functions into modules in order to provide a more functional way to use those c# libraries?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

醉酒的小男人 2024-11-09 17:54:16

首先,我认为你的包装器不会工作 - 在你的 ETC.property 中,你不能将普通函数作为参数,然后在 ETC.property 中引用它/代码>。您需要传递已经引用的 lambda 函数(并且参数的类型需要为 Expr<'a -> string>

entity |> ETC.property <@ fun z -> z.Path @>
       |> finished

对于您原来的问题 - 我认为使用带有包装器的模块函数是一个不错的选择。您可以通过指定属性的管道传递实体的表示,从而使语法更好一些,通过适当的定义,您可以得到如下内容:

EF.entity<FileUpload> modelBuilder
|> EF.hasKey <@ fun z -> z.ID @>
|> EF.property <@ fun z -> z.Path @>
|> EF.property <@ fun z -> z.Extension @>
|> EF.toTable "Some"

想法是您将拥有某种类型 EntityInfo<'T> 包装了通过调用 modelBuilder.Entity<'T>() 获得的内容,这些函数的类型如下:

EF.hasKey : Expr<'a -> 'b> -> EntityInfo<'T> -> EntityInfo<'T>
EF.toTable : string -> EntityInfo<'T> -> unit

我有意使用 unit< /code> 作为 toTable 的结果,因为它可能总是需要调用(因此我们可以将其移至末尾并避免显式ignore)。函数只需指定属性,然后返回原始的 EntityInfo<'T> 对象。

您可以使其变得更加奇特,并将整个规范编写为引用。例如:

modelBuilder |> EF.entity<FileUpload> <@ fun z ->
    EF.hasKey z.ID
    EF.property z.Path
    EF.property z.Extension
    EF.toTable "Some" @>

这不是可执行代码 - 这些函数只是虚拟函数。诀窍在于您可以处理报价并基于此调用 EF 方法。然而,这比较棘手——您必须进行大量的报价处理,而且这也不是太好。

First of all, I don't think that your wrapper is going to work - in your ETC.property you cannot take an ordinary function as an argument and then quote it inside ETC.property. You need to pass the lambda function as already quoted (and the type of the argument needs to be Expr<'a -> string>:

entity |> ETC.property <@ fun z -> z.Path @>
       |> finished

To your original question - I think that using a module with wrapper functions is a good option. You could make the syntax a bit nicer by passing the representation of the entity through a pipeline that specifies properties. With appropriate definitions, you could get something like:

EF.entity<FileUpload> modelBuilder
|> EF.hasKey <@ fun z -> z.ID @>
|> EF.property <@ fun z -> z.Path @>
|> EF.property <@ fun z -> z.Extension @>
|> EF.toTable "Some"

The idea is that you'd have some type EntityInfo<'T> that wraps the thing you get from calling modelBuilder.Entity<'T>(). The functions then have types like:

EF.hasKey : Expr<'a -> 'b> -> EntityInfo<'T> -> EntityInfo<'T>
EF.toTable : string -> EntityInfo<'T> -> unit

I intentionally used unit as the result of toTable because it is probably something that always needs to be called anyway (so we can move it to the end and avoid explicit ignore). Other functions just specify properties and then return the original EntityInfo<'T> object.

You could make it even more fancy and write the entire specification as quotation. For example:

modelBuilder |> EF.entity<FileUpload> <@ fun z ->
    EF.hasKey z.ID
    EF.property z.Path
    EF.property z.Extension
    EF.toTable "Some" @>

This isn't an executable code - the functions would be just dummy functions. The trick is that you could process the quotation and call the EF methods based on that. However, this is more tricky - you'd have to do quite a lot of quotation processing, and it isn't too nicer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文