开发者

F# - Make C# library more "F#"

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

    // P开发者_如何学Croperties
    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?


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜