Actions
This commit is contained in:
parent
08c208cc46
commit
81d7fbcffa
@ -0,0 +1,59 @@
|
|||||||
|
package hep.dataforge.data
|
||||||
|
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple data transformation on a data node
|
||||||
|
*/
|
||||||
|
interface Action<in T : Any, out R : Any> {
|
||||||
|
/**
|
||||||
|
* Transform the data in the node, producing a new node. By default it is assumed that all calculations are lazy
|
||||||
|
* so not actual computation is started at this moment
|
||||||
|
*/
|
||||||
|
operator fun invoke(node: DataNode<T>, meta: Meta): DataNode<R>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminal action is the one that could not be invoked lazily and requires some kind of blocking computation to invoke
|
||||||
|
*/
|
||||||
|
val isTerminal: Boolean get() = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action composition. The result is terminal if one of parts is terminal
|
||||||
|
*/
|
||||||
|
infix fun <T : Any, I : Any, R : Any> Action<T, I>.then(action: Action<I, R>): Action<T, R> {
|
||||||
|
return object : Action<T, R> {
|
||||||
|
override fun invoke(node: DataNode<T>, meta: Meta): DataNode<R> {
|
||||||
|
return action(this@then.invoke(node, meta), meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isTerminal: Boolean
|
||||||
|
get() = this@then.isTerminal || action.isTerminal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An action that performs the same transformation on each of input data nodes. Null results are ignored.
|
||||||
|
*/
|
||||||
|
class PipeAction<in T : Any, out R : Any>(val transform: (Name, Data<T>, Meta) -> Data<R>?) : Action<T, R> {
|
||||||
|
override fun invoke(node: DataNode<T>, meta: Meta): DataNode<R> = DataNode.build {
|
||||||
|
node.asSequence().forEach { (name, data) ->
|
||||||
|
val res = transform(name, data, meta)
|
||||||
|
if (res != null) {
|
||||||
|
set(name, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* A simple pipe that performs transformation on the data and copies input meta into the output
|
||||||
|
*/
|
||||||
|
fun <T : Any, R : Any> simple(transform: suspend (Name, T, Meta) -> R) = PipeAction { name, data: Data<T>, meta ->
|
||||||
|
val goal = data.goal.pipe { transform(name, it, meta) }
|
||||||
|
return@PipeAction Data.of(goal, data.meta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,34 +7,47 @@ import hep.dataforge.names.NameToken
|
|||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A data element characterized by its meta
|
* A data element characterized by its meta
|
||||||
*/
|
*/
|
||||||
interface Data<out T> : MetaRepr {
|
interface Data<out T : Any> : MetaRepr {
|
||||||
|
/**
|
||||||
|
* Type marker for the data. The type is known before the calculation takes place so it could be cheched.
|
||||||
|
*/
|
||||||
|
val type: KClass<out T>
|
||||||
|
/**
|
||||||
|
* Meta for the data
|
||||||
|
*/
|
||||||
val meta: Meta
|
val meta: Meta
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazy data value
|
||||||
|
*/
|
||||||
val goal: Goal<T>
|
val goal: Goal<T>
|
||||||
|
|
||||||
override fun toMeta(): Meta = meta
|
override fun toMeta(): Meta = meta
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun <T> of(meta: Meta, goal: Goal<T>): Data<T> = DataImpl(meta, goal)
|
fun <T : Any> of(type: KClass<out T>, goal: Goal<T>, meta: Meta): Data<T> = DataImpl(type, goal, meta)
|
||||||
fun <T> of(name: String, meta: Meta, goal: Goal<T>): Data<T> = NamedData(name, of(meta, goal))
|
inline fun <reified T : Any> of(goal: Goal<T>, meta: Meta): Data<T> = of(T::class, goal, meta)
|
||||||
fun <T> static(context: CoroutineContext, meta: Meta, value: T): Data<T> = DataImpl(meta, Goal.static(context, value))
|
fun <T : Any> of(name: String, goal: Goal<T>, meta: Meta): Data<T> = NamedData(name, of(goal, meta))
|
||||||
|
fun <T : Any> static(context: CoroutineContext, value: T, meta: Meta): Data<T> = DataImpl(value::class, Goal.static(context, value), meta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic Data implementation
|
* Generic Data implementation
|
||||||
*/
|
*/
|
||||||
private class DataImpl<out T>(override val meta: Meta, override val goal: Goal<T>) : Data<T>
|
private class DataImpl<out T : Any>(override val type: KClass<out T>, override val goal: Goal<T>, override val meta: Meta) : Data<T>
|
||||||
|
|
||||||
class NamedData<out T>(val name: String, data: Data<T>) : Data<T> by data
|
class NamedData<out T : Any>(val name: String, data: Data<T>) : Data<T> by data
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A tree-like data structure grouped into the node. All data inside the node must inherit its type
|
* A tree-like data structure grouped into the node. All data inside the node must inherit its type
|
||||||
*/
|
*/
|
||||||
interface DataNode<out T> {
|
interface DataNode<out T : Any> {
|
||||||
/**
|
/**
|
||||||
* Get the specific data if it exists
|
* Get the specific data if it exists
|
||||||
*/
|
*/
|
||||||
@ -53,17 +66,17 @@ interface DataNode<out T> {
|
|||||||
operator fun iterator(): Iterator<Pair<Name, Data<T>>> = asSequence().iterator()
|
operator fun iterator(): Iterator<Pair<Name, Data<T>>> = asSequence().iterator()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun <T> build(block: DataTreeBuilder<T>.() -> Unit) = DataTreeBuilder<T>().apply(block).build()
|
fun <T : Any> build(block: DataTreeBuilder<T>.() -> Unit) = DataTreeBuilder<T>().apply(block).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class DataTreeItem<out T> {
|
internal sealed class DataTreeItem<out T : Any> {
|
||||||
class Node<out T>(val tree: DataTree<T>) : DataTreeItem<T>()
|
class Node<out T : Any>(val tree: DataTree<T>) : DataTreeItem<T>()
|
||||||
class Value<out T>(val value: Data<T>) : DataTreeItem<T>()
|
class Value<out T : Any>(val value: Data<T>) : DataTreeItem<T>()
|
||||||
}
|
}
|
||||||
|
|
||||||
class DataTree<out T> internal constructor(private val items: Map<NameToken, DataTreeItem<T>>) : DataNode<T> {
|
class DataTree<out T : Any> internal constructor(private val items: Map<NameToken, DataTreeItem<T>>) : DataNode<T> {
|
||||||
//TODO add node-level meta?
|
//TODO add node-level meta?
|
||||||
|
|
||||||
override fun get(name: Name): Data<T>? = when (name.length) {
|
override fun get(name: Name): Data<T>? = when (name.length) {
|
||||||
@ -93,15 +106,15 @@ class DataTree<out T> internal constructor(private val items: Map<NameToken, Dat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class DataTreeBuilderItem<out T> {
|
private sealed class DataTreeBuilderItem<out T : Any> {
|
||||||
class Node<T>(val tree: DataTreeBuilder<T>) : DataTreeBuilderItem<T>()
|
class Node<T : Any>(val tree: DataTreeBuilder<T>) : DataTreeBuilderItem<T>()
|
||||||
class Value<T>(val value: Data<T>) : DataTreeBuilderItem<T>()
|
class Value<T : Any>(val value: Data<T>) : DataTreeBuilderItem<T>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A builder for a DataTree.
|
* A builder for a DataTree.
|
||||||
*/
|
*/
|
||||||
class DataTreeBuilder<T> {
|
class DataTreeBuilder<T : Any> {
|
||||||
private val map = HashMap<NameToken, DataTreeBuilderItem<T>>()
|
private val map = HashMap<NameToken, DataTreeBuilderItem<T>>()
|
||||||
|
|
||||||
operator fun set(token: NameToken, node: DataTreeBuilder<T>) {
|
operator fun set(token: NameToken, node: DataTreeBuilder<T>) {
|
||||||
@ -116,7 +129,7 @@ class DataTreeBuilder<T> {
|
|||||||
|
|
||||||
private fun buildNode(token: NameToken): DataTreeBuilder<T> {
|
private fun buildNode(token: NameToken): DataTreeBuilder<T> {
|
||||||
return if (!map.containsKey(token)) {
|
return if (!map.containsKey(token)) {
|
||||||
DataTreeBuilder<T>().also { map.put(token, DataTreeBuilderItem.Node(it)) }
|
DataTreeBuilder<T>().also { map[token] = DataTreeBuilderItem.Node(it) }
|
||||||
} else {
|
} else {
|
||||||
(map[token] as? DataTreeBuilderItem.Node ?: error("The node with name $token is occupied by leaf")).tree
|
(map[token] as? DataTreeBuilderItem.Node ?: error("The node with name $token is occupied by leaf")).tree
|
||||||
}
|
}
|
||||||
@ -177,11 +190,21 @@ class DataTreeBuilder<T> {
|
|||||||
/**
|
/**
|
||||||
* Generate a mutable builder from this node. Node content is not changed
|
* Generate a mutable builder from this node. Node content is not changed
|
||||||
*/
|
*/
|
||||||
fun <T> DataNode<T>.builder(): DataTreeBuilder<T> = DataTreeBuilder<T>().apply {
|
fun <T : Any> DataNode<T>.builder(): DataTreeBuilder<T> = DataTreeBuilder<T>().apply {
|
||||||
asSequence().forEach { (name, data) -> this[name] = data }
|
asSequence().forEach { (name, data) -> this[name] = data }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start computation for all goals in data node
|
* Start computation for all goals in data node
|
||||||
*/
|
*/
|
||||||
fun DataNode<*>.startAll() = asSequence().forEach { (_,data)->data.goal.start() }
|
fun DataNode<*>.startAll() = asSequence().forEach { (_, data) -> data.goal.start() }
|
||||||
|
|
||||||
|
fun <T : Any> DataNode<T>.filter(predicate: (Name, Data<T>) -> Boolean): DataNode<T> = DataNode.build {
|
||||||
|
asSequence().forEach {(name, data)->
|
||||||
|
if(predicate(name,data)){
|
||||||
|
this[name] = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//fun <T : Any, R: T> DataNode<T>.filterIsInstance(type: KClass<R>): DataNode<R> = filter{_,data -> type.}
|
Loading…
Reference in New Issue
Block a user