Skip to content

improve error message when a label instance is not available - #44

Merged
benikm91 merged 2 commits into
dimwit-dev:mainfrom
marcelluethi:labels-error-message
Jan 20, 2026
Merged

improve error message when a label instance is not available#44
benikm91 merged 2 commits into
dimwit-dev:mainfrom
marcelluethi:labels-error-message

Conversation

@marcelluethi

Copy link
Copy Markdown
Contributor

Making mistakes with the labels can lead to nasty error messages in dimwit, which are extremely hard to pinpoint, as the resulting type is often computed and, thanks to type inference, not seen directly in the code.

As an example, consider the following code

  trait Base derives Label
  trait Target derives Label

  val dist  = Normal(
    Tensor(
      Shape(Axis[Target] -> 2)
    ).fill(2f),
    Tensor(
      Shape(Axis[Target] -> 2, Axis[Target] -> 2)
    ).fromArray(
      Array(0.5f, 0f, 0f, 0.5f, 0.5f, 0f, 0f, 0.5f)
    )
  )

Here, the user provides a covariance matrix to an independent normal distribution, instead of just the variances. From the error message it is almost impossible to infer the mistake.

Given really targeted error messages would require adding explicit checks to all sites where several labels are used (which seems impractical). This PR is a first attempt to provide at least a better general error message when something is wrong with the labels.

@marcelluethi
marcelluethi requested a review from benikm91 January 18, 2026 17:22
@benikm91

Copy link
Copy Markdown
Collaborator

Larger issue

I was confused why the original error is as bad as it is and why the type system does not catch the wrong second type, as both parameters are Tensor[T, V].

As far as I understand is it because of the covariance of Tensor:
class Tensor[+T, V]

Because Tuple is a Parent type of all Tuples, e.g. Tuple1 <: Tuple and Tuple2 <: Tuple, the scala compiler find T==Tuple as a valid type for T, but then finds no Labels for Labels[Tuple], leading to the delayed and therefore confusing error message...


Compromise?

I did not find a good solution to fix this, while keeping covariance. Maybe covariance was added to quickly as I was not aware of this issue. So let's take a step back. What does covariance give us? It allows for such code:

trait Parent derives Label
trait Child1 extends Parent derives Label
trait Child2 extends Parent derives Label
def concreteFunction(t: Tensor1[Parent, Float]): Tensor1[Parent, Float] = t + t // Parent is parameter type
val child1: Tensor1[Child1, Float] = Tensor(Shape1(Axis[Child1] -> 4)).fill(1f)
concreteFunction(child1) // we can call functio with Child types

However, this can also be implemented as:

def concreteFunction2[C <: Parent](t: Tensor1[C, Float]): Tensor1[C, Float] = t + t

Which is even better as it preserves the Child type:
image

The motivation to add covariance to Tensor's T was to allow "def concreteFunction" as this can be jit compiled, whereas concreteFunction2 must be compiled twice for Child1 and Child2 Generic type...

An additional fact to consider is that the label and the type may differ if we have covariance. E.g., in concreteFunction, printing the tensor shape would print "child1", not "parent". While this makes sense to me, it could add confusion.


Suggestion

I suggest removing the T covariance from Tensor for now. Which leads to minor adjustments in some examples (but manageable). Then rethink your error message, probably one of the secarios is handled by the type system then...

Related: I discovered something while investigating. We should allow this, which currently doesn't work.

image

This can be supported without Tensor Covariance (so is somewhat unrelated). It requires adjusting the binary operations: See https://github.com/benikm91/dimwit/tree/tensor-covariance for implementation.
image

A real world example for this would be the mean and std in VariationalAutoEncoder - this is actually how I discoverd the problem:

trait Latent derives Label
trait MeanLatent extends Latent derives Label
trait LogVarLatent extends Latent derives Label

val mean: Tensor1[MeanLatent, Float] = ???
val std: Tensor1[LogVarLatent, Float] = ???
val latent: Tensor1[Latent, Float] = (1f +! logVar - mean.pow(2f) - logVar.exp) // automatically change to latent parent type if two different childs are added.
println(latent.axes)  // prints "latent"

@benikm91

Copy link
Copy Markdown
Collaborator

I just checked. Changing Normal to T type will result in smart error (now that covariance is gone 👍 )
I suggest changing the implicitNotFound message to

image

I suggest reducing the implicitNotFound message to only include the "2." case. Or do you think "1." can still occure?

Then this can be merged as well.

@marcelluethi

Copy link
Copy Markdown
Contributor Author

@benikm91 I think you meant message 1 not 2. I simplified the message. You can check if it makes sense for you and, if it does, merge it.

@benikm91
benikm91 merged commit c8cbdda into dimwit-dev:main Jan 20, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants