odersky: odersky: If implicits are so good why are they not the run-away success they should be? Why do the great majority of people who are exposed to implicits hate them, yet the same people would love Haskell’s type classes, or Rust’s traits, or Swift’s protocols? The usual answer I get from people who are used to current implicits is that we just need minor tweaks and everything will be fine. I don’t believe that anymore.

Otherwise put: What can we learn from the other languages? The main distinguishing factor is that their term synthesis is separate from the rest of programming, and that they more or less hide what terms get generated. What terms are generated is an implementation detail, the user should not be too concerned about it.

In my opinion, changing keywords from implicit to given/ implied/ whatever is just another minor tweak when it comes to beginner friendliness, albeit bringing heavy migration pain.

First I’ll link to my previous post on this subject:

Proposal To Revise Implicit Parameters SIP Proposal One of main Rust’s strengths is friendliness of compiler errors. Rust compiler very often suggest possible corrections and the first one frequently works. If I forget some import (use in Rust parlance) needed for a typeclass to work, Rust compiler often suggest it to me. What will Scala compiler say? object Main { implicit class RichInt(value: Int)(implicit name: String) { def print(): Unit = println(s"$name: $value") } def main(args: Array[String]): Unit = { // implicit va…

Typical problem with implicits is that implicit conversion doesn’t work. Changing syntax for implicits has zero influence on that. A pretty rare and simple to fix problem is when we have to disambiguate between implicit and explicit parameter lists with explicit apply :

def method(explicit1: Int)(implicit implicit1: String): String => Int = x => explicit + x.length method(5).apply("wow") // implicit1 stays implicit

Why implicit conversion doesn’t work in a particular place (but works somewhere else)?

because you forgot to import some implicit conversions or implicit values

types don’t match so implicit conversions are rejected silently

there are implicits ambiguities

you forgot to mark something implicit

etc

What Scala compiler reports when it can’t find a extension method coming from implicit conversion (or implicit class which is a syntactic sugar for it)?

value print is not a member of Int

What Rust compiler says?

https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=0dc03e79269cc6efff0ad31947811ebe

error[E0369]: binary operation `<<` cannot be applied to type `f32` --> src/main.rs:6:1 | 6 | x << 2; | ^^^^^^ | = note: an implementation of `std::ops::Shl` might be missing for `f32`

https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=4dd0b62770be7752e1d354028246f3c4

error[E0119]: conflicting implementations of trait `main::MyTrait` for type `main::Foo`: --> src/main.rs:15:1 | 7 | impl<T> MyTrait for T { | --------------------- first implementation here ... 15 | impl MyTrait for Foo { // error: conflicting implementations of trait | ^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `main::Foo`

I wrote an application using Rust. Here’s very simplified case from it.

In one file I have:

pub trait FixedPoint where Self: Sized { ... } pub trait FixI32: FixedPoint<Raw=i32> { ... } impl<T: FixedPoint<Raw=i32>> FixI32 for T {}

In second file I have:

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct NoFractI32(i32); impl FixedPoint for NoFractI32 { ... }

In third file I forgot to import relevant typeclasses (signalled by commenting out):

// use demixer::fixed_point::{FixedPoint, FixI32, FixU32}; use demixer::fixed_point::types::{NoFractI32, Log2D}; #[test] fn initial_cost_corresponds_to_one_bit_costs_series() { ... let new_tracker = old_tracker.updated(NoFractI32::ONE.to_fix_i32()); ... }

What Rust compiler says?

error[E0599]: no method named `to_fix_i32` found for type `demixer::fixed_point::types::NoFractI32` in the current scope --> tests/cost_tracking.rs:33:59 | 33 | let new_tracker = old_tracker.updated(NoFractI32::ONE.to_fix_i32()); | ^^^^^^^^^^ | = help: items from traits can only be used if the trait is in scope help: the following trait is implemented but not in scope, perhaps add a `use` for it: | 20 | use demixer::fixed_point::FixI32; |

Plus similar error message about FixedPoint . Following compiler suggestions correctly solves the problem with missing imports ( use s in Rust parlance).

There’s a huge discrepancy between scalac error messages and rustc error messages. Usefulness of Rust’s error messages is IMO the main reason Rust is so liked.

In Scala we don’t have the simple rules and useful error messages that Rust has. Instead:

Scala has very complicated prioritized implicit resolution, while Rust just rejects any ambiguities

typeclasses instances in Scala can be defined anywhere, while Rust rejects orphans

typeclasses instances in Scala can be stored in anything, while Rust allows only global definitions

Scala just says that a method is not a member of some type and doesn’t even try to suggest any solution while Rust gives ready to copy-and-paste code snippet that usually solves the problem

Rust also has more simplifications compared to Scala, e.g. Rust doesn’t have method overloading: https://internals.rust-lang.org/t/justification-for-rust-not-supporting-function-overloading-directly/7012

Complex implicit conversions/ classes/ whatever are done almost exclusively by library writers. Library user’s job is usually to have proper implicits imported into scope. Implicits defined by ordinary Scala programmers are usually simple, like implicit correlation ID or implicit ExecutionContext.

Providing useful suggestion in compilation errors for all existing code may be unfeasible now, but when Scala compiler starts giving suggestions to fix missing implicits in scope then library writers will reorganize their libraries to make the compiler suggestions more useful. Rust compiler was designed to provide useful error messages from the start (i.e. from the first public release, I think), so if we want to compete with Rust in this area we must make useful error messages a core feature of the compiler.

Rust’s syntax related to typeclasses wasn’t perfect either, but that didn’t give Rust bad PR. Some explanation: https://doc.rust-lang.org/edition-guide/rust-2018/trait-system/dyn-trait-for-trait-objects.html

Using just the trait name for trait objects turned out to be a bad decision. The current syntax is often ambiguous and confusing, even to veterans, and favors a feature that is not more frequently used than its alternatives, is sometimes slower, and often cannot be used at all when its alternatives can.

To summarize:

Typical problem with implicits is not the syntax, but figuring out what to import to have required extension methods available on type.