Adding typed Table<'r> which stores typed rows of the table and computes columns on demand.

This commit is contained in:
Dmitry Voytsekhovskiy 2016-03-15 17:35:42 +03:00
Родитель c07196c05e
Коммит 6bc773f4e7
4 изменённых файлов: 75 добавлений и 55 удалений

Просмотреть файл

@ -26,6 +26,8 @@ let ``Combination of functions ToRows and OfRows returns a record array identica
let table = Table.OfRows p
let rows = table.ToRows<ValidTypesRec>() |> Seq.toArray
Assert.AreEqual(p, rows)
Assert.AreEqual(p, table.Rows |> Seq.toArray)
Assert.AreEqual(table.RowsCount, table.Rows.Length)
[<Test; Category("CI")>]
@ -60,6 +62,8 @@ let ``Table.OfRows for a non-record``() =
Assert.AreEqual(2, t.RowsCount, "rows count")
Assert.AreEqual("A", t.[0].Rows.[0].AsString, "0,0")
Assert.AreEqual("B", t.["Prop"].Rows.[1].AsString, "0,1")
Assert.AreEqual("A", t.Rows.[0].Prop)
Assert.AreEqual("B", t.Rows.[1].Prop)
[<Test; Category("CI"); ExpectedException(typeof<System.Collections.Generic.KeyNotFoundException>)>]
let ``Table.ToRows fails when table has no column for a property``() =

Просмотреть файл

@ -120,9 +120,9 @@ type Column private (name:string, values: ColumnValues, height: int) =
| t -> raise (new NotSupportedException(sprintf "Type '%A' is not a valid column type" t))
Column(name, values, count)
type Table private (columns : Column list, height : int) =
static let emptyTable = new Table(List.Empty)
static let raiseDiffHeights() = invalidOp("Given columns are of different heights")
type Table internal (columns : Column list, height : int) =
static let emptyTable : Table = Table(List.Empty, 0)
static let assertAndGetHeight (columns:Column list) =
match columns with
| [] -> 0
@ -155,11 +155,39 @@ type Table private (columns : Column list, height : int) =
member x.GetEnumerator() : IEnumerator<Column> = (columns |> Seq.ofList).GetEnumerator()
member x.GetEnumerator() : System.Collections.IEnumerator = ((columns |> Seq.ofList) :> System.Collections.IEnumerable).GetEnumerator()
member x.ToRows<'r>() : 'r seq =
abstract ToRows<'r> : unit -> 'r seq
default x.ToRows<'r>() : 'r seq = Table.ToRows x
override x.ToString() = String.Join("\n", columns |> Seq.map (fun c -> c.ToString()))
static member OfColumns (columns: Column seq) : Table = Table(columns)
static member OfRows<'r> (rows : 'r seq) : Table<'r> = Table<'r>(rows |> ImmutableArray.CreateRange)
static member OfRows<'r> (rows : ImmutableArray<'r>) : Table<'r> = Table<'r>(rows)
static member internal ColumnsOfRows<'r>(rows : ImmutableArray<'r>) : Column seq =
let typeR = typeof<'r>
let n = rows.Length
let props =
match typeR.GetCustomAttributes(typeof<CompilationMappingAttribute>, false)
|> Seq.exists(fun attr -> (attr :?> CompilationMappingAttribute).SourceConstructFlags = SourceConstructFlags.RecordType) with
| true -> // F# record
Util.getRecordProperties typeR
| false -> // non-record
typeR.GetProperties(Reflection.BindingFlags.Instance ||| Reflection.BindingFlags.Public) |> Seq.filter (fun p -> p.CanRead)
props |> Seq.map(fun p ->
match p.PropertyType with
| t when t = typeof<float> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,float>(rows, p), n)
| t when t = typeof<int> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,int>(rows, p), n)
| t when t = typeof<string> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,string>(rows, p), n)
| t when t = typeof<DateTime> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,DateTime>(rows, p), n)
| t when t = typeof<bool> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,bool>(rows, p), n)
| t -> invalidArg "rows" (sprintf "Property '%s' has type '%A' which is not a valid table column type" p.Name t))
static member internal ToRows<'r>(t: Table) : 'r seq =
let typeR = typeof<'r>
let d_createR, props =
match typeR.GetCustomAttributes(typeof<CompilationMappingAttribute>, false)
|> Seq.exists(fun attr -> (attr :?> CompilationMappingAttribute).SourceConstructFlags = SourceConstructFlags.RecordType) with
|> Seq.exists(fun attr -> (attr :?> CompilationMappingAttribute).SourceConstructFlags = SourceConstructFlags.RecordType) with
| true -> // F# record
let props = Util.getRecordProperties typeR |> Seq.map(fun p -> p.Name, p.PropertyType) |> Seq.toArray
let ctor = typeR.GetConstructor(props |> Array.map snd)
@ -177,39 +205,12 @@ type Table private (columns : Column list, height : int) =
Expression.Block(
[l_r],
Seq.concat
[seq{ yield Expression.Assign(l_r, Expression.New(ctor)) :> Expression }
Seq.zip props l_pars |> Seq.map(fun (p, l_p) -> Expression.Assign(Expression.Property(l_r, p), l_p) :> Expression)
seq{ yield upcast l_r }]),
[ seq{ yield Expression.Assign(l_r, Expression.New(ctor)) :> Expression }
Seq.zip props l_pars |> Seq.map(fun (p, l_p) -> Expression.Assign(Expression.Property(l_r, p), l_p) :> Expression)
seq{ yield upcast l_r }]),
l_pars)
l.Compile(), props |> Array.map(fun p -> p.Name)
Table.Mapd (props |> Array.map (fun name -> x.[name])) d_createR x
override x.ToString() = String.Join("\n", columns |> Seq.map (fun c -> c.ToString()))
static member OfColumns (columns: Column seq) = Table(columns)
static member OfRows<'r>(rows : 'r seq) =
let typeR = typeof<'r>
let rows_a = rows |> Seq.toArray
let n = rows_a.Length
let props =
match typeR.GetCustomAttributes(typeof<CompilationMappingAttribute>, false)
|> Seq.exists(fun attr -> (attr :?> CompilationMappingAttribute).SourceConstructFlags = SourceConstructFlags.RecordType) with
| true -> // F# record
Util.getRecordProperties typeR
| false -> // non-record
typeR.GetProperties(Reflection.BindingFlags.Instance ||| Reflection.BindingFlags.Public) |> Seq.filter (fun p -> p.CanRead)
let columns =
props
|> Seq.map(fun p ->
match p.PropertyType with
| t when t = typeof<float> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,float>(rows_a, p), n)
| t when t = typeof<int> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,int>(rows_a, p), n)
| t when t = typeof<string> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,string>(rows_a, p), n)
| t when t = typeof<DateTime> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,DateTime>(rows_a, p), n)
| t when t = typeof<bool> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,bool>(rows_a, p), n)
| t -> invalidArg "rows" (sprintf "Property '%s' has type '%A' which is not a valid table column type" p.Name t))
Table(columns)
Table.Mapd (props |> Array.map (fun name -> t.[name])) d_createR t
static member Empty: Table = emptyTable
@ -339,7 +340,6 @@ type Table private (columns : Column list, height : int) =
let deleg = Funcs.toDelegate transform
let colArrays = cs |> Array.map(fun n -> table.[n].Rows.ToUntypedList() |> box)
deleg.DynamicInvoke(colArrays) :?> 'c
static member Load (reader:System.IO.TextReader, settings:Angara.Data.DelimitedFile.ReadSettings) : Table =
let cols =
@ -352,7 +352,7 @@ type Table private (columns : Column list, height : int) =
| Angara.Data.DelimitedFile.ColumnType.Boolean -> Column.OfArray (schema.Name, data :?> ImmutableArray<bool>)
| Angara.Data.DelimitedFile.ColumnType.DateTime -> Column.OfArray (schema.Name, data :?> ImmutableArray<DateTime>)
| Angara.Data.DelimitedFile.ColumnType.String -> Column.OfArray (schema.Name, data :?> ImmutableArray<string>))
new Table(cols |> Seq.toList)
Table(cols |> Seq.toList)
static member Load (reader:System.IO.TextReader) : Table =
Table.Load (reader, Angara.Data.DelimitedFile.ReadSettings.Default)
static member Load (path: string, settings:Angara.Data.DelimitedFile.ReadSettings) : Table =
@ -371,4 +371,19 @@ type Table private (columns : Column list, height : int) =
use writer = System.IO.File.CreateText path
Table.Save (table, writer, settings)
static member Save (table:Table, path: string) : unit =
Table.Save (table, path, Angara.Data.DelimitedFile.WriteSettings.Default)
Table.Save (table, path, Angara.Data.DelimitedFile.WriteSettings.Default)
and Table<'r>(rows : ImmutableArray<'r>) =
inherit Table(Table.ColumnsOfRows rows |> Seq.toList, rows.Length)
member x.Rows : ImmutableArray<'r> = rows
override x.ToRows<'s>() : 's seq =
// Implementation issue: cannot invoke base.ToRows<'s>() here because of undocumented (?) F# compiler property;
// if I don't use the mutual types recursion (i.e. "and Table<'r>"), "base.ToRows" works well here; but then I cannot
// create Table<'r> instances from the static methods "OfRows" of the Table type.
// That's why I call static method Table.ToRows<'s>() instead of base.ToRows<'s>().
match typeof<'s> with
| t when t = typeof<'r> -> rows |> coerce
| _ -> Table.ToRows<'s>(x)

Просмотреть файл

@ -38,8 +38,7 @@ type ColumnValues =
member Item : rowIndex:int -> DataValue
/// Represents a table column which is a pair of column name and an immutable array of one of the supported types.
[<Class>]
type Column =
type [<Class>] Column =
member Name : string with get
/// Returns column values.
member Rows : ColumnValues with get
@ -54,8 +53,7 @@ type Column =
/// Represents a table wich is an immutable list of named columns.
/// The type is thread safe.
[<Class>]
type Table =
type [<Class>] Table =
interface IEnumerable<Column>
/// Gets a count of the total number of columns in the table.
@ -84,7 +82,7 @@ type Table =
/// The method uses reflection to build instances of `'r` from the table columns:
/// - If `'r` is F# record, then for each property of the type there must be a corresponding column of identical type.
/// - Otherwise, then `'r` has default constructor and for each public writable property there must be a column of same name and type as the property.
member ToRows<'r> : unit -> 'r seq
abstract ToRows<'r> : unit -> 'r seq
/// Builds a table from a finite sequence of columns.
/// All given columns must be of same height.
@ -97,7 +95,13 @@ type Table =
/// each table row corresponds to an element of the input sequence with the order respected.
/// If the type `'r` is an F# record, the order of columns is identical to the record properties order.
/// If there is a public property having a type that is not valid for a table column, the function fails with an exception.
static member OfRows<'r> : 'r seq -> Table
static member OfRows<'r> : 'r seq -> Table<'r>
/// Builds a table such that each public property of a given type `'r`
/// becomes the table column with the name and type identical to the property;
/// each table row corresponds to an element of the input sequence with the order respected.
/// If the type `'r` is an F# record, the order of columns is identical to the record properties order.
/// If there is a public property having a type that is not valid for a table column, the function fails with an exception.
static member OfRows<'r> : ImmutableArray<'r> -> Table<'r>
/// Creates a new, empty table
static member Empty : Table
@ -225,12 +229,7 @@ type Table =
/// Saves the table to a delimited text stream using given writer.
static member Save : table:Table * writer:System.IO.TextWriter * settings:Angara.Data.DelimitedFile.WriteSettings -> unit
//[<Class>]
//type Table<'r> =
// inherit Table
//
// new : rows : 'r seq -> Table<'r>
//
// member Rows : ImmutableArray<'r>
and [<Class>] Table<'r> =
inherit Table
new : rows:ImmutableArray<'r> -> Table<'r>
member Rows : ImmutableArray<'r>

Просмотреть файл

@ -28,6 +28,8 @@ let inline invalidCast message = raise (new System.InvalidCastException(message)
let inline notFound message = raise (new System.Collections.Generic.KeyNotFoundException(message))
let inline raiseDiffHeights() = invalidOp("Given columns are of different heights")
let getRecordProperties (typeR : System.Type) =
typeR.GetProperties()
|> Seq.choose(fun p ->
@ -37,7 +39,7 @@ let getRecordProperties (typeR : System.Type) =
|> Seq.sortBy snd
|> Seq.map fst
let arrayOfProp<'r,'p> (rows: 'r[], p:System.Reflection.PropertyInfo) =
let arrayOfProp<'r,'p> (rows: ImmutableArray<'r>, p:System.Reflection.PropertyInfo) =
lazy(
let n = rows.Length
let bld = ImmutableArray.CreateBuilder<'p>(n)