• 0 Posts
  • 4 Comments
Joined 1 year ago
cake
Cake day: June 11th, 2023

help-circle
  • Nevoic@lemmy.worldtoProgrammer Humor@programming.devGolang be like
    link
    fedilink
    English
    arrow-up
    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.


  • Nevoic@lemmy.worldtoProgrammer Humor@programming.devGolang be like
    link
    fedilink
    English
    arrow-up
    2
    ·
    edit-2
    1 year ago

    Here’s an example (first in Haskell then in Go), lets say you have some types/functions:

    • type Possible a = Either String a
    • data User = User { name :: String, age :: Int }
    • validateName :: String -> Possible String
    • validateAge :: Int -> Possible Int

    then you can make

    mkValidUser :: String -> Int -> Possible User
    mkValidUser name age = do
      validatedName ← validateName name
      validatedAge  ← validateAge age
      pure $ User validatedName validatedAge
    

    for some reason <- in lemmy shows up as &lt;- inside code blocks, so I used the left arrow unicode in the above instead

    in Go you’d have these

    • (no Possible type alias, Go can’t do generic type aliases yet, there’s an open issue for it)
    • type User struct { Name string; Age int }
    • func validateName(name string) (string, error)
    • func validateAge(age int) (int, error)

    and with them you’d make:

    func mkValidUser(name string, age int) (*User, error) {
      validatedName, err = validateName(name)
      if err != nil {
        return nil, err
      }
    
      validatedAge, err = validateAge(age)
      if err != nil {
        return nil, err
      }
    
      return User(Name: validatedName, Age: validatedAge), nil
    }
    

    In the Haskell, the fact that Either is a monad is saving you from a lot of boilerplate. You don’t have to explicitly handle the Left/error case, if any of the Eithers end up being a Left value then it’ll correctly “short-circuit” and the function will evaluate to that Left value.

    Without using the fact that it’s a functor/monad (e.g you have no access to fmap/>>=/do syntax), you’d end up with code that has a similar amount of boilerplate to the Go code (notice we have to handle each Left case now):

    mkValidUser :: String -> Int -> Possible User
    mkValidUser name age =
      case (validatedName name, validateAge age) of
        (Left nameErr, _) => Left nameErr
        (_, Left ageErr)  => Left ageErr
        (Right validatedName, Right validatedAge) => 
          Right $ User validatedName validatedAge