From 81d7fbcffa76479ed1e919f41e1a9af11459c1c1 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 16 Dec 2018 19:37:11 +0300 Subject: [PATCH] Actions --- .../kotlin/hep/dataforge/data/Action.kt | 59 ++++++++++++++++++ .../kotlin/hep/dataforge/data/Data.kt | 61 +++++++++++++------ 2 files changed, 101 insertions(+), 19 deletions(-) create mode 100644 dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Action.kt diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Action.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Action.kt new file mode 100644 index 00000000..e4b35e39 --- /dev/null +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Action.kt @@ -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 { + /** + * 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, meta: Meta): DataNode + + /** + * 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 Action.then(action: Action): Action { + return object : Action { + override fun invoke(node: DataNode, meta: Meta): DataNode { + 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(val transform: (Name, Data, Meta) -> Data?) : Action { + override fun invoke(node: DataNode, meta: Meta): DataNode = 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 simple(transform: suspend (Name, T, Meta) -> R) = PipeAction { name, data: Data, meta -> + val goal = data.goal.pipe { transform(name, it, meta) } + return@PipeAction Data.of(goal, data.meta) + } + } +} + diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt index b4f95f05..14f98651 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt @@ -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 : MetaRepr { +interface Data : MetaRepr { + /** + * Type marker for the data. The type is known before the calculation takes place so it could be cheched. + */ + val type: KClass + /** + * Meta for the data + */ val meta: Meta + + /** + * Lazy data value + */ val goal: Goal override fun toMeta(): Meta = meta companion object { - fun of(meta: Meta, goal: Goal): Data = DataImpl(meta, goal) - fun of(name: String, meta: Meta, goal: Goal): Data = NamedData(name, of(meta, goal)) - fun static(context: CoroutineContext, meta: Meta, value: T): Data = DataImpl(meta, Goal.static(context, value)) + fun of(type: KClass, goal: Goal, meta: Meta): Data = DataImpl(type, goal, meta) + inline fun of(goal: Goal, meta: Meta): Data = of(T::class, goal, meta) + fun of(name: String, goal: Goal, meta: Meta): Data = NamedData(name, of(goal, meta)) + fun static(context: CoroutineContext, value: T, meta: Meta): Data = DataImpl(value::class, Goal.static(context, value), meta) } } /** * Generic Data implementation */ -private class DataImpl(override val meta: Meta, override val goal: Goal) : Data +private class DataImpl(override val type: KClass, override val goal: Goal, override val meta: Meta) : Data -class NamedData(val name: String, data: Data) : Data by data +class NamedData(val name: String, data: Data) : Data by data /** * A tree-like data structure grouped into the node. All data inside the node must inherit its type */ -interface DataNode { +interface DataNode { /** * Get the specific data if it exists */ @@ -53,17 +66,17 @@ interface DataNode { operator fun iterator(): Iterator>> = asSequence().iterator() companion object { - fun build(block: DataTreeBuilder.() -> Unit) = DataTreeBuilder().apply(block).build() + fun build(block: DataTreeBuilder.() -> Unit) = DataTreeBuilder().apply(block).build() } } -internal sealed class DataTreeItem { - class Node(val tree: DataTree) : DataTreeItem() - class Value(val value: Data) : DataTreeItem() +internal sealed class DataTreeItem { + class Node(val tree: DataTree) : DataTreeItem() + class Value(val value: Data) : DataTreeItem() } -class DataTree internal constructor(private val items: Map>) : DataNode { +class DataTree internal constructor(private val items: Map>) : DataNode { //TODO add node-level meta? override fun get(name: Name): Data? = when (name.length) { @@ -93,15 +106,15 @@ class DataTree internal constructor(private val items: Map { - class Node(val tree: DataTreeBuilder) : DataTreeBuilderItem() - class Value(val value: Data) : DataTreeBuilderItem() +private sealed class DataTreeBuilderItem { + class Node(val tree: DataTreeBuilder) : DataTreeBuilderItem() + class Value(val value: Data) : DataTreeBuilderItem() } /** * A builder for a DataTree. */ -class DataTreeBuilder { +class DataTreeBuilder { private val map = HashMap>() operator fun set(token: NameToken, node: DataTreeBuilder) { @@ -116,7 +129,7 @@ class DataTreeBuilder { private fun buildNode(token: NameToken): DataTreeBuilder { return if (!map.containsKey(token)) { - DataTreeBuilder().also { map.put(token, DataTreeBuilderItem.Node(it)) } + DataTreeBuilder().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 { /** * Generate a mutable builder from this node. Node content is not changed */ -fun DataNode.builder(): DataTreeBuilder = DataTreeBuilder().apply { +fun DataNode.builder(): DataTreeBuilder = DataTreeBuilder().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() } \ No newline at end of file +fun DataNode<*>.startAll() = asSequence().forEach { (_, data) -> data.goal.start() } + +fun DataNode.filter(predicate: (Name, Data) -> Boolean): DataNode = DataNode.build { + asSequence().forEach {(name, data)-> + if(predicate(name,data)){ + this[name] = data + } + } +} + +//fun DataNode.filterIsInstance(type: KClass): DataNode = filter{_,data -> type.} \ No newline at end of file