F # - сделать библиотеку C # более "F #" - PullRequest
1 голос
/ 21 апреля 2011

Каков наилучший способ сделать библиотеку ac # более функциональной, если у вас нет доступа к исходному коду?На мой взгляд, кажется, что у вас есть два варианта: методы расширения или функции, заключенные в модули

... или есть более аккуратный и менее "хакерский" способ решения такой задачи?

Примером может служить 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 #?

1 Ответ

2 голосов
/ 21 апреля 2011

Прежде всего, я не думаю, что ваша оболочка будет работать - в вашей ETC.property вы не можете взять обычную функцию в качестве аргумента и затем заключить ее в кавычки внутри ETC.property. Вам нужно передать лямбда-функцию, как уже указано (и тип аргумента должен быть 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 как результат 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, основанные на этом. Тем не менее, это более сложная задача - вам придется довольно много обрабатывать цитаты, и это не слишком хорошо.

...