Как мне создать функцию F # с аргументом ведения журнала в стиле printf? - PullRequest
7 голосов
/ 06 апреля 2011

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

let testLogger (source:seq<'a>) logger =
    logger "Testing..."
    let length = source |> Seq.length
    logger "Got a length of %d" length


let logger format = Printf.kprintf (printfn "%A: %s" System.DateTime.Now) format
testLogger [1; 2; 3] logger

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

Ответы [ 2 ]

14 голосов
/ 07 апреля 2011

Как указывает Томас, функции в F # не могут требовать полиморфных аргументов.В этом случае, я думаю, что подход Томаса довольно хорош, так как вам, вероятно, нужно только иметь возможность передать функцию string -> unit, которая используется для ведения журнала.

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

type ILogger = abstract Log : Printf.StringFormat<'a,unit> -> 'a

let testLogger (source:seq<'a>) (logger:ILogger) = 
    logger.Log "Testing..."
    let length = source |> Seq.length        
    logger.Log "Got a length of %d" length

let logger = { 
    new ILogger with member __.Log format = 
        Printf.kprintf (printfn "%A: %s" System.DateTime.Now) format }

Чтобы сделать эту работу более удобной с выводом типа, выможет определить модуль с помощью простой вспомогательной функции:

module Log =
    let logWith (logger : ILogger) = logger.Log

let testLogger2 (source:seq<'a>) logger =
    Log.logWith logger "Testing..."
    let length = source |> Seq.length        
    Log.logWith logger "Got a length of %d" length

Этот конечный результат во многом похож на решение Томаса, но дает вам немного больше гибкости в том, как вы определяете свой регистратор, который на самом деле может или не может бытьполезно в этом случае.

11 голосов
/ 06 апреля 2011

К сожалению, вы не можете передавать функции типа printf в качестве параметров другим функциям, а затем использовать их с несколькими различными аргументами . Проблема в том, что printf является универсальной функцией типа Printf.TextWriterFormat<'a> -> 'a. Фактический тип, замененный параметром типа 'a, представляет собой некоторый тип функции, который отличается каждый раз, когда вы используете printf (например, 'a == string -> unit для "%s" и т. Д.).

В F # вы не можете иметь параметр функции, которая сама является универсальной функцией. Общей функцией должна быть некоторая глобальная функция, но вы можете параметризовать ее функцией, которая на самом деле что-то делает со строкой. По сути, это то, что делает kprintf, но вы можете назвать свою функцию лучше:

let logPrintf logger format = 
    Printf.kprintf logger format

Примером функции, параметризованной регистратором, будет:

let testLogger (source:seq<'a>) logger =
    logPrintf logger "Testing..."
    let length = source |> Seq.length
    logPrintf logger "Got a length of %d" length


let logger = printfn "%A: %s" System.DateTime.Now
testLogger [1; 2; 3] logger
...