Функция, подобная printf, в вычислительном выражении F # - PullRequest
0 голосов
/ 19 июня 2020

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

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

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

У меня есть доказательство работы концепции, и генерация чисел работает очень хорошо, но я не могу заставить ее хорошо работать с параметрами стиля printf для моя функция ведения журнала:

module Test

type Simulator = { random : int * int -> int; logger : string -> unit }

module Simulator =
    let create = let random = new System.Random() in 
                     { 
                     random = fun (min, max) -> random.Next(min, max)
                     logger = fun str -> (printfn "%s" str |> ignore) 
                     }

type Simulation<'T> = Simulation of (Simulator -> 'T)

module Simulation =
    /// Runs a simulation given a simulator
    let inline run state simulation = let (Simulation(play)) = simulation in play state
    /// Returns a random number
    let random min max = Simulation (fun simulator -> simulator.random (min, max))
    /// Writes to simulation log
    let log = Simulation (fun simulation -> Printf.ksprintf simulation.logger)


type SimulationBuilder() =
    member this.Bind (x, f) = let (Simulation(simulation)) = x in Simulation (fun simulator -> f (simulation simulator))
    member this.Return (x) = x

let simulate = new SimulationBuilder()

let simpleSimulation =
    simulate
        {
        //very nice, working
        let! x = Simulation.random 2 12
        //this is working, but verbose
        let! logger = Simulation.log 
        do logger "Value: %d" x
        //I want to write 
        //do! Simulation.log "Value: %d" x
        // or something similar
        return x;
        }

Simulation.run Simulator.create simpleSimulation |> ignore

Кто-нибудь может мне помочь? Я новичок в написании пользовательских выражений вычислений.

EDIT

Обратите внимание, что функция журнала может иметь подпись

let log str = Simulation (fun simulation -> simulation.logger str)

, и это будет легко позвонить:

simulate
   {
   ...
   do! Simulation.log "Hello world!"
   ...
   }

, но здесь я теряю возможность передавать параметры формата без использования sprintf

EDIT

Ошибка в реализация привязки, должна быть:

member this.Bind (x, f) = let (Simulation(simulation)) = x in Simulation (fun simulator -> Simulation.run simulator (f (simulation simulator)))

1 Ответ

4 голосов
/ 19 июня 2020

Проблема здесь в том, что ваша функция log возвращает функцию печати, заключенную в Simulation, так что вам нужно использовать дополнительный let!, чтобы развернуть его, прежде чем вы сможете его использовать.

Но ничто не мешает вам обернуть результат функции в Simulation, а не саму функцию:

let log = Printf.ksprintf (fun str -> Simulation (fun simulation -> simulation.logger str))

Это позволит вашему коду скомпилировать:

do! Simulation.log "Value: %d" x

Потому что теперь выражение Simulation.log "Value: %d" возвращает функцию int -> Simulation<unit>, а не Simulation<int -> unit>, как это было раньше.


Однако это также мономорфизирует функцию log: компилятор увидит, что она использовалась с одним параметром int и исправит его тип на принятие int s, а затем, если вы попробуете что-то другое:

do! Simulation.log "Value: %s" "foo"

, он не будет компилироваться снова, жалуясь, что ожидал int, но с учетом string.

Чтобы решить эту новую проблему, вам придется немного помочь компилятору, предоставив явную аннотацию типа generi c и указав, как именно она должна быть переведено to ksprintf:

let log (format: Printf.StringFormat<'T, _>) = 
  Printf.ksprintf (fun str -> Simulation (fun simulation -> simulation.logger str)) format

Здесь 'T представляет строку параметров, например int -> или string -> bool -> или что-то еще, что указывает ваша строка формата.

С этим, любой формат будет компилироваться:

do! Simulation.log "Value: %d" x
do! Simulation.log "Value: %s" "foo"
do! Simulation.log "I have %d apples which are %s" 42 "rotten"
...