Scala:
enum Status:
case Ok
case Error(err: String)
export Status.*
def status: Status = Ok
def status: Status = Error("Bad!")
enum HttpResponse[+A]:
case NotFound extends HttpResponse[Nothing]
case Ok[A](res: A) extends HttpResponse[A]
export HttpResponse.*
def response: HttpResponse[String] = Ok("Response")
def response: HttpResponse[String] = NotFound
Haskell:
data Status = Ok | Error String
status :: Status
status = Ok
status :: Status
status = Error "Bad!"
data HttpResponse a = NotFound | Ok a
response :: HttpResponse String
response = Ok "Response"
response :: HttpResponse String
response = NotFound
Scala:
object Name:
opaque type Name = String
def mkName(s: String): Either[String, Name] = Either.cond(
s.length >= 2 && s.length <= 50,
s,
"Invalid name"
)
import Name.*
def doStuff(name: Name) = ()
doStuff("")
// Found: ("" : String)
// Required: Name.Name
mkName("André") match
case Right(name) => doStuff(name)
case _ => ()
Haskell:
module Name
( Name
, mkName
)
where
newtype Name =
Name String
deriving (Eq, Show)
mkName :: String -> Either String Name
mkName s
| l < 2 || l > 50 = Left "Invalid name"
| otherwise = Right $ Name s
where
l = length s
module OtherModule
import Name
doStuff :: Name -> ()
doStuff = const ()
doStuff ""
-- Couldn't match expected type ‘Name’ with actual type ‘[Char]’
case mkName "Andre" of
Right name -> doStuff name
_ -> ()
Scala:
import cats.effect.*
import cats.implicits.*
def printStuff: String => IO[Unit] = IO.println
def getStuff: IO[String] = IO.pure("stuff")
def doStuff: IO[Unit] = getStuff >>= printStuff
Haskell:
printStuff :: String -> IO ()
printStuff = putStrLn
getStuff :: IO String
getStuff = return "stuff"
doStuff :: IO ()
doStuff = getStuff >>= printStuff
Scala:
def doStuff: IO[String] = for
s <- getStuff
reverted = s.reverse
_ <- printStuff(reverted)
yield reverted
Haskell:
doStuff :: IO String
doStuff = do
s <- getStuff
let reverted = reverse s
_ <- printStuff reverted
return reverted
// Or
...
doStuff = do
...
printStuff reverted -- Discard unnecessary result
return reverted
Scala:
trait UserDsl[F[_]]:
def findUserByName(name: String): F[Option[User]]
def saveUser(user: User): F[Unit]
object UserDsl:
def apply[F[_]](implicit userDsl: UserDsl[F]): UserDsl[F] = userDsl
def doStuffWithName[F[_]: Sync: UserDsl](name: String): F[Unit] = for
maybeUser <- UserDsl[F].findUserByName(name)
_ <- maybeUser match
case Some(user) => UserDsl[F].saveUser(user)
case _ => Sync[F].raiseError(Error(s"User not found with name: $name"))
yield ()
implicit def userDsl: UserDsl[IO] = new UserDsl[IO]:
override def findUserByName(name: String) = ...
Haskell:
class UserDsl m where
findUserByName :: String -> m (Maybe User)
saveUser :: User -> m ()
doStuffWithName :: (MonadError e m, UserDsl m) => String -> m ()
doStuffWithName name = do
maybeUser <- findUserByName name
case maybeUser of
Just user -> saveUser user
_ -> throwError $ error $ "User not found with name: " ++ name
instance UserDsl IO where
findUserByName name = ...