"""
    compute(t::AbstractTask; data...)

Fallback implementation of the compute function of a compute task, throwing an error.
"""
function compute(t::AbstractTask; data...)
    return error("Need to implement compute()")
end

"""
    compute(t::FusedComputeTask; data...)

Compute a fused compute task.
"""
function compute(t::FusedComputeTask; data...)
    (T1, T2) = collect(typeof(t).parameters)

    return compute(T2(), compute(T1(), data))
end

"""
    compute(t::AbstractDataTask; data...)

The compute function of a data task, always the identity function, regardless of the specific task.
"""
compute(t::AbstractDataTask; data...) = data

"""
    compute_effort(t::AbstractTask)

Fallback implementation of the compute effort of a task, throwing an error.
"""
function compute_effort(t::AbstractTask)::Float64
    # default implementation using compute
    return error("Need to implement compute_effort()")
end

"""
    data(t::AbstractTask)

Fallback implementation of the data of a task, throwing an error.
"""
function data(t::AbstractTask)::Float64
    return error("Need to implement data()")
end

"""
    compute_effort(t::AbstractDataTask)

Return the compute effort of a data task, always zero, regardless of the specific task.
"""
compute_effort(t::AbstractDataTask)::Float64 = 0.0

"""
    data(t::AbstractDataTask)

Return the data of a data task. Given by the task's `.data` field.
"""
data(t::AbstractDataTask)::Float64 = getfield(t, :data)

"""
    copy(t::DataTask)

Copy the data task and return it.
"""
copy(t::DataTask) = DataTask(t.data)

"""
    children(::DataTask)

Return the number of children of a data task (always 1).
"""
children(::DataTask) = 1

"""
    data(t::AbstractComputeTask)

Return the data of a compute task, always zero, regardless of the specific task.
"""
data(t::AbstractComputeTask)::Float64 = 0.0

"""
    compute_effort(t::FusedComputeTask)

Return the compute effort of a fused compute task. 
"""
function compute_effort(t::FusedComputeTask)::Float64
    return compute_effort(t.first_task) + compute_effort(t.second_task)
end

"""
    get_types(::FusedComputeTask{T1, T2})

Return a tuple of a the fused compute task's components' types.
"""
get_types(t::FusedComputeTask) = (typeof(t.first_task), typeof(t.second_task))
