Как я могу сделать это F# более функциональным?


В настоящее время я изучаю себя немного F# и написал следующий код на практике.

Код использует моно.Библиотека Сесил, чтобы придать некоторую ил в начале каждого метода каждого .net сборки, найденных в указанной директории. Ил будем называть LogMe метод в сборке loggit.dll .

Я хочу сделать этот код как функциональные и идиоматически F# как возможно.

Моя главная забота заключается в том, что способ внедрить код в сборку, имеет определенные побочные эффекты. Это неизбежно как то, что весь смысл. Я делаю это в InjectMethods функции. Эта функция передается в функцию DoOnModule.

Эта функция DoOnModule используется дважды -

  • однажды, когда методы инъекции - метод с побочными эффектами и нет никаких реальных возвращаемое значение,
  • и однажды, когда при получении LogMe эталонный метод из сборки loggit.dll - метод без побочных эффектов и полезное возвращаемое значение.

Я чувствую себя немного неловко об этом двойного использования, которые не чувствуют себя очень функциональная - и все-таки я считаю, это очень полезно сохранить повторения кода...

Как я могу сделать этот код более функциональным?

// 
open System.IO
open Mono.Cecil
open Mono.Cecil.Cil

exception LogNotFoundError of string

let IsClass(t:TypeDefinition) =
    t.IsAnsiClass && not t.IsInterface

let UsefulMethods(t:TypeDefinition) =
    t.Methods |> Seq.filter ( fun m -> m.HasBody ) 

// Perform the given funtion on the module found at the given filename.
let DoOnModule fn (file:string) =
    try
        let _module = ModuleDefinition.ReadModule file 
        Some ( fn ( _module ) )
    with
        | :? System.BadImageFormatException as ex -> printfn "%A" ex; None
        | :? System.Exception as ex -> printfn "%A" ex; None

// Do the given function on the dll filenames found in the given directory
let MapAssemblies fn directory = 
    System.IO.Directory.GetFiles(directory) |> 
    Seq.filter(fun file -> file.EndsWith("dll") || file.EndsWith("exe") ) |>
    Seq.map( fn )

// Return the methods found in the given module
let GetMethods(_module:ModuleDefinition) =
    _module.Types |> Seq.filter ( IsClass ) |> Seq.collect ( UsefulMethods )

// Get the log method found in the Loggit.dll.
// A call to this method will be injected into each method
let LogMethod (_module:ModuleDefinition) = 
    let GetLogMethod(_logmodule:ModuleDefinition) =
        let logClass = _logmodule.Types |> Seq.filter ( fun t -> t.Name.Contains ( "Log" ) ) |> Seq.head
        let logMethod = logClass.Methods |> Seq.filter ( fun m -> m.Name.Contains ( "LogMe" ) ) |> Seq.head
        _module.Import logMethod
    "Loggit.dll" |> DoOnModule GetLogMethod 

// Injects IL into the second method to call the first method, 
// passing this and the method name as parameters
let InjectCallToMethod(logMethod:MethodReference) (_method:MethodDefinition) =
        let processor = _method.Body.GetILProcessor()
        let firstInstruction = _method.Body.Instructions.Item(0)
        let parameter1 = processor.Create(OpCodes.Ldstr, _method.Name)
        let parameter2 = processor.Create(if _method.HasThis then OpCodes.Ldarg_0 else OpCodes.Ldnull)
        let call = processor.Create(OpCodes.Call, logMethod)
        processor.InsertBefore ( firstInstruction, parameter1 );
        processor.InsertBefore ( firstInstruction, parameter2 );
        processor.InsertBefore ( firstInstruction, call )

// Inject a call to the Log method at the start of every method found in the given module.
let InjectMethods(_module:ModuleDefinition) =
    // Inject the call
    let logMethod = LogMethod _module
    match logMethod with
    | Some(log) ->
        let methods = GetMethods _module
        for m in methods do
            m |> InjectCallToMethod log
    | None -> raise(LogNotFoundError("Cant find log method"))

    // Save the module
    Directory.CreateDirectory ( Path.GetDirectoryName ( _module.FullyQualifiedName ) + @"\jiggled\" ) |> ignore
    _module.Write ( Path.Combine ( Path.GetDirectoryName ( _module.FullyQualifiedName ) + @"\jiggled\", Path.GetFileName ( _module.FullyQualifiedName ) ) );

let dir = "D:\\Random\\AssemblyJig\\spog\\bin\\Debug"

// Now inject into the methods
try
    dir |>
    MapAssemblies ( DoOnModule InjectMethods )  |>
    Seq.toList |>
    ignore
with
    | :? System.Exception as ex -> printfn "%A" ex; 

System.Console.ReadLine |> ignore


480
4
задан 25 февраля 2011 в 04:02 Источник Поделиться
Комментарии
1 ответ

У меня только два незначительных что доставит некоторое неудобство с этим кодом:

Seq.filter(fun file -> file.EndsWith("dll") || file.EndsWith("exe") ) |>

Наверное, это должно сказать ".dll файлы" и ".ехе". Или вы могли бы использовать путь.GetExtension.

Seq.filter ( fun t -> t.Name.Contains ( "Log" ) ) |> Seq.head

Это будет первый тип, чье имя содержит журнал; даже если имя ILoggable или LogProcessor или что-то. Вы уверены, что знаете, что там никогда не будет никаких других типов с такими именами, чем тип вы хотите? То же самое касается метода LogMe.

3
ответ дан 25 февраля 2011 в 04:02 Источник Поделиться