Apologies in advance, this might boil down to a newbie Haskell advice request, but as far as I can see, the question is valid enough.
I arrived on a similar implementation of a Rust-inspired TryFrom and while refining it with AI, it pointed me to this project as reference. A big difference that I was surprised to find was the stated
You should not have both a From instance and a TryFrom instance for the same pair of types.
Also mentioned in #43 like an obvious law.
But I wonder, why is that? I can't help but read this as
Total functions are not a subset of partial functions
Sure, I can see that with both class TryFrom and class From, we want to prevent the possibility of their instances having differing implementations, but Rust enforces it with a blanket instance/impl (albeit with a From/Into indirection):
https://doc.rust-lang.org/src/core/convert/mod.rs.html#825-833
// Infallible conversions are semantically equivalent to fallible conversions
// with an uninhabited error type.
const impl<T, U> TryFrom<U> for T
where
U: [const] Into<T>,
{
type Error = Infallible;
Which in Haskell direct translation would be:
instance From a b => TryFrom a b where
I reckon there are problems with this, for one, this would be {-# OVERLAPPABLE #-} and not good for a library design choice, but keeping close to Rust implementation of associated Error type and Infallible case, this seemed as the natural GHC-sound equivalent:
class TryFrom source target where
type TryFromError source target :: Type
tryFrom :: source -> Either (TryFromError source target) target
type From source target = (TryFrom source target, TryFromError source target ~ Void)
Ie. having From being a refinement instead of it's own class. Was there a strong reason to not take a similar path? The dowside I can think is the use of Void being needed, which I'm not sure of how idiomatic it is in Haskell.
Still it feels pretty solid for my reading:
A total conversion is a special case of partial conversion
The motivation
My actual use-cases are somewhat weak, but hopefully illustrative enough.
I've been using ghci ocasionally as a shell and runhaskell for everyday scripts to help me get more Haskell exp.
In that context, I really miss the convenience of loose union parameters like in Python or TS, so I've been using Rust conventions to achieve similar ergonomics on my local utils.
Eg. TS fetch signature:
function fetch(input: string | URL | Request): Promise<Response>
This allows:
fetch("https://example.com") // I can *try* making a GET request from that
fetch(new URL(...)) // I can *definitely* make a GET request from that
In Rust, supporting this is pretty common, by using Into/TryInto, but with Witch.TryFrom:
fetch :: (TryFrom a Request) => a -> IO Response
-- instance TryFrom String Request
fetch "https://example.com" -- Sure, I can *try* making a GET request from that
-- instance From Url Request
fetch Url{...} -- Type Error: sorry, not fallible enough
Apologies in advance, this might boil down to a newbie Haskell advice request, but as far as I can see, the question is valid enough.
I arrived on a similar implementation of a Rust-inspired
TryFromand while refining it with AI, it pointed me to this project as reference. A big difference that I was surprised to find was the statedAlso mentioned in #43 like an obvious law.
But I wonder, why is that? I can't help but read this as
Sure, I can see that with both
class TryFromandclass From, we want to prevent the possibility of their instances having differing implementations, but Rust enforces it with a blanket instance/impl (albeit with aFrom/Intoindirection):https://doc.rust-lang.org/src/core/convert/mod.rs.html#825-833
Which in Haskell direct translation would be:
I reckon there are problems with this, for one, this would be
{-# OVERLAPPABLE #-}and not good for a library design choice, but keeping close to Rust implementation of associatedErrortype andInfalliblecase, this seemed as the natural GHC-sound equivalent:Ie. having
Frombeing a refinement instead of it's own class. Was there a strong reason to not take a similar path? The dowside I can think is the use ofVoidbeing needed, which I'm not sure of how idiomatic it is in Haskell.Still it feels pretty solid for my reading:
The motivation
My actual use-cases are somewhat weak, but hopefully illustrative enough.
I've been using ghci ocasionally as a shell and runhaskell for everyday scripts to help me get more Haskell exp.
In that context, I really miss the convenience of loose union parameters like in Python or TS, so I've been using Rust conventions to achieve similar ergonomics on my local utils.
Eg. TS
fetchsignature:This allows:
In Rust, supporting this is pretty common, by using
Into/TryInto, but withWitch.TryFrom: