Open armanbilge opened 1 year ago
Somewhat hampered by: https://github.com/scalameta/metals/issues/4325
It's going to be hard to strike a balance here, and arguably some of the import complaints should be resolved by the libraries themselves rather than toolkit
providing a way out(http4s
:P).
One aspect of the import complaints is that, usually the answer is: import cats.syntax.all.*
or some variation of that. I think if the "kitchen sink" import only solved that aspect, that'd be an efficient start.
One aspect of the import complaints is that, usually the answer is: import
cats.syntax.all.*
or some variation of that. I think if the "kitchen sink" import only solved that aspect, that'd be an efficient start.
IMHO a good fit might be this 👇
package typelevel.toolkit
export cats.syntax.all.*
export cats.effect.*
export fs2.*
but I'll confirm what I'm saying after I'll have the time to enucleate all the stuff that will get imported in this way. WDYT @zetashift ?
One aspect of the import complaints is that, usually the answer is: import
cats.syntax.all.*
or some variation of that. I think if the "kitchen sink" import only solved that aspect, that'd be an efficient start.IMHO a good fit might be this point_down
package typelevel.toolkit export cats.syntax.all.* export cats.effect.* export fs2.*
but I'll confirm what I'm saying after I'll have the time to enucleate all the stuff that will get imported in this way. WDYT @zetashift ?
I think I'm going to use enucleate in daily life :P.
In all seriousness, that looks good to me!
my only issue is that with export fs2.*
we completely shadow io.circe
because of fs2.io
. _root_
is a workaround but that prevents discoverability and is detrimental to a first experience with the toolkit
my only issue is that with
export fs2.*
we completely shadowio.circe
because offs2.io
._root_
is a workaround but that prevents discoverability and is detrimental to a first experience with the toolkit
That's totally true, but (thinking out loud) isn't that solvable adding export io.circe.*
? AFAIK every circe module lives under io.circe
.
Note that Decoder
, Encoder
, and Codec
(as in io.circe.*
, skunk.*
, scodec.*
) are very overloaded. In theory we could remap these a la JsonDecoder
etc. but there's this overarching concern that you might know how to do things with the toolkit, but take away the training wheels and you don't know what anything is actually called or where it comes from.
my only issue is that with
export fs2.*
I think the answer here is, don't do that :) instead of a wildcard we should export very specific things, like Stream
.
Discussing with @armanbilge, we came up with the idea that a Kitchen Sink import is probably not a great idea because it can be confusing for beginners progressing past the "hello world" with the TL stack.
Code written relying on this kitchen sink import won't be copy-pastable to other codebases as imports may need to be added. If this may not represent a problem for users proficient with the toolkit's libraries, beginners may struggle to compile the code as it may even happen that the IDE won't cooperate.
Last but not least, at the import level, the examples in the toolkit homepage will be different from those in every included repository.
We're trying to achieve higher ergonomics at the cost of clarity/compatibility. It may not be the correct way or place to solve the "lots of imports" problem.
WDYT?
Discussing with @armanbilge, we came up with the idea that a Kitchen Sink import is probably not a great idea because it can be confusing for beginners progressing past the "hello world" with the TL stack.
Since the import is opt-in, I think it's okay if we entertain the idea. But reading the downsides you listed, it's a net-negative for me. Especially the copy-pastability of scripts hurts most :(.
I think the best long-term strategy is that we should keep pursuing ways to reduce the number of imports necessary overall in the various libraries. At present we are in a really annoying spot, but somewhere in the future we can define syntax directly on typeclasses, so that the syntax imports are no longer necessary.
we should keep pursuing ways to reduce the number of imports necessary overall
Here's a contributors thread by @benhutchison on this topic!
What’s the problem? Modern Scala code written on top of library-ecosystems such as Typelevel is dependent on a huge number of imports to work correctly. The import section reaches 50 lines long in some of my application files, which rivals the actual application code.
https://contributors.scala-lang.org/t/idiomatic-imports/5788/1
I was also reminded of this. https://gist.github.com/Daenyth/03889157b1c409196c17d8d8e509e603
https://contributors.scala-lang.org/t/idiomatic-imports/5788/1
Yea the import problem and discovery of things is really mehh. But reading through the thread it seems a more powerful export
is also necessary?
Until then, I hope we can pick up some low-hanging fruit fixes for typelevel libraries, because when I look at http4s
or circe
, I have the feeling that some things can be easier wrt imports, at the library level.
https://contributors.scala-lang.org/t/idiomatic-imports/5788/1
TBH I think Martin has a point in saying "you are establishing a DSL that only an expert can be comfortable in" while talking about -Yimports:
.
Also, to cite Ben:
Ensuring imports are consistent across files is a source of errors & low-value busy-work.
Changes to import lines clog up diffs and distract from more interesting changes.
Verbose imports add friction to refactors and experiments.
IMHO, the consistency across files
is probably the most time-consuming thing that happens, as nowadays Metals and IntelliJ do a pretty good job at importing stuff in a specific file. As this toolkit was "realized with Scala CLI in mind" we can assume (but not pretend ofc) that a good number of uses will be for single-file applications, solving the consistency
problem at its root.
What Daenyth did in the CE prelude was the scala 3 export alternative that I had in mind while looking at this issue. I'm on the same page as @zetashift: I think that overall re-namespacing the most used classes/data structures/syntaxes may have a net-negative impact.
IMHO, the
consistency across files
is probably the most time-consuming thing that happens, as nowadays Metals and IntelliJ do a pretty good job at importing stuff in a specific file
Actually, my experience is that IDE automation falls far short my ideal wish-list around imports. And this is in large part because the problem is hard.
Example. If you didn't already have import cats.syntax.all.*
in a file, but did have Cats on your classpath, and you typed "option".some
, AFAIK no IDE is smart enough to tell you, hey if you import this package you can enable that method."
Similarly, if you activate code completion, you will not see extension method options unless they were already imported. But often the biggest challenge is discovering what extensions exist and how to import them.
Finally, neither IDE is yet smart enough to make decisions about whether added Scala 3 imports should be .*
, .given
or .{*, given}
.
As a small update, with the recent improvements of toolkit and typelevel being a kit, I don't have anything else to complain(things are nice! :P) about except for the imports issue across Typelevel ecosystem.
A Prelude
doesn't seem so bad these days, but it's a very conflicting feel.
A
Prelude
doesn't seem so bad these days, but it's a very conflicting feel.
I thought so many times to publish a toolkit + a Prelude under my group id just to test out if this solution works for my day by day 🤔 I'll do it eventually
A common complaint is about the enormous amount of imports it takes to do stuff. e.g. https://github.com/http4s/http4s/discussions/6696
What if ... we just exported a bunch of useful stuff under a single package?
Not gonna lie, it's playing with fire a bit. Probably instead of wildcards we should be thoughtful about the specific things we export.
But then:
and you are off to the races!