2013-04-08 6 views
3

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

OutputStuff printfn 

Моя первая попытка была, чтобы фигура компилятор все из него для меня:

let OutputStuff output = 
    output "Header" 
    output "Data: %d" 42 

Это терпит неудачу, потому что он решает, что output функция принимает string и возвращая unit, поэтому второй вызов завершается с ошибкой.

Далее я попытался объявить output иметь ту же сигнатуру, как printfn:

let OutputStuff (output : Printf.TextWriterFormat<'a> -> 'a) = 
    output "Header" 
    output "Data: %d" 42 

Это терпит неудачу, потому что компилятор решает, что реальный тип output является Printf.TextWriterFormat<string> -> unit, поэтому снова второй вызов терпит неудачу. Он также генерирует предупреждение FS0064, указывающее, что первый вызов output заставляет код быть менее общим, чем аннотации типа, что является основной проблемой здесь.

Наконец, я попытался объявить функцию вывода в виде отдельного типа аббревиатуры:

type OutputMe<'a> = Printf.TextWriterFormat<'a> -> 'a 
let OutputStuff (output : OutputMe<'a>) = 
    output "Header" 
    output "Data: %d" 42 

Это терпит неудачу с теми же результатами, что и предыдущая попытка.

Как убедить компилятор не специализировать тип output и оставить его как Printf.TextWriterFormat<'a> -> 'a?

+0

Do вам действительно нужно передать функцию, подобную printf? Если у вас есть какая-то функция, которую вы хотите использовать, которая берет 'string', и вы хотите называть ее форматированной строкой, просто используйте' Printf.ksprintf' с вашей функцией. –

+0

Близко связанные: http://stackoverflow.com/questions/5569909/how-do-i-create-an-f-function-with-a-printf-style-logging-argument – Charlie

+1

Вы также можете найти это полезным - в нижней части этого файла ([Pervasive.fs] (https://github.com/jack-pappas/ExtCore/blob/master/ExtCore/Pervasive.fs)) есть некоторые написанные мной функции, которые берут произвольную строку формата и которые используют «Printf.ksprintf» под капотом. –

ответ

6

Проблема заключается в том, что когда вы говорите (output : Printf.TextWriterFormat<'a> -> 'a), что означает «есть некоторые 'a, для которых выход принимает Printf.TextWriterFormat<'a> к 'a». Вместо того, что вы хотите сказать, "для всех'a выхода может занять Printf.TextWriterFormat<'a> и вернуть 'a.

Это немного некрасиво, чтобы выразить в F #, но способ сделать это с типа с общий метод:

type IPrinter = 
    abstract Print : Printf.TextWriterFormat<'a> -> 'a 

let OutputStuff (output : IPrinter) = 
    output.Print "Header" 
    output.Print "Data: %d" 42 

OutputStuff { new IPrinter with member this.Print(s) = printfn s } 
+0

Похоже, что контекст дженериков F # (материал a и b) имеет разные интерпретации в зависимости от контекста (общий метод против общей функции), что странно происходит из C# и C++. Спасибо, что объяснил разницу. – Charlie

4

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

Итак, если вы хотите параметризовать свой выход, я думаю, что проще взять System.IO.TextWriter в качестве аргумента, а затем использовать printf как функцию, которая печатает вывод в указанный TextWriter:

let OutputStuff printer = 
    Printf.fprintfn printer "Hi there!" 
    Printf.fprintfn printer "The answer is: %d" 42 

OutputStuff System.Console.Out 

Таким образом, вы все равно можете печатать на разных выходах с помощью строк форматирования printf, но код выглядит намного проще (в качестве альтернативы вы можете использовать Printf.kprintf и указать функцию печати, которая принимает string вместо TextWriter).

Если вы хотите печатать в строку в памяти, это слишком легко:

let sb = System.Text.StringBuilder() 
OutputStuff (new System.IO.StringWriter(sb)) 
sb.ToString() 

В общем, TextWriter является стандартной .NET абстракции для задания вывода на печать, так что, вероятно, является хорошим выбором.

+0

Спасибо за предложение - я согласен, что это, вероятно, более практично, чем метод IPrinter, предложенный kvb. Было трудно решить, что принять, но я пошел с kvb, потому что использование внутри OutputStuff наиболее похоже на то, что я хотел. – Charlie

0

Использования inline особенности FSharp вы можете предварительно настроить функцию Printf.ksprintf с функцией «работой» и, таким образом, имеете конечную функцию, которая принимает строку формата вместе со своими специфическими требуемыми параметрами, аналогичными printf или sprintf. Функция работы, которая признает, что в результате string, вероятно, может делать все, что он хочет, например, в случае журналирования примера, который печатает string к console:

let logger = fun (msg:string) -> System.Console.WriteLine msg 
let inline log msg = Printf.ksprintf logger msg 

Примеры использования:

open System 
open System.Globalization.CultureInfo.CurrentCulture 

log "Hello %s, how is your %s" name Calendar.GetDayOfWeek(DateTime.Today) 

log "132 + 6451 = %d" (132+6451) 

... 
Смежные вопросы