typelevel / typelevel/cats

Consider removing attemptNarrow

Open
#3,360 6 comments 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Scala
Stars
5.5k
Forks
1.2k
Avg merge
2d 10h
Merged PRs (30d)
5

Description

Somehow I'd never paid attention to attemptNarrow, which we added in 2.1.0. It has some problems. For example it's trivially easy to make it crash at runtime:

scala> val e: Either[Any, Unit] = Left(List("abc"))
e: Either[Any,Unit] = Left(List(abc))

scala> import cats.implicits._
import cats.implicits._

scala> def x = e.attemptNarrow[List[Int]] match {
     |   case Right(Left(List(x))) => x
     |   case _ => 0
     | }
x: Int

scala> x
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
  at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:99)
  at .x(<console>:2)
  ... 36 elided

It can also be used to launder a runtime type check:

scala> import cats.implicits._, scala.reflect.ClassTag
import cats.implicits._
import scala.reflect.ClassTag

scala> def isIt[A: ClassTag](any: Any): Boolean =
     |   (Left(any): Either[Any, Unit]).attemptNarrow[A].isRight
isIt: [A](any: Any)(implicit evidence$1: scala.reflect.ClassTag[A])Boolean

scala> val x = if (true) 1 else "abc"
x: Any = 1

scala> isIt[String](x)
res0: Boolean = false

scala> isIt[Int](x)
res1: Boolean = true

Maybe that's fine, and you should know that if you've got a ClassTag around you've given up any hope of parametricity, but that seems pretty far from the spirit of Cats.

@tpolecat requested tests for the erasure issues in the original PR, but unfortunately the tests that were added don't actually check for the relevant problems.

I guess the reasoning is that E is usually an exception type, and generally won't be generic? In any case I think we should make both the type class and syntax methods package-private in future 2.x releases and remove them in 3.0, or at least put an explicit <: Throwable constraint on them (which doesn't eliminate the possibility of runtime crashes, but does make them much less likely).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the attemptNarrow type class and syntax methods, then review the tests added in pull request #2863 for the reported erasure issues. Determine whether the API should become package-private, be constrained to Throwable, or be removed in 3.0; done means the selected API change is implemented with tests covering the runtime type and erasure problems.

Written by the indexing model from the issue text.

Assessment

Tech stack
scala
Domain
backend-api-design
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.