• @[email protected]
    link
    fedilink
    English
    1
    edit-2
    1 year ago

    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:

    • call a function on A? that returns B?
    • call a function on 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 &lt;&amp;> 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
        (?.) = (&lt;&amp;>)
    
    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 Maybes (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
        (?.) = (&lt;&amp;>)
    
    instance Chainable a b (Maybe b) where
        (?.) = (>>=)
    

    restricting it to only maybes could also theoretically help type inference.

    • @[email protected]
      link
      fedilink
      1
      edit-2
      1 year ago

      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 :)