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.
精彩评论