this post was submitted on 14 Aug 2023
1254 points (97.9% liked)

Programmer Humor

19555 readers
1337 users here now

Welcome to Programmer Humor!

This is a place where you can post jokes, memes, humor, etc. related to programming!

For sharing awful code theres also Programming Horror.

Rules

founded 1 year ago
MODERATORS
 
you are viewing a single comment's thread
view the rest of the comments
[โ€“] Nevoic@lemmy.world 1 points 1 year ago (1 children)

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 <&> 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 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
    (?.) = (<&>)

instance Chainable a b (Maybe b) where
    (?.) = (>>=)

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

[โ€“] barsoap@lemm.ee 1 points 1 year ago* (last edited 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 :)