3 people like it.

Computation expression for String.Create

We can use power of computation expressions and InlineIfLambda to get nice syntax and mutable index tracking for allocating strings

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
63: 
64: 
65: 
66: 
67: 
68: 
69: 
70: 
open System

type SpanReader<'T, 'TState, 'TRet> = delegate of span: Span<'T> * state: byref<'TState> -> 'TRet

[<Struct; NoComparison; NoEquality>]
type StringAllocatorState<'TCtx> = 
    {
        mutable Position: int
        Ctx: 'TCtx
    }

type StringAllocatorReader<'TCtx, 'TRet> = SpanReader<char, StringAllocatorState<'TCtx>, 'TRet>

let inline getContext() =
    StringAllocatorReader(fun _ state -> state.Ctx)

type StringAllocator() =  
    member inline this.Zero() = StringAllocatorReader(fun _ _ -> ())
    
    member inline _.Combine(
        [<InlineIfLambda>] f: StringAllocatorReader<'TCtx, unit>, 
        [<InlineIfLambda>] g: StringAllocatorReader<'TCtx, unit>) =
        StringAllocatorReader(fun chars state -> 
            f.Invoke(chars, &state)
            g.Invoke(chars, &state))
    
    member inline _.Delay(
        [<InlineIfLambda>] f: unit -> StringAllocatorReader<'TCtx, unit>) = 
        StringAllocatorReader(fun chars state -> (f()).Invoke(chars, &state))
    
    member inline _.Bind(
        [<InlineIfLambda>] reader: StringAllocatorReader<'TCtx, 'TRet>,
        [<InlineIfLambda>] cont: 'TRet -> StringAllocatorReader<'TCtx, unit>) =
        StringAllocatorReader(fun chars state ->
            let ret = reader.Invoke(chars, &state)
            (cont ret).Invoke(chars, &state))
    
    member inline _.Yield(ch: char) = 
        StringAllocatorReader(fun chars state -> 
            chars[state.Position] <- ch; 
            state.Position <- state.Position + 1)

    member inline _.Yield(str: string) =
        StringAllocatorReader(fun chars state -> 
            str.AsSpan().CopyTo(chars.Slice(state.Position))
            state.Position <- state.Position + str.Length)

let stringAlloc = StringAllocator()

let inline run 
    (length: int)
    (ctx: 'TCtx)
    ([<InlineIfLambda>] action: StringAllocatorReader<'TCtx, unit>) =
    String.Create(
        length, 
        ctx, 
        System.Buffers.SpanAction(fun chars ctx -> 
            let mutable state = { Position = 0; Ctx = ctx }
            action.Invoke(chars, &state)))
            
let s = 
    run 5 [| "ab"; "cd"; "e" |]
        (stringAlloc {
            let! xs = getContext()
            yield xs[0]
            yield xs[1]
            yield xs[2]
        })
    
Console.WriteLine(s)
namespace System
type SpanReader<'T,'TState,'TRet> =
  delegate of Span<'T> * byref<'TState> -> 'TRet
type byref<'T> = (# "<Common IL Type Omitted>" #)
Multiple items
type StructAttribute =
  inherit Attribute
  new : unit -> StructAttribute

--------------------
new : unit -> StructAttribute
Multiple items
type NoComparisonAttribute =
  inherit Attribute
  new : unit -> NoComparisonAttribute

--------------------
new : unit -> NoComparisonAttribute
Multiple items
type NoEqualityAttribute =
  inherit Attribute
  new : unit -> NoEqualityAttribute

--------------------
new : unit -> NoEqualityAttribute
[<Struct>]
type StringAllocatorState<'TCtx> =
  { mutable Position: int
    Ctx: 'TCtx }
StringAllocatorState.Position: int
Multiple items
val int : value:'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
StringAllocatorState.Ctx: 'TCtx
type StringAllocatorReader<'TCtx,'TRet> = SpanReader<char,StringAllocatorState<'TCtx>,'TRet>
Multiple items
val char : value:'T -> char (requires member op_Explicit)

--------------------
type char = Char
val getContext : unit -> StringAllocatorReader<'a,'a>
val state : byref<StringAllocatorState<'a>>
Multiple items
type StringAllocator =
  new : unit -> StringAllocator
  member Bind : reader:StringAllocatorReader<'TCtx,'TRet> * cont:('TRet -> StringAllocatorReader<'TCtx,unit>) -> StringAllocatorReader<'TCtx,unit>
  member Combine : f:StringAllocatorReader<'TCtx,unit> * g:StringAllocatorReader<'TCtx,unit> -> StringAllocatorReader<'TCtx,unit>
  member Delay : f:(unit -> StringAllocatorReader<'TCtx,unit>) -> StringAllocatorReader<'TCtx,unit>
  member Yield : ch:char -> StringAllocatorReader<'b,unit>
  member Yield : str:string -> StringAllocatorReader<'a,unit>
  member Zero : unit -> StringAllocatorReader<'c,unit>

--------------------
new : unit -> StringAllocator
val this : StringAllocator
val f : StringAllocatorReader<'TCtx,unit>
type unit = Unit
val g : StringAllocatorReader<'TCtx,unit>
val chars : Span<char>
val state : byref<StringAllocatorState<'TCtx>>
val f : (unit -> StringAllocatorReader<'TCtx,unit>)
val reader : StringAllocatorReader<'TCtx,'TRet>
val cont : ('TRet -> StringAllocatorReader<'TCtx,unit>)
val ret : 'TRet
val ch : char
val state : byref<StringAllocatorState<'b>>
val str : string
Multiple items
val string : value:'T -> string

--------------------
type string = String
val stringAlloc : StringAllocator
val run : length:int -> ctx:'TCtx -> action:StringAllocatorReader<'TCtx,unit> -> string
val length : int
val ctx : 'TCtx
val action : StringAllocatorReader<'TCtx,unit>
Multiple items
type String =
  new : value:char[] -> string + 8 overloads
  member Chars : int -> char
  member Clone : unit -> obj
  member CompareTo : value:obj -> int + 1 overload
  member Contains : value:string -> bool + 3 overloads
  member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
  member EndsWith : value:string -> bool + 3 overloads
  member EnumerateRunes : unit -> StringRuneEnumerator
  member Equals : obj:obj -> bool + 2 overloads
  member GetEnumerator : unit -> CharEnumerator
  ...

--------------------
String(value: char []) : String
String(value: nativeptr<char>) : String
String(value: nativeptr<sbyte>) : String
String(value: ReadOnlySpan<char>) : String
String(c: char, count: int) : String
String(value: char [], startIndex: int, length: int) : String
String(value: nativeptr<char>, startIndex: int, length: int) : String
String(value: nativeptr<sbyte>, startIndex: int, length: int) : String
String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: Text.Encoding) : String
String.Create<'TState>(length: int, state: 'TState, action: Buffers.SpanAction<char,'TState>) : string
namespace System.Buffers
type SpanAction<'T,'TArg> =
  delegate of Span<'T> * 'TArg -> unit
[<Struct>]
val mutable state : StringAllocatorState<'TCtx>
val s : string
val xs : string []
type Console =
  static member BackgroundColor : ConsoleColor with get, set
  static member Beep : unit -> unit + 1 overload
  static member BufferHeight : int with get, set
  static member BufferWidth : int with get, set
  static member CapsLock : bool
  static member Clear : unit -> unit
  static member CursorLeft : int with get, set
  static member CursorSize : int with get, set
  static member CursorTop : int with get, set
  static member CursorVisible : bool with get, set
  ...
Console.WriteLine() : unit
   (+0 other overloads)
Console.WriteLine(value: string) : unit
   (+0 other overloads)
Console.WriteLine(value: obj) : unit
   (+0 other overloads)
Console.WriteLine(value: uint64) : unit
   (+0 other overloads)
Console.WriteLine(value: int64) : unit
   (+0 other overloads)
Console.WriteLine(value: uint32) : unit
   (+0 other overloads)
Console.WriteLine(value: int) : unit
   (+0 other overloads)
Console.WriteLine(value: float32) : unit
   (+0 other overloads)
Console.WriteLine(value: float) : unit
   (+0 other overloads)
Console.WriteLine(value: decimal) : unit
   (+0 other overloads)
Raw view Test code New version

More information

Link:http://fssnip.net/86b
Posted:7 months ago
Author:Evgeniy Andreev
Tags: computation expressions , string , stringbuilder