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 <- inside code blocks, so I used the left arrow unicode in the above instead
in Go you'd have these
- (no
Possibletype 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