5 ms·
This is strange to me because I don't see FP faltering here at all. I suppose it depends on precisely what you mean by "OOP" and "FP". Below is an example imp
by tome 2y ago
This is strange to me because I don't see FP faltering here at all. I suppose it depends on precisely what you mean by "OOP" and "FP". Below is an example implementation in my Haskell effect system Bluefin. It defines a Logger interface (that can log a message with severity) and then instantiates it with two implementations
https://hackage.haskell.org/package/bluefin https://hackage.haskell.org/package/bluefin
1. a logger that logs to stdout
2. a logger that logs to a file
The file logger also brackets the opening of the file so that if abnormal termination is encountered then the file handle is guaranteed to be cleaned up. This is similar to RAII.
I really like this solution! It's just programming against an interface, and then instantiating the interface in different ways. I think an solution using inheritance would be worse, because it would use a special language concept (inheritance) rather than a standard one (function definition).
Perhaps this is "OOP" and not "FP"? That's fine by me! But in that case I conclude Haskell is an excellent OOP language. (I already conclude that it's an excellent imperative language.)
{-# LANGUAGE GHC2021 #-}
import Bluefin.Compound
import Bluefin.Eff
import Bluefin.IO
import System.IO
import Prelude hiding (log)
newtype Logger e =
-- Log a message with a severity
MkLogger {log :: String -> Int -> Eff e ()}
withStdoutLogger ::
(e1 :> es) =>
IOE e1 ->
(forall e. Logger e -> Eff (e :& es) r) ->
Eff es r
withStdoutLogger io k =
useImplIn
k
MkLogger
{ log =
\msg sev ->
effIO io (putStrLn (show sev ++ ": " ++ msg))
}
withFileLogger ::
(e1 :> es) =>
FilePath ->
IOE e1 ->
(forall e. Logger e -> Eff (e :& es) r) ->
Eff es r
withFileLogger fp io k =
bracket
(effIO io (openFile fp ReadMode))
(effIO io . hClose)
( \handle -> do
useImplIn
k
MkLogger
{ log =
\msg sev ->
effIO io (hPutStr handle (show sev ++ ": " ++ msg))
}
)
- throwawaymaths 2y agoThat's fine, but suppose you wanted to swap out loggers (or add an extra logger target) at runtime. Maybe someone wants transiently hooks in an observer by logging into a webpage that shoots the logs at that users browser. I don't know enough Haskell (and it's hard enough to read) that I can't tell if this code can deal with that case.
- ndriscoll 2y agoSwapping out loggers at runtime is a separate concern (inheritance and OOP don't make that any easier), and is more to do with control flow or whether there's an indirection when accessing the logger.
- kergonath 2y agoPolymorphism definitely makes it much easier, and it is a core concept in OOP. I am not advocating for or against OOP, but “swapping behaviour at runtime” is one of the things it’s good at.
- josephg 2y agoIf you want to be able to swap out implementations, personally I’d much rather rust’s trait system (or Java or typescript’s interfaces) over what “OO languages” like C++ give you. I basically never want a strict tree of object types with inheritance. This isn’t an OO idea. I’m pretty sure FP languages like Haskell or ocaml have something very similar.
- kergonath 2y agoI don’t know about Rust, but interfaces as in Java (or things like protocols in Objective-C) are fundamentally object-oriented features. It is at the core of the concept. Java is much more object-oriented than C++. You can do something similar with overloaded functions and functions interface, at which point you’re re-implementing OOP without encapsulation and worse language support.
- josephg 2y agoIt’s certainly not a feature that is exclusive to OO languages. Haskell supports type classes which can do the same thing. Or ocaml’s module types. Rust traits. Etc. I don’t think anyone will accuse those languages of being object oriented. Frankly C++’s abstract superclasses have, in my opinion, the worst syntax of them all to support the same feature. I really don’t see this kind of polymorphism as a weakness of non-OO languages. If you wanna play “who wore it better”, Haskell and rust get my vote. Rust traits are strictly more powerful than Java interfaces since you can say things like impl MyTrait for T where T: Iterator<…> {}
- deleted 2y ago[deleted]