Note: Lemmy code blocks don't play nice with some symbols, specifically < and & in the following code examples
This isn't a language level issue really though, Haskell can be equally ergonomic.
The weird thing about ?.
is that it's actually overloaded, it can mean:
A?
that returns B?
A?
that returns B
you'd end up with B?
in either case
Say you have these functions
toInt :: String -> Maybe Int
double :: Int -> Int
isValid :: Int -> Maybe Int
and you want to construct the following using these 3 functions
fn :: Maybe String -> Maybe Int
in a Rust-type syntax, you'd call
str?.toInt()?.double()?.isValid()
in Haskell you'd have two different operators here
str >>= toInt <&> double >>= isValid
however you can define this type class
class Chainable f a b fb where
(?.) :: f a -> (a -> fb) -> f b
instance Functor f => Chainable f a b b where
(?.) = (<&>)
instance Monad m => Chainable m a b (m b) where
(?.) = (>>=)
and then get roughly the same syntax as rust without introducing a new language feature
str ?. toInt ?. double ?. isValid
though this is more general than just Maybe
s (it works with any functor/monad), and maybe you wouldn't want it to be. In that case you'd do this
class Chainable a b fb where
(?.) :: Maybe a -> (a -> fb) -> Maybe b
instance Chainable a b b where
(?.) = (<&>)
instance Chainable a b (Maybe b) where
(?.) = (>>=)
restricting it to only maybes could also theoretically help type inference.
I was thinking along the lines of "you can't easily get at the wrapped type". To get at b
instead of Maybe b
you need to either use do-notation or lambdas (which do-notation is supposed to eliminate because they're awkward in a monadic context) whereas Rust will gladly hand you that b
in the middle of an expression, and doesn't force you to name the point.
Or to give a concrete example, if foo()? {...}
is rather awkward in Haskell, you end up writing things like
foo x y = bar >>= baz x y
where
baz x y True = x
baz x y False = y
, though of course baz is completely generic and can be factored out. I think I called it "cap" in my Haskell days, for "consequent-alternative-predicate".
Flattening Functors and Monads syntax-wise is neat but it's not getting you all the way. But it's the Haskell way: Instead of macros, use tons upon tons of trivial functions :)