Log in function or function using it?

后端 未结 2 749
眼角桃花
眼角桃花 2021-01-19 05:10

Is it best (I\'m aware of that there\'s no silver bullet, but there may be some advantage by using one over the other) - to log in the calling function, or the func

2条回答
  •  深忆病人
    2021-01-19 05:28

    There is a more general way to implement cross-cutting concerns such as logging with a functional language. The example I have is from an async service library (think ASP.NET MVC and ActionFilters) but the same applies here as well. As stated by Mark, the function tryGetServer is of type string -> MongoServer option. Suppose we abstract it to:

    type Service<'a, 'b> = 'a -> 'b option

    Then suppose we also have a type as follows:

    type Filter<'a, 'b> = 'a -> Service<'a, 'b> -> 'b option

    A filter is a function which takes a value 'a and a Service<'a, 'b> and then returns a value of the same type as the Service<'a, 'b> function. The simplest filter is a function which simply passes the 'a it receives directly to the service and returns the value it gets from the service. A more interesting filter would be a function which prints a log message after receiving output from the service.

    let loggingFilter (connStr:string) (tryGetServer:string -> MongoServer option) : Filter =
      let server = tryGetServer connStr 
      match tryGetServer connStr with
      | Some _ ->
        logger.Information "Successfully connected to the database server."
        server
      | None -> 
        logger.Information "Unable to connect to the database server."
        server
    

    Then if you have the following defined:

    type Continuation<'a,'r> = ('a  -> 'r) -> 'r
    
    module Continuation =     
    
        let bind (m:Continuation<'a, 'r>) k c = m (fun a -> k a c)
    
    module Filter =
    
        /// Composes two filters into one which calls the first one, then the second one.
        let andThen (f2:Filter<_,,_>) (f1:Filter<_,_>) : Filter<_,_> = fun input -> Continuation.bind (f1 input) f2
    
        /// Applies a filter to a service returning a filtered service.
        let apply (service:Service<_,_>) (filter:Filter<_,_>) : Service<_,_> = fun input -> filter input service
    
        /// The identity filter which passes the input directly to the service and propagates the output.
        let identity : Filter<_,_> = fun (input:'Input) (service:Service<_,_>) -> service input
    

    You can apply a filter to a service and get back the original service type but which now does logging:

    let tryGetServerLogable = Filter.apply tryGetServer loggingFilter
    

    Why bother? Well, now you can compose filters together. For example you may add a filter which measures the time it takes to create a connection and you can then combine them using Filter.andThen. The gist I originally made is here.

    Another approach to consider is the use of a writer monad. With the writer monad, you can defer the actual printing of log messages until some well defined point, but still have similar composition characteristics.

提交回复
热议问题