Added strong typing to tasks and task dependencies

This commit is contained in:
Alexander Nozik 2019-09-14 10:36:47 +03:00
parent a0abb99d88
commit c175dc7de4
13 changed files with 180 additions and 122 deletions
dataforge-data/src
commonMain/kotlin/hep/dataforge/data
jvmMain/kotlin/hep/dataforge/data
dataforge-workspace/src
commonMain/kotlin/hep/dataforge/workspace
jvmTest/kotlin/hep/dataforge/workspace

@ -1,8 +1,6 @@
package hep.dataforge.data package hep.dataforge.data
import hep.dataforge.meta.Meta import hep.dataforge.meta.*
import hep.dataforge.meta.MetaRepr
import hep.dataforge.meta.buildMeta
import hep.dataforge.names.* import hep.dataforge.names.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -18,20 +16,20 @@ sealed class DataItem<out T : Any> : MetaRepr {
class Node<out T : Any>(val value: DataNode<T>) : DataItem<T>() { class Node<out T : Any>(val value: DataNode<T>) : DataItem<T>() {
override val type: KClass<out T> get() = value.type override val type: KClass<out T> get() = value.type
override fun toMeta(): Meta = value.toMeta() override fun toMeta(): Meta = value.toMeta()
} }
class Leaf<out T : Any>(val value: Data<T>) : DataItem<T>() { class Leaf<out T : Any>(val value: Data<T>) : DataItem<T>() {
override val type: KClass<out T> get() = value.type override val type: KClass<out T> get() = value.type
override fun toMeta(): Meta = value.toMeta() override fun toMeta(): Meta = value.toMeta()
} }
} }
/** /**
* 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 : Any>: MetaRepr { interface DataNode<out T : Any> : MetaRepr {
/** /**
* The minimal common ancestor to all data in the node * The minimal common ancestor to all data in the node
@ -41,7 +39,7 @@ interface DataNode<out T : Any>: MetaRepr {
val items: Map<NameToken, DataItem<T>> val items: Map<NameToken, DataItem<T>>
override fun toMeta(): Meta = buildMeta { override fun toMeta(): Meta = buildMeta {
"type" to (type.simpleName?:"undefined") "type" to (type.simpleName ?: "undefined")
"items" to { "items" to {
this@DataNode.items.forEach { this@DataNode.items.forEach {
it.key.toString() to it.value.toMeta() it.key.toString() to it.value.toMeta()
@ -52,9 +50,12 @@ interface DataNode<out T : Any>: MetaRepr {
companion object { companion object {
const val TYPE = "dataNode" const val TYPE = "dataNode"
fun <T : Any> build(type: KClass<out T>, block: DataTreeBuilder<T>.() -> Unit) = operator fun <T : Any> invoke(type: KClass<out T>, block: DataTreeBuilder<T>.() -> Unit) =
DataTreeBuilder(type).apply(block).build() DataTreeBuilder(type).apply(block).build()
inline operator fun <reified T : Any> invoke(noinline block: DataTreeBuilder<T>.() -> Unit) =
DataTreeBuilder(T::class).apply(block).build()
fun <T : Any> builder(type: KClass<out T>) = DataTreeBuilder(type) fun <T : Any> builder(type: KClass<out T>) = DataTreeBuilder(type)
} }
} }
@ -142,7 +143,7 @@ private sealed class DataTreeBuilderItem<out T : Any> {
class DataTreeBuilder<T : Any>(private val type: KClass<out T>) { class DataTreeBuilder<T : Any>(private val type: KClass<out T>) {
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<out T>) {
if (map.containsKey(token)) error("Tree entry with name $token is not empty") if (map.containsKey(token)) error("Tree entry with name $token is not empty")
map[token] = DataTreeBuilderItem.Node(node) map[token] = DataTreeBuilderItem.Node(node)
} }
@ -154,9 +155,9 @@ class DataTreeBuilder<T : Any>(private val type: KClass<out 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>(type).also { map[token] = DataTreeBuilderItem.Node(it) } DataTreeBuilder(type).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<T> ?: error("The node with name $token is occupied by leaf")).tree
} }
} }
@ -176,7 +177,7 @@ class DataTreeBuilder<T : Any>(private val type: KClass<out T>) {
} }
} }
operator fun set(name: Name, node: DataTreeBuilder<T>) { operator fun set(name: Name, node: DataTreeBuilder<out T>) {
when (name.length) { when (name.length) {
0 -> error("Can't add data with empty name") 0 -> error("Can't add data with empty name")
1 -> set(name.first()!!, node) 1 -> set(name.first()!!, node)
@ -206,7 +207,7 @@ class DataTreeBuilder<T : Any>(private val type: KClass<out T>) {
/** /**
* Build and append node * Build and append node
*/ */
infix fun String.to(block: DataTreeBuilder<T>.() -> Unit) = set(toName(), DataTreeBuilder<T>(type).apply(block)) infix fun String.to(block: DataTreeBuilder<out T>.() -> Unit) = set(toName(), DataTreeBuilder<T>(type).apply(block))
fun update(node: DataNode<T>) { fun update(node: DataNode<T>) {
@ -227,6 +228,42 @@ class DataTreeBuilder<T : Any>(private val type: KClass<out T>) {
} }
} }
fun <T : Any> DataTreeBuilder<T>.datum(name: Name, data: Data<T>) {
this[name] = data
}
fun <T : Any> DataTreeBuilder<T>.datum(name: String, data: Data<T>) {
this[name.toName()] = data
}
fun <T : Any> DataTreeBuilder<T>.static(name: Name, data: T, meta: Meta = EmptyMeta) {
this[name] = Data.static(data, meta)
}
fun <T : Any> DataTreeBuilder<T>.static(name: Name, data: T, block: MetaBuilder.() -> Unit = {}) {
this[name] = Data.static(data, buildMeta(block))
}
fun <T : Any> DataTreeBuilder<T>.static(name: String, data: T, block: MetaBuilder.() -> Unit = {}) {
this[name.toName()] = Data.static(data, buildMeta(block))
}
fun <T : Any> DataTreeBuilder<T>.node(name: Name, node: DataNode<T>) {
this[name] = node
}
fun <T : Any> DataTreeBuilder<T>.node(name: String, node: DataNode<T>) {
this[name.toName()] = node
}
inline fun <reified T : Any> DataTreeBuilder<T>.node(name: Name, noinline block: DataTreeBuilder<T>.() -> Unit) {
this[name] = DataNode(T::class, block)
}
inline fun <reified T : Any> DataTreeBuilder<T>.node(name: String, noinline block: DataTreeBuilder<T>.() -> Unit) {
this[name.toName()] = DataNode(T::class, block)
}
/** /**
* Generate a mutable builder from this node. Node content is not changed * Generate a mutable builder from this node. Node content is not changed
*/ */
@ -234,7 +271,7 @@ fun <T : Any> DataNode<T>.builder(): DataTreeBuilder<T> = DataTreeBuilder(type).
dataSequence().forEach { (name, data) -> this[name] = data } dataSequence().forEach { (name, data) -> this[name] = data }
} }
fun <T : Any> DataNode<T>.filter(predicate: (Name, Data<T>) -> Boolean): DataNode<T> = DataNode.build(type) { fun <T : Any> DataNode<T>.filter(predicate: (Name, Data<T>) -> Boolean): DataNode<T> = DataNode.invoke(type) {
dataSequence().forEach { (name, data) -> dataSequence().forEach { (name, data) ->
if (predicate(name, data)) { if (predicate(name, data)) {
this[name] = data this[name] = data

@ -74,14 +74,14 @@ class JoinGroupBuilder<T : Any, R : Any>(val actionMeta: Meta) {
* The same rules as for KPipe * The same rules as for KPipe
*/ */
class JoinAction<T : Any, R : Any>( class JoinAction<T : Any, R : Any>(
val inputType: KClass<T>, val inputType: KClass<out T>,
val outputType: KClass<R>, val outputType: KClass<out R>,
private val action: JoinGroupBuilder<T, R>.() -> Unit private val action: JoinGroupBuilder<T, R>.() -> Unit
) : Action<T, R> { ) : Action<T, R> {
override fun invoke(node: DataNode<T>, meta: Meta): DataNode<R> { override fun invoke(node: DataNode<T>, meta: Meta): DataNode<R> {
node.ensureType(inputType) node.ensureType(inputType)
return DataNode.build(outputType) { return DataNode.invoke(outputType) {
JoinGroupBuilder<T, R>(meta).apply(action).buildGroups(node).forEach { group -> JoinGroupBuilder<T, R>(meta).apply(action).buildGroups(node).forEach { group ->
val laminate = Laminate(group.meta, meta) val laminate = Laminate(group.meta, meta)

@ -22,16 +22,16 @@ class PipeBuilder<T, R>(var name: Name, var meta: MetaBuilder) {
} }
class PipeAction<T : Any, R : Any>( class PipeAction<T : Any, out R : Any>(
val inputType: KClass<T>, val inputType: KClass<out T>,
val outputType: KClass<R>, val outputType: KClass<out R>,
private val block: PipeBuilder<T, R>.() -> Unit private val block: PipeBuilder<T, R>.() -> Unit
) : Action<T, R> { ) : Action<T, R> {
override fun invoke(node: DataNode<T>, meta: Meta): DataNode<R> { override fun invoke(node: DataNode<T>, meta: Meta): DataNode<R> {
node.ensureType(inputType) node.ensureType(inputType)
return DataNode.build(outputType) { return DataNode.invoke(outputType) {
node.dataSequence().forEach { (name, data) -> node.dataSequence().forEach { (name, data) ->
//merging data meta with action meta (data meta is primary) //merging data meta with action meta (data meta is primary)
val oldMeta = meta.builder().apply { update(data.meta) } val oldMeta = meta.builder().apply { update(data.meta) }
@ -53,7 +53,7 @@ class PipeAction<T : Any, R : Any>(
inline fun <reified T : Any, reified R : Any> DataNode<T>.pipe( inline fun <reified T : Any, reified R : Any> DataNode<T>.pipe(
meta: Meta, meta: Meta,
noinline action: PipeBuilder<T, R>.() -> Unit noinline action: PipeBuilder<in T, out R>.() -> Unit
): DataNode<R> = PipeAction(T::class, R::class, action).invoke(this, meta) ): DataNode<R> = PipeAction(T::class, R::class, action).invoke(this, meta)

@ -33,15 +33,15 @@ class SplitBuilder<T : Any, R : Any>(val name: Name, val meta: Meta) {
} }
class SplitAction<T : Any, R : Any>( class SplitAction<T : Any, R : Any>(
val inputType: KClass<T>, val inputType: KClass<out T>,
val outputType: KClass<R>, val outputType: KClass<out R>,
private val action: SplitBuilder<T, R>.() -> Unit private val action: SplitBuilder<T, R>.() -> Unit
) : Action<T, R> { ) : Action<T, R> {
override fun invoke(node: DataNode<T>, meta: Meta): DataNode<R> { override fun invoke(node: DataNode<T>, meta: Meta): DataNode<R> {
node.ensureType(inputType) node.ensureType(inputType)
return DataNode.build(outputType) { return DataNode.invoke(outputType) {
node.dataSequence().forEach { (name, data) -> node.dataSequence().forEach { (name, data) ->
val laminate = Laminate(data.meta, meta) val laminate = Laminate(data.meta, meta)

@ -12,12 +12,12 @@ class TypeFilteredDataNode<out T : Any>(val origin: DataNode<*>, override val ty
origin.items.mapNotNull { (key, item) -> origin.items.mapNotNull { (key, item) ->
when (item) { when (item) {
is DataItem.Leaf -> { is DataItem.Leaf -> {
(item.value.withType(type))?.let { (item.value.filterIsInstance(type))?.let {
key to DataItem.Leaf(it) key to DataItem.Leaf(it)
} }
} }
is DataItem.Node -> { is DataItem.Node -> {
key to DataItem.Node(item.value.withType(type)) key to DataItem.Node(item.value.filterIsInstance(type))
} }
} }
}.associate { it } }.associate { it }

@ -22,18 +22,18 @@ actual fun <R : Any> Data<*>.canCast(type: KClass<out R>): Boolean =
/** /**
* Cast the node to given type if the cast is possible or return null * Cast the node to given type if the cast is possible or return null
*/ */
fun <R : Any> Data<*>.withType(type: KClass<out R>): Data<R>? = fun <R : Any> Data<*>.filterIsInstance(type: KClass<out R>): Data<R>? =
if (canCast(type)) cast(type) else null if (canCast(type)) cast(type) else null
/** /**
* Filter a node by data and node type. Resulting node and its subnodes is guaranteed to have border type [type], * Filter a node by data and node type. Resulting node and its subnodes is guaranteed to have border type [type],
* but could contain empty nodes * but could contain empty nodes
*/ */
fun <R : Any> DataNode<*>.withType(type: KClass<out R>): DataNode<R> { fun <R : Any> DataNode<*>.filterIsInstance(type: KClass<out R>): DataNode<R> {
return if (canCast(type)) { return if (canCast(type)) {
cast(type) cast(type)
} else if (this is TypeFilteredDataNode) { } else if (this is TypeFilteredDataNode) {
origin.withType(type) origin.filterIsInstance(type)
} else { } else {
TypeFilteredDataNode(this, type) TypeFilteredDataNode(this, type)
} }
@ -42,10 +42,10 @@ fun <R : Any> DataNode<*>.withType(type: KClass<out R>): DataNode<R> {
/** /**
* Filter all elements of given data item that could be cast to given type. If no elements are available, return null. * Filter all elements of given data item that could be cast to given type. If no elements are available, return null.
*/ */
fun <R : Any> DataItem<*>?.withType(type: KClass<out R>): DataItem<R>? = when (this) { fun <R : Any> DataItem<*>?.filterIsInstance(type: KClass<out R>): DataItem<R>? = when (this) {
null -> null null -> null
is DataItem.Node -> DataItem.Node(this.value.withType(type)) is DataItem.Node -> DataItem.Node(this.value.filterIsInstance(type))
is DataItem.Leaf -> this.value.withType(type)?.let { DataItem.Leaf(it) } is DataItem.Leaf -> this.value.filterIsInstance(type)?.let { DataItem.Leaf(it) }
} }
inline fun <reified R : Any> DataItem<*>?.withType(): DataItem<R>? = this@withType.withType(R::class) inline fun <reified R : Any> DataItem<*>?.filterIsInstance(): DataItem<R>? = this@filterIsInstance.filterIsInstance(R::class)

@ -21,7 +21,7 @@ class DataDependency(val filter: DataFilter, val placement: Name = EmptyName) :
return if (placement.isEmpty()) { return if (placement.isEmpty()) {
result result
} else { } else {
DataNode.build(Any::class) { this[placement] = result } DataNode.invoke(Any::class) { this[placement] = result }
} }
} }
@ -35,7 +35,7 @@ class AllDataDependency(val placement: Name = EmptyName) : Dependency() {
override fun apply(workspace: Workspace): DataNode<Any> = if (placement.isEmpty()) { override fun apply(workspace: Workspace): DataNode<Any> = if (placement.isEmpty()) {
workspace.data workspace.data
} else { } else {
DataNode.build(Any::class) { this[placement] = workspace.data } DataNode.invoke(Any::class) { this[placement] = workspace.data }
} }
override fun toMeta() = buildMeta { override fun toMeta() = buildMeta {
@ -44,22 +44,25 @@ class AllDataDependency(val placement: Name = EmptyName) : Dependency() {
} }
} }
abstract class TaskDependency(val meta: Meta, val placement: Name = EmptyName) : Dependency() { abstract class TaskDependency<out T : Any>(
abstract fun resolveTask(workspace: Workspace): Task<*> val meta: Meta,
val placement: Name = EmptyName
) : Dependency() {
abstract fun resolveTask(workspace: Workspace): Task<T>
/** /**
* A name of the dependency for logging and serialization * A name of the dependency for logging and serialization
*/ */
abstract val name: Name abstract val name: Name
override fun apply(workspace: Workspace): DataNode<Any> { override fun apply(workspace: Workspace): DataNode<T> {
val task = resolveTask(workspace) val task = resolveTask(workspace)
if (task.isTerminal) TODO("Support terminal task") if (task.isTerminal) TODO("Support terminal task")
val result = workspace.run(task, meta) val result = workspace.run(task, meta)
return if (placement.isEmpty()) { return if (placement.isEmpty()) {
result result
} else { } else {
DataNode.build(Any::class) { this[placement] = result } DataNode(task.type) { this[placement] = result }
} }
} }
@ -70,8 +73,12 @@ abstract class TaskDependency(val meta: Meta, val placement: Name = EmptyName) :
} }
} }
class DirectTaskDependency(val task: Task<*>, meta: Meta, placement: Name) : TaskDependency(meta, placement) { class DirectTaskDependency<T : Any>(
override fun resolveTask(workspace: Workspace): Task<*> = task val task: Task<T>,
meta: Meta,
placement: Name
) : TaskDependency<T>(meta, placement) {
override fun resolveTask(workspace: Workspace): Task<T> = task
override val name: Name get() = DIRECT_TASK_NAME + task.name override val name: Name get() = DIRECT_TASK_NAME + task.name
@ -80,7 +87,7 @@ class DirectTaskDependency(val task: Task<*>, meta: Meta, placement: Name) : Tas
} }
} }
class WorkspaceTaskDependency(override val name: Name, meta: Meta, placement: Name) : TaskDependency(meta, placement) { class WorkspaceTaskDependency(override val name: Name, meta: Meta, placement: Name) : TaskDependency<Any>(meta, placement) {
override fun resolveTask(workspace: Workspace): Task<*> = override fun resolveTask(workspace: Workspace): Task<*> =
workspace.tasks[name] ?: error("Task with name $name is not found in the workspace") workspace.tasks[name] ?: error("Task with name $name is not found in the workspace")
} }

@ -20,7 +20,7 @@ class GenericTask<R : Any>(
) : Task<R> { ) : Task<R> {
private fun gather(workspace: Workspace, model: TaskModel): DataNode<Any> { private fun gather(workspace: Workspace, model: TaskModel): DataNode<Any> {
return DataNode.build(Any::class) { return DataNode.invoke(Any::class) {
model.dependencies.forEach { dep -> model.dependencies.forEach { dep ->
update(dep.apply(workspace)) update(dep.apply(workspace))
} }

@ -13,7 +13,7 @@ import hep.dataforge.names.toName
import kotlin.reflect.KClass import kotlin.reflect.KClass
@TaskBuildScope @TaskBuildScope
class TaskBuilder(val name: String) { class TaskBuilder<R : Any>(val name: String, val type: KClass<out R>) {
private var modelTransform: TaskModelBuilder.(Meta) -> Unit = { allData() } private var modelTransform: TaskModelBuilder.(Meta) -> Unit = { allData() }
var descriptor: NodeDescriptor? = null var descriptor: NodeDescriptor? = null
private val dataTransforms: MutableList<DataTransformation> = ArrayList() private val dataTransforms: MutableList<DataTransformation> = ArrayList()
@ -21,12 +21,12 @@ class TaskBuilder(val name: String) {
/** /**
* TODO will look better as extension class * TODO will look better as extension class
*/ */
private class DataTransformation( private inner class DataTransformation(
val from: String = "", val from: String = "",
val to: String = "", val to: String = "",
val transform: (Context, TaskModel, DataNode<Any>) -> DataNode<Any> val transform: (Context, TaskModel, DataNode<Any>) -> DataNode<R>
) { ) {
operator fun invoke(workspace: Workspace, model: TaskModel, node: DataNode<Any>): DataNode<Any>? { operator fun invoke(workspace: Workspace, model: TaskModel, node: DataNode<Any>): DataNode<R>? {
val localData = if (from.isEmpty()) { val localData = if (from.isEmpty()) {
node node
} else { } else {
@ -46,9 +46,9 @@ class TaskBuilder(val name: String) {
fun rawTransform( fun rawTransform(
from: String = "", from: String = "",
to: String = "", to: String = "",
block: TaskEnv.(DataNode<*>) -> DataNode<*> block: TaskEnv.(DataNode<*>) -> DataNode<R>
) { ) {
dataTransforms += DataTransformation(from, to){context, model, data-> dataTransforms += DataTransformation(from, to) { context, model, data ->
val env = TaskEnv(EmptyName, model.meta, context) val env = TaskEnv(EmptyName, model.meta, context)
env.block(data) env.block(data)
} }
@ -58,7 +58,7 @@ class TaskBuilder(val name: String) {
inputType: KClass<out T>, inputType: KClass<out T>,
from: String = "", from: String = "",
to: String = "", to: String = "",
block: TaskEnv.(DataNode<T>) -> DataNode<Any> block: TaskEnv.(DataNode<T>) -> DataNode<R>
) { ) {
dataTransforms += DataTransformation(from, to) { context, model, data -> dataTransforms += DataTransformation(from, to) { context, model, data ->
data.ensureType(inputType) data.ensureType(inputType)
@ -70,7 +70,7 @@ class TaskBuilder(val name: String) {
inline fun <reified T : Any> transform( inline fun <reified T : Any> transform(
from: String = "", from: String = "",
to: String = "", to: String = "",
noinline block: TaskEnv.(DataNode<T>) -> DataNode<Any> noinline block: TaskEnv.(DataNode<T>) -> DataNode<R>
) { ) {
transform(T::class, from, to, block) transform(T::class, from, to, block)
} }
@ -78,7 +78,7 @@ class TaskBuilder(val name: String) {
/** /**
* Perform given action on data elements in `from` node in input and put the result to `to` node * Perform given action on data elements in `from` node in input and put the result to `to` node
*/ */
inline fun <reified T : Any, reified R : Any> action( inline fun <reified T : Any> action(
from: String = "", from: String = "",
to: String = "", to: String = "",
crossinline block: TaskEnv.() -> Action<T, R> crossinline block: TaskEnv.() -> Action<T, R>
@ -93,7 +93,7 @@ class TaskBuilder(val name: String) {
/** /**
* A customized pipe action with ability to change meta and name * A customized pipe action with ability to change meta and name
*/ */
inline fun <reified T : Any, reified R : Any> customPipe( inline fun <reified T : Any> customPipe(
from: String = "", from: String = "",
to: String = "", to: String = "",
crossinline block: PipeBuilder<T, R>.(TaskEnv) -> Unit crossinline block: PipeBuilder<T, R>.(TaskEnv) -> Unit
@ -101,7 +101,7 @@ class TaskBuilder(val name: String) {
action(from, to) { action(from, to) {
PipeAction( PipeAction(
inputType = T::class, inputType = T::class,
outputType = R::class outputType = type
) { block(this@action) } ) { block(this@action) }
} }
} }
@ -109,7 +109,7 @@ class TaskBuilder(val name: String) {
/** /**
* A simple pipe action without changing meta or name * A simple pipe action without changing meta or name
*/ */
inline fun <reified T : Any, reified R : Any> pipe( inline fun <reified T : Any> pipe(
from: String = "", from: String = "",
to: String = "", to: String = "",
crossinline block: suspend TaskEnv.(T) -> R crossinline block: suspend TaskEnv.(T) -> R
@ -117,7 +117,7 @@ class TaskBuilder(val name: String) {
action(from, to) { action(from, to) {
PipeAction( PipeAction(
inputType = T::class, inputType = T::class,
outputType = R::class outputType = type
) { ) {
//TODO automatically append task meta //TODO automatically append task meta
result = { data -> result = { data ->
@ -130,7 +130,7 @@ class TaskBuilder(val name: String) {
/** /**
* Join elements in gathered data by multiple groups * Join elements in gathered data by multiple groups
*/ */
inline fun <reified T : Any, reified R : Any> joinByGroup( inline fun <reified T : Any> joinByGroup(
from: String = "", from: String = "",
to: String = "", to: String = "",
crossinline block: JoinGroupBuilder<T, R>.(TaskEnv) -> Unit crossinline block: JoinGroupBuilder<T, R>.(TaskEnv) -> Unit
@ -138,7 +138,7 @@ class TaskBuilder(val name: String) {
action(from, to) { action(from, to) {
JoinAction( JoinAction(
inputType = T::class, inputType = T::class,
outputType = R::class outputType = type
) { block(this@action) } ) { block(this@action) }
} }
} }
@ -146,7 +146,7 @@ class TaskBuilder(val name: String) {
/** /**
* Join all elemlents in gathered data matching input type * Join all elemlents in gathered data matching input type
*/ */
inline fun <reified T : Any, reified R : Any> join( inline fun <reified T : Any> join(
from: String = "", from: String = "",
to: String = "", to: String = "",
crossinline block: suspend TaskEnv.(Map<Name, T>) -> R crossinline block: suspend TaskEnv.(Map<Name, T>) -> R
@ -154,7 +154,7 @@ class TaskBuilder(val name: String) {
action(from, to) { action(from, to) {
JoinAction( JoinAction(
inputType = T::class, inputType = T::class,
outputType = R::class, outputType = type,
action = { action = {
result( result(
actionMeta[TaskModel.MODEL_TARGET_KEY]?.string ?: "@anonymous" actionMeta[TaskModel.MODEL_TARGET_KEY]?.string ?: "@anonymous"
@ -169,7 +169,7 @@ class TaskBuilder(val name: String) {
/** /**
* Split each element in gathered data into fixed number of fragments * Split each element in gathered data into fixed number of fragments
*/ */
inline fun <reified T : Any, reified R : Any> split( inline fun <reified T : Any> split(
from: String = "", from: String = "",
to: String = "", to: String = "",
crossinline block: SplitBuilder<T, R>.(TaskEnv) -> Unit crossinline block: SplitBuilder<T, R>.(TaskEnv) -> Unit
@ -177,7 +177,7 @@ class TaskBuilder(val name: String) {
action(from, to) { action(from, to) {
SplitAction( SplitAction(
inputType = T::class, inputType = T::class,
outputType = R::class outputType = type
) { block(this@action) } ) { block(this@action) }
} }
} }
@ -189,22 +189,23 @@ class TaskBuilder(val name: String) {
this.descriptor = NodeDescriptor.build(transform) this.descriptor = NodeDescriptor.build(transform)
} }
internal fun build(): GenericTask<Any> = internal fun build(): GenericTask<R> =
GenericTask( GenericTask(
name.asName(), name.asName(),
Any::class, type,
descriptor ?: NodeDescriptor.empty(), descriptor ?: NodeDescriptor.empty(),
modelTransform modelTransform
) { ) {
val workspace = this val workspace = this
{ data -> return@GenericTask { data ->
val model = this val model = this
if (dataTransforms.isEmpty()) { if (dataTransforms.isEmpty()) {
//return data node as is //return data node as is
logger.warn { "No transformation present, returning input data" } logger.warn { "No transformation present, returning input data" }
data data.ensureType(type)
data.cast(type)
} else { } else {
val builder = DataTreeBuilder(Any::class) val builder = DataTreeBuilder(type)
dataTransforms.forEach { transformation -> dataTransforms.forEach { transformation ->
val res = transformation(workspace, model, data) val res = transformation(workspace, model, data)
if (res != null) { if (res != null) {
@ -221,12 +222,13 @@ class TaskBuilder(val name: String) {
} }
} }
fun Workspace.Companion.task(name: String, builder: TaskBuilder.() -> Unit): GenericTask<Any> { fun <T : Any> Workspace.Companion.task(
return TaskBuilder(name).apply(builder).build() name: String,
type: KClass<out T>,
builder: TaskBuilder<T>.() -> Unit
): GenericTask<T> {
return TaskBuilder(name, type).apply(builder).build()
} }
fun WorkspaceBuilder.task(name: String, builder: TaskBuilder.() -> Unit) {
task(TaskBuilder(name).apply(builder).build())
}
//TODO add delegates to build gradle-like tasks //TODO add delegates to build gradle-like tasks

@ -36,7 +36,7 @@ data class TaskModel(
"meta" to meta "meta" to meta
"dependsOn" to { "dependsOn" to {
val dataDependencies = dependencies.filterIsInstance<DataDependency>() val dataDependencies = dependencies.filterIsInstance<DataDependency>()
val taskDependencies = dependencies.filterIsInstance<TaskDependency>() val taskDependencies = dependencies.filterIsInstance<TaskDependency<*>>()
setIndexed("data".toName(), dataDependencies.map { it.toMeta() }) setIndexed("data".toName(), dataDependencies.map { it.toMeta() })
setIndexed("task".toName(), taskDependencies.map { it.toMeta() }) { taskDependencies[it].name.toString() } setIndexed("task".toName(), taskDependencies.map { it.toMeta() }) { taskDependencies[it].name.toString() }
//TODO ensure all dependencies are listed //TODO ensure all dependencies are listed

@ -2,12 +2,14 @@ package hep.dataforge.workspace
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.context.ContextBuilder import hep.dataforge.context.ContextBuilder
import hep.dataforge.data.Data
import hep.dataforge.data.DataNode import hep.dataforge.data.DataNode
import hep.dataforge.data.DataTreeBuilder import hep.dataforge.data.DataTreeBuilder
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.names.EmptyName
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.toName import hep.dataforge.names.isEmpty
import kotlin.jvm.JvmName
import kotlin.reflect.KClass
@TaskBuildScope @TaskBuildScope
interface WorkspaceBuilder { interface WorkspaceBuilder {
@ -28,32 +30,26 @@ fun WorkspaceBuilder.context(name: String = "WORKSPACE", block: ContextBuilder.(
context = ContextBuilder(name, parentContext).apply(block).build() context = ContextBuilder(name, parentContext).apply(block).build()
} }
fun WorkspaceBuilder.data(name: Name, data: Data<Any>) { inline fun <reified T : Any> WorkspaceBuilder.data(
this.data[name] = data name: Name = EmptyName,
noinline block: DataTreeBuilder<T>.() -> Unit
): DataNode<T> {
val node = DataTreeBuilder(T::class).apply(block)
if (name.isEmpty()) {
@Suppress("UNCHECKED_CAST")
data = node as DataTreeBuilder<Any>
} else {
data[name] = node
}
return node.build()
} }
fun WorkspaceBuilder.data(name: String, data: Data<Any>) = data(name.toName(), data) @JvmName("rawData")
fun WorkspaceBuilder.data(
name: Name = EmptyName,
block: DataTreeBuilder<Any>.() -> Unit
): DataNode<Any> = data<Any>(name, block)
fun WorkspaceBuilder.static(name: Name, data: Any, meta: Meta = EmptyMeta) =
data(name, Data.static(data, meta))
fun WorkspaceBuilder.static(name: Name, data: Any, block: MetaBuilder.() -> Unit = {}) =
data(name, Data.static(data, buildMeta(block)))
fun WorkspaceBuilder.static(name: String, data: Any, block: MetaBuilder.() -> Unit = {}) =
data(name, Data.static(data, buildMeta(block)))
fun WorkspaceBuilder.data(name: Name, node: DataNode<Any>) {
this.data[name] = node
}
fun WorkspaceBuilder.data(name: String, node: DataNode<Any>) = data(name.toName(), node)
fun WorkspaceBuilder.data(name: Name, block: DataTreeBuilder<Any>.() -> Unit) {
this.data[name] = DataNode.build(Any::class, block)
}
fun WorkspaceBuilder.data(name: String, block: DataTreeBuilder<Any>.() -> Unit) = data(name.toName(), block)
fun WorkspaceBuilder.target(name: String, block: MetaBuilder.() -> Unit) { fun WorkspaceBuilder.target(name: String, block: MetaBuilder.() -> Unit) {
targets[name] = buildMeta(block).seal() targets[name] = buildMeta(block).seal()
@ -70,17 +66,31 @@ fun WorkspaceBuilder.target(name: String, base: String, block: MetaBuilder.() ->
.seal() .seal()
} }
fun WorkspaceBuilder.task(task: Task<Any>) { fun <T : Any> WorkspaceBuilder.task(
this.tasks.add(task) name: String,
type: KClass<out T>,
builder: TaskBuilder<T>.() -> Unit
): Task<T> {
return TaskBuilder(name, type).apply(builder).build().also { tasks.add(it) }
} }
inline fun <reified T : Any> WorkspaceBuilder.task(
name: String,
noinline builder: TaskBuilder<T>.() -> Unit
): Task<T> = task(name, T::class, builder)
@JvmName("rawTask")
fun WorkspaceBuilder.task(
name: String,
builder: TaskBuilder<Any>.() -> Unit
): Task<Any> = task(name, Any::class, builder)
/** /**
* A builder for a simple workspace * A builder for a simple workspace
*/ */
class SimpleWorkspaceBuilder(override val parentContext: Context) : WorkspaceBuilder { class SimpleWorkspaceBuilder(override val parentContext: Context) : WorkspaceBuilder {
override var context: Context = parentContext override var context: Context = parentContext
override var data = DataTreeBuilder(Any::class) override var data: DataTreeBuilder<Any> = DataTreeBuilder(Any::class)
override var tasks: MutableSet<Task<Any>> = HashSet() override var tasks: MutableSet<Task<Any>> = HashSet()
override var targets: MutableMap<String, Meta> = HashMap() override var targets: MutableMap<String, Meta> = HashMap()

@ -1,5 +1,5 @@
package hep.dataforge.workspace package hep.dataforge.workspace
//fun <T1: Any, T2: Any, R: Any> TaskBuilder.zip( //fun <T1: Any, T2: Any, R: Any> TaskBuilder.zip(
// val firstNo //// val firstNo
//) = rawTransform { } ////) = rawTransform { }

@ -5,7 +5,7 @@ import hep.dataforge.data.*
import hep.dataforge.meta.boolean import hep.dataforge.meta.boolean
import hep.dataforge.meta.get import hep.dataforge.meta.get
import hep.dataforge.names.asName import hep.dataforge.names.asName
import kotlin.test.Test import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue import kotlin.test.assertTrue
@ -14,8 +14,8 @@ class SimpleWorkspaceTest {
val testPlugin = object : WorkspacePlugin() { val testPlugin = object : WorkspacePlugin() {
override val tag: PluginTag = PluginTag("test") override val tag: PluginTag = PluginTag("test")
val contextTask = Workspace.task("test") { val contextTask = Workspace.task("test", Any::class) {
pipe<Any, Unit> { pipe<Any> {
context.logger.info { "Test: $it" } context.logger.info { "Test: $it" }
} }
} }
@ -28,8 +28,10 @@ class SimpleWorkspaceTest {
plugin(testPlugin) plugin(testPlugin)
} }
repeat(100) { data {
static("myData[$it]", it) repeat(100) {
static("myData[$it]", it)
}
} }
@ -37,7 +39,7 @@ class SimpleWorkspaceTest {
model { model {
allData() allData()
} }
pipe<Int, Int> { data -> pipe<Int> { data ->
if (meta["testFlag"].boolean == true) { if (meta["testFlag"].boolean == true) {
println("flag") println("flag")
} }
@ -50,7 +52,7 @@ class SimpleWorkspaceTest {
model { model {
allData() allData()
} }
pipe<Int, Int> { data -> pipe<Int> { data ->
context.logger.info { "Starting linear on $data" } context.logger.info { "Starting linear on $data" }
data * 2 + 1 data * 2 + 1
} }
@ -62,16 +64,16 @@ class SimpleWorkspaceTest {
dependsOn("linear", placement = "linear".asName()) dependsOn("linear", placement = "linear".asName())
} }
transform<Any> { data -> transform<Any> { data ->
val squareNode = data["square"].withType<Int>().node!! val squareNode = data["square"].filterIsInstance<Int>().node!!
val linearNode = data["linear"].withType<Int>().node!! val linearNode = data["linear"].filterIsInstance<Int>().node!!
return@transform DataNode.build(Int::class) { return@transform DataNode(Int::class) {
squareNode.dataSequence().forEach { (name, _) -> squareNode.dataSequence().forEach { (name, _) ->
val newData = Data{ val newData = Data {
val squareValue = squareNode[name].data!!.get() val squareValue = squareNode[name].data!!.get()
val linearValue = linearNode[name].data!!.get() val linearValue = linearNode[name].data!!.get()
squareValue+linearValue squareValue + linearValue
} }
set(name,newData) set(name, newData)
} }
} }
} }
@ -81,17 +83,17 @@ class SimpleWorkspaceTest {
model { model {
dependsOn("square") dependsOn("square")
} }
join<Int, Int> { data -> join<Int> { data ->
context.logger.info { "Starting sum" } context.logger.info { "Starting sum" }
data.values.sum() data.values.sum()
} }
} }
task("average") { task<Double>("average") {
model { model {
allData() allData()
} }
joinByGroup<Int, Double> { env -> joinByGroup<Int> { env ->
group("even", filter = { name, _ -> name.toString().toInt() % 2 == 0 }) { group("even", filter = { name, _ -> name.toString().toInt() % 2 == 0 }) {
result { data -> result { data ->
env.context.logger.info { "Starting even" } env.context.logger.info { "Starting even" }
@ -111,7 +113,7 @@ class SimpleWorkspaceTest {
model { model {
dependsOn("average") dependsOn("average")
} }
join<Double, Double> { data -> join<Double> { data ->
data["even"]!! - data["odd"]!! data["even"]!! - data["odd"]!!
} }
} }
@ -140,7 +142,7 @@ class SimpleWorkspaceTest {
} }
@Test @Test
fun testFullSquare(){ fun testFullSquare() {
val node = workspace.run("fullsquare") val node = workspace.run("fullsquare")
println(node.toMeta()) println(node.toMeta())
} }