Multi parameter instances that reuse the same type variable

I wanted to see if I could emulate the From trait from Rust. I think a Purescript version (of this Rust source) looks like this:

class From source target where
  from :: source -> target

This compiles just fine.

One of the things the Rust standard library provides is reflexivity for this trait (more rusty code here). That is, every type T has an implementation From<T>. My attempt at doing this in Purescript compiles just fine too:

instance fromSelf :: From source source where
  from source = source

The problem comes when I try to implement something specific:

instance fromBooleanInt :: From Boolean Int where
  from true = 1
  from false = 0
Overlapping type class instances found for

    Data.Convert.From Boolean
                      Int

  The following instances were found:

    Data.Convert.fromSelf
    Data.Convert.fromBooleanInt


in type class instance

  Data.Convert.From Boolean
                    Int
PureScript(OverlappingInstances)

Iā€™m aware that the Purescript compiler doesnā€™t check constraints until it picks an instance. I also understand that I can use instance chains to fix this, though from my understanding that would prevent anyone outside this module from writing an instance for this class for their own types. But it feels weird to me that I get this error when I reuse the same type variable for each parameter in fromSelf.

Could someone explain what the compiler is doing here? Does the fact that two variables are the same count as a constraint? Is there an example of something similar being done without instance chains, or is what Iā€™m trying to do impossible?

Just wanted to mention that this is purely an academic exercise for me. I might be way too curious about these kinds of thingsā€¦

ā€¦ also, I actually ran into this when trying to write an instance for From Void target. I donā€™t know if that has a different cause, but the From Boolean Int case seemed simpler to start with while trying to understand things.

1 Like

I had a quick look at the overlapping instance code of the compiler, and I think it just checks ā€˜per columnā€™ if types are overlapping. I went ahead and disabled this check and indeed the compiler picks the right instances when used:

class From source target where
  from :: source -> target

instance fromSelf :: From source source where
  from source = source

instance fromBooleanInt :: From Boolean Int where
  from true = 1
  from false = 0

test1 :: String
test1 = from "hello"

test2 :: Int
test2 = from true

produces

var test1 = from(fromSelf)("hello");
var test2 = from(fromBooleanInt)(true);

I could be wrong about what the overlapping instance check is doing exactly, but I think itā€™s just not smart enough/we assume using instance chains will be used when itā€™s overzealous

PureScript doesnā€™t support any sort of instance specialization, which I think Rust will do under certain conditions. Thereā€™s no way to provide instance ā€œdefaultsā€ or anything (that is, some instance that will be picked by default given some constraint holds), which I assume Rust would be doing to support that.

This is really interesting, could you point me to where I might find that logic?

Right, so I did see discussions on that when trying to research this. What Iā€™m having trouble with is the idea that From T T and From A B constitute an overlap. I guess it just doesnā€™t seem intuitive to me. I can get my head around not checking constraints before an instance is picked: thatā€™s just a rule of the language I need to remember. I guess the rule in this case would be ā€œInstances are picked base on the position of their arguments, not the names of the parametersā€ or something along those lines.

Iā€™m not sure if you were speaking generally about Rusts From trait, or the PS code supplied by @MelvIsntNormal? If itā€™s the latter then I donā€™t think this is instance specialization - a constraint of From Boolean Int would never use the fromSelf instance. This is made clear if we remove the fromBooleanInt instance as then from true :: Int throws a NoInstanceFound error; the fromBooleanInt isnā€™t overriding the fromSelf instance