Using Span<T> In F#

I've covered the motivation behind the usage of Span<T> in my blog. To make a long story short, it's an easy way to reduce some heap allocations without sacrificing code readability.
 
At some point, I've decided to check how Span<T> is supported in F# which I'm a huge believer in.
 
In the example code, I've covered the conversion of Linux permissions into octal representation. Here's the code to recap what's happening 
  1. internal ref struct SymbolicPermission  
  2. {  
  3.     private struct PermissionInfo  
  4.     {  
  5.         public int Value { getset; }  
  6.         public char Symbol { getset; }  
  7.     }  
  8.   
  9.     private const int BlockCount = 3;  
  10.     private const int BlockLength = 3;  
  11.     private const int MissingPermissionSymbol = '-';  
  12.   
  13.     //I could smh decuce value from the position  
  14.     //Since values are powers of 2  
  15.     //But I think that such dictionary  
  16.     //Allows better to capture domain knowledge  
  17.     private readonly static Dictionary<int, PermissionInfo> Permissions = new Dictionary<int, PermissionInfo>() {  
  18.             {0, new PermissionInfo {  
  19.                 Symbol = 'r',  
  20.                 Value = 4  
  21.             } },  
  22.             {1, new PermissionInfo {  
  23.                 Symbol = 'w',  
  24.                 Value = 2  
  25.             }},  
  26.             {2, new PermissionInfo {  
  27.                 Symbol = 'x',  
  28.                 Value = 1  
  29.             }}};  
  30.   
  31.     private ReadOnlySpan<char> _value;  
  32.   
  33.     private SymbolicPermission(string value)  
  34.     {  
  35.         _value = value;  
  36.     }  
  37.   
  38.     public static SymbolicPermission Parse(string input)  
  39.     {  
  40.         if (input.Length != BlockCount * BlockLength)  
  41.         {  
  42.             throw new ArgumentException("input should be a string 3 blocks of 3 characters each");  
  43.         }  
  44.         for (var i = 0; i < input.Length; i++)  
  45.         {  
  46.             TestCharForValidity(input, i);  
  47.         }  
  48.   
  49.         return new SymbolicPermission(input);  
  50.     }  
  51.   
  52.     public int GetOctalRepresentation()  
  53.     {  
  54.         var res = 0;  
  55.         for (var i = 0; i < BlockCount; i++)  
  56.         {  
  57.             var block = GetBlock(i);  
  58.             res += ConvertBlockToOctal(block) * (int)Math.Pow(10, BlockCount - i - 1);  
  59.         }  
  60.         return res;  
  61.     }  
  62.   
  63.     private static void TestCharForValidity(string input, int position)  
  64.     {  
  65.         var index = position % BlockLength;  
  66.         var expectedPermission = Permissions[index];  
  67.         var symbolToTest = input[position];  
  68.         if (symbolToTest != expectedPermission.Symbol && symbolToTest != MissingPermissionSymbol)  
  69.         {  
  70.             throw new ArgumentException($"invalid input in position {position}");  
  71.         }  
  72.     }  
  73.   
  74.     private ReadOnlySpan<char> GetBlock(int blockNumber)  
  75.     {  
  76.         return _value.Slice(blockNumber * BlockLength, BlockLength);  
  77.     }  
  78.   
  79.     private int ConvertBlockToOctal(ReadOnlySpan<char> block)  
  80.     {  
  81.         var res = 0;  
  82.         foreach (var (index, permission) in Permissions)  
  83.         {  
  84.             var actualValue = block[index];  
  85.             if (actualValue == permission.Symbol)  
  86.             {  
  87.                 res += permission.Value;  
  88.             }  
  89.         }  
  90.         //I guess name of the method suggests   
  91.         //that I shoud play with base of 8  
  92.         //but since requirements don't explictly state that  
  93.         //I think base of 10 is good enough :)  
  94.         return res;  
  95.     }  
  96. }  
  97.   
  98. public static class SymbolicUtils  
  99. {  
  100.     public static int SymbolicToOctal(string input)  
  101.     {  
  102.         var permission = SymbolicPermission.Parse(input);  
  103.         return permission.GetOctalRepresentation();  
  104.     }  
  105. }  
Which is called like this:
  1. var result = SymbolicUtils.SymbolicToOctal("rwxr-x-w-");  
So let's now jump to F#. We'll declare Helpers type which will calculate octal representation instead of C# code.
  1. [<Struct>]  
  2. type PermissionInfo(symbol: char, value: int) =  
  3.     member x.Symbol = symbol  
  4.     member x.Value = value  
  5.   
  6. type Helpers =  
  7.     val private Permissions : PermissionInfo[]  
  8.     new () = {  
  9.         Permissions =  
  10.         [|PermissionInfo('r', 4);  
  11.         PermissionInfo('w', 2);  
  12.         PermissionInfo('x', 1); |]  
  13.     }  
  14.   
  15.     member x.ConvertBlockToOctal (block : ReadOnlySpan<char>) =  
  16.         let mutable acc = 0  
  17.         for i = 0 to x.Permissions.Length - 1 do  
  18.             if block.[i] = x.Permissions.[i].Symbol then  
  19.                 acc <- acc + x.Permissions.[i].Value  
  20.             else  
  21.                 acc <- acc  
  22.         acc  
One notable point here is that Permissions array is marked as val. As documentation states it allows declaring a location to store a value in a class or structure type, without initializing it. 
 
Calling it in C# is seamless.
  1. var block = GetBlock(i);  
  2. res += new Helpers().ConvertBlockToOctal(block) * (int)Math.Pow(10, BlockCount - i - 1);  
Here are the benchmarks
 
Using Span<T /> In F#
 
Although the  F# version allocates more memory execution, the time difference is quite impressive. 
 
So as we can see F# keeps up with C# and supports new features quite well.