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.
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.
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