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.toName
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Lazy data value
|
||||
*/
|
||||
val goal: Goal<T>
|
||||
|
||||
override fun toMeta(): Meta = meta
|
||||
|
||||
companion object {
|
||||
fun <T> of(meta: Meta, goal: Goal<T>): Data<T> = DataImpl(meta, goal)
|
||||
fun <T> of(name: String, meta: Meta, goal: Goal<T>): Data<T> = NamedData(name, of(meta, goal))
|
||||
fun <T> static(context: CoroutineContext, meta: Meta, value: T): Data<T> = DataImpl(meta, Goal.static(context, value))
|
||||
fun <T : Any> of(type: KClass<out T>, goal: Goal<T>, meta: Meta): Data<T> = DataImpl(type, goal, meta)
|
||||
inline fun <reified T : Any> of(goal: Goal<T>, meta: Meta): Data<T> = of(T::class, goal, meta)
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
interface DataNode<out T> {
|
||||
interface DataNode<out T : Any> {
|
||||
/**
|
||||
* 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()
|
||||
|
||||
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> {
|
||||
class Node<out T>(val tree: DataTree<T>) : DataTreeItem<T>()
|
||||
class Value<out T>(val value: Data<T>) : DataTreeItem<T>()
|
||||
internal sealed class DataTreeItem<out T : Any> {
|
||||
class Node<out T : Any>(val tree: DataTree<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?
|
||||
|
||||
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> {
|
||||
class Node<T>(val tree: DataTreeBuilder<T>) : DataTreeBuilderItem<T>()
|
||||
class Value<T>(val value: Data<T>) : DataTreeBuilderItem<T>()
|
||||
private sealed class DataTreeBuilderItem<out T : Any> {
|
||||
class Node<T : Any>(val tree: DataTreeBuilder<T>) : DataTreeBuilderItem<T>()
|
||||
class Value<T : Any>(val value: Data<T>) : DataTreeBuilderItem<T>()
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for a DataTree.
|
||||
*/
|
||||
class DataTreeBuilder<T> {
|
||||
class DataTreeBuilder<T : Any> {
|
||||
private val map = HashMap<NameToken, DataTreeBuilderItem<T>>()
|
||||
|
||||
operator fun set(token: NameToken, node: DataTreeBuilder<T>) {
|
||||
@ -116,7 +129,7 @@ class DataTreeBuilder<T> {
|
||||
|
||||
private fun buildNode(token: NameToken): DataTreeBuilder<T> {
|
||||
return if (!map.containsKey(token)) {
|
||||
DataTreeBuilder<T>().also { map.put(token, DataTreeBuilderItem.Node(it)) }
|
||||
DataTreeBuilder<T>().also { map[token] = DataTreeBuilderItem.Node(it) }
|
||||
} else {
|
||||
(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
|
||||
*/
|
||||
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 }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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