2015-06-13 4 views
6

У меня есть следующий код, который попытается прочитать, возможно, неполные данные (данные изображения, например) из сетевого потока с использованием обычного MaybeBuilder:выражение # вычисление F прозрачное состояние прохождения с Bind

let image = maybe { 
    let pos = 2 //Initial position skips 2 bytes of packet ID 
    let! width, pos = readStreamAsInt 2 pos 
    let! height, pos = readStreamAsInt 2 pos 
    let! data, pos = readStream (width*height) pos 
    advanceInStream pos 
    return {width = width; height = height; pixels = data} 
} 

Итак, ReadStream [ asInt] [numBytes] Функция [offset] возвращает некоторые [данные] или None, если данные еще не были получены в NetworkStream. Функция advanceInStream выполняется, когда считывается весь сетевой пакет.

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

P.S. MaybeBuilder используется:

type MaybeBuilder() =  
    member x.Bind(d,f) = Option.bind f d 
    member x.Return d = Some d 
    member x.ReturnFrom d = d 
    member x.Zero() = None 
let maybe = new MaybeBuilder() 

P.P.S

На втором подумал я, кажется, есть сделать позы изменяемыми, из-за возможными «за» или «а» петли в чтении. Просто пусть! отлично работает с pos Привязывает тени, но вы не можете удерживать неизменность, если добавить чтение в цикл, не так ли? Тогда задача становится тривиальной.

+0

Да, это действительно можно записать в виде выражения вычислений. Это часто встречается при определении выражений вычисления парсера (которые должны отслеживать позицию в строке). – FuleSnabel

+3

Существует две проблемы, которые необходимо решить: (1) работа с 'pos' представляется корректной задачей для выражения вычисления' State', тогда как (2) работает с функциями, возвращающими 'Option <'T>' - это задание для 'Maybe' comp.expression, точно так же, как вы. Самая большая проблема заключается в том, что ** Выражения вычисления в F # не сочетаются хорошо **, например, у вас может быть тот или иной, но для того, чтобы получить два одновременно, вам нужно написать собственное пользовательское выражение comp.expression, которое будет делайте обе вещи. Это хорошо для учебных целей, но в реальных проектах, возможно, трудно поддерживать. – bytebuster

ответ

3

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

На «традиционных» языках у нас есть хорошая поддержка для составления таких значений, как целые числа, но мы сталкиваемся с проблемами при разработке парсеров (получение значений из двоичного потока по существу разбора). Для парсеров мы хотели бы составить простые функции парсера в более сложные функции парсера, но здесь «традиционные» языки часто не имеют хорошей поддержки.

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

Сначала давайте определим функцию StreamReader. A StreamReader принимает StreamPosition (поток + позиция) и производит обновленные StreamPosition и StreamReaderResult (значение чтения или отказ).

type StreamReader<'T> = 
    StreamReader of (StreamPosition -> StreamPosition*StreamReaderResult<'T>) 

(Это самый важный шаг.)

Мы хотели, чтобы иметь возможность создавать простые StreamReader функции в более сложные. Очень важным свойством, которое мы хотим сохранить, является то, что операция компоновки «закрыта» под StreamReader, что означает, что результатом композиции является новый StreamReader, который, в свою очередь, может быть составлен бесконечно.

Для того, чтобы прочитать изображение, нам необходимо прочитать ширину & height, вычислить произведение и прочитать байты. Что-то вроде этого:

let readImage = 
    reader { 
    let! width = readInt32 
    let! height = readInt32 
    let! bytes = readBytes (width*height) 

    return width, height, bytes 
    } 

Из состава закрытия readImage является StreamReader<int*int*byte[]>.

Для того, чтобы иметь возможность создавать StreamReader как выше, мы должны определить выражение вычисления, но прежде чем мы сможем сделать это, мы должны определить операцию Return и Bind для StreamReader. Оказывается, Yield тоже хорошо.

module StreamReader = 
    let Return v : StreamReader<'T> = 
    StreamReader <| fun sp -> 
     sp, (Success v) 

    let Bind (StreamReader t) (fu : 'T -> StreamReader<'U>) : StreamReader<'U> = 
    StreamReader <| fun sp -> 
     let tsp, tr = t sp 
     match tr with 
     | Success tv -> 
     let (StreamReader u) = fu tv 
     u tsp 
     | Failure tfs -> tsp, Failure tfs 

    let Yield (ft : unit -> StreamReader<'T>) : StreamReader<'T> = 
    StreamReader <| fun sp -> 
     let (StreamReader t) = ft() 
     t sp 

Return тривиальна как StreamReader следует вернуть данное значение и не обновлять StreamPosition.

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

Yield только что создает функцию StreamReader и запускает ее. Yield используется F # при построении вычислений.

Наконец давайте создадим выражение вычисления построитель

type StreamReaderBuilder() = 
    member x.Return v = StreamReader.Return v 
    member x.Bind(t,fu) = StreamReader.Bind t fu 
    member x.Yield(ft) = StreamReader.Yield ft 

let reader = StreamReaderBuilder() 

Теперь мы создали базовую основу для объединения StreamReader функций. Кроме того, нам нужно было бы определить примитивные функции StreamReader.

Полный пример:

open System 
open System.IO 

// The result of a stream reader operation is either 
// Success of value 
// Failure of list of failures 
type StreamReaderResult<'T> = 
    | Success of 'T 
    | Failure of (string*StreamPosition) list 

and StreamPosition = 
    { 
    Stream : byte[] 
    Position : int 
    } 

    member x.Remaining = max 0 (x.Stream.Length - x.Position) 

    member x.ReadBytes (size : int) : StreamPosition*StreamReaderResult<byte[]> = 
    if x.Remaining < size then 
     x, Failure ["EOS", x] 
    else 
     let nsp = StreamPosition.New x.Stream (x.Position + size) 
     nsp, Success (x.Stream.[x.Position..(x.Position + size - 1)]) 

    member x.Read (converter : byte[]*int -> 'T) : StreamPosition*StreamReaderResult<'T> = 
    let size = sizeof<'T> 
    if x.Remaining < size then 
     x, Failure ["EOS", x] 
    else 
     let nsp = StreamPosition.New x.Stream (x.Position + size) 
     nsp, Success (converter (x.Stream, x.Position)) 

    static member New s p = {Stream = s; Position = p;} 

// Defining the StreamReader<'T> function is the most important decision 
// In this case a stream reader is a function that takes a StreamPosition 
// and produces a (potentially) new StreamPosition and a StreamReadeResult 
type StreamReader<'T> = StreamReader of (StreamPosition -> StreamPosition*StreamReaderResult<'T>) 

// Defining the StreamReader CE 
module StreamReader = 
    let Return v : StreamReader<'T> = 
    StreamReader <| fun sp -> 
     sp, (Success v) 

    let Bind (StreamReader t) (fu : 'T -> StreamReader<'U>) : StreamReader<'U> = 
    StreamReader <| fun sp -> 
     let tsp, tr = t sp 
     match tr with 
     | Success tv -> 
     let (StreamReader u) = fu tv 
     u tsp 
     | Failure tfs -> tsp, Failure tfs 

    let Yield (ft : unit -> StreamReader<'T>) : StreamReader<'T> = 
    StreamReader <| fun sp -> 
     let (StreamReader t) = ft() 
     t sp 

type StreamReaderBuilder() = 
    member x.Return v = StreamReader.Return v 
    member x.Bind(t,fu) = StreamReader.Bind t fu 
    member x.Yield(ft) = StreamReader.Yield ft 

let reader = StreamReaderBuilder() 

let read (StreamReader sr) (bytes : byte[]) (pos : int) : StreamReaderResult<'T> = 
    let sp = StreamPosition.New bytes pos 
    let _, sr = sr sp 
    sr 

// Defining various stream reader functions 
let readValue (converter : byte[]*int -> 'T) : StreamReader<'T> = 
    StreamReader <| fun sp -> sp.Read converter 

let readInt32 = readValue BitConverter.ToInt32 
let readInt16 = readValue BitConverter.ToInt16 
let readBytes size : StreamReader<byte[]> = 
    StreamReader <| fun sp -> 
    sp.ReadBytes size 

let readImage = 
    reader { 
    let! width = readInt32 
    let! height = readInt32 
    let! bytes = readBytes (width*height) 

    return width, height, bytes 
    } 

[<EntryPoint>] 
let main argv = 
    // Sample byte stream 
    let bytes = [|2;0;0;0;3;0;0;0;1;2;3;4;5;6|] |> Array.map byte 
    let result = read readImage bytes 0 

    printfn "%A" result 

    0 
+0

Спасибо, я догадываюсь, что я понял основную идею: сам монадический тип не является значением, а является результатом состояния функции -> state *, поэтому Bind (и целое выражение тоже) возвращает эту функцию, которая позволяет передавать потоковое положение вниз по цепочке. И, кажется, я ошибаюсь в цикле «For» и изменчивости в своем посте, я постараюсь написать его правильно сейчас. – Dzugaru

+0

Да, это правильно. Составление функций парсера очень мощно. Если вы заглянете, например, в «FParsec» (библиотека парсеров), вы увидите, что использует подобный подход. – FuleSnabel

+0

Добавлен немного больше разъяснений. – FuleSnabel