Functional version of this OO snippet

Hi all, public interface IEngine { void foo(); void bar(string bah); ... } public class Program { public void Run(IEngine engine){ while(true){ string command = GetLine(); if (command.startsWith("foo")){ engine.foo(); } else if (command.startsWith("bar")){ engine.bar(command); ... else break; } In other words, I want to provide the same UI across multiple implementations of the engine that actually processes the commands. Thanks in advance.

On 5 Dec 2008, at 13:40, Andrew Wagner wrote:
Hi all, public interface IEngine { void foo(); void bar(string bah); ... } public class Program { public void Run(IEngine engine){ while(true){ string command = GetLine(); if (command.startsWith("foo")){ engine.foo(); } else if (command.startsWith("bar")){ engine.bar(command); ... else break; }
In other words, I want to provide the same UI across multiple implementations of the engine that actually processes the commands.
class IEngine a where foo :: a -> String bar :: a -> String -> String run :: IEngine a => a -> IO () run x = interact (unlines . processCommand x . lines) processCommand e c | "foo" `isPrefixOf` c = foo e | "bar" `isPrefixOf` c = bar e c That should about do it. Bob

Thomas Davie wrote:
Andrew Wagner wrote:
Hi all, public interface IEngine { void foo(); void bar(string bah); ... } public class Program { public void Run(IEngine engine){ while(true){ string command = GetLine(); if (command.startsWith("foo")){ engine.foo(); } else if (command.startsWith("bar")){ engine.bar(command); ... else break; }
In other words, I want to provide the same UI across multiple implementations of the engine that actually processes the commands.
class IEngine a where foo :: a -> String bar :: a -> String -> String
You don't even need a type class, a simple data type is enough. data Engine = Engine { foo :: IO (), bar :: String -> IO () } run e = processCommand e =<< getLine processCommand e c | "foo" `isPrefixOf` c = foo e >> run e | "bar" `isPrefixOf` c = bar e c >> run e | otherwise = return () This always works because all object methods expect a "self" argument. In other words, all type class translations of OOP classes have the form class IFoo a where method1 :: a -> ... method2 :: a -> ... ... See also http://www.haskell.org/haskellwiki/Existential_type#Using_constructors_and_c... Regards, H. Apfelmus

You don't even need a type class, a simple data type is enough.
Very true, but I disagree that you've made it functional in any way, IO is all about sequencing things, it's very much not a functional style
data Engine = Engine { foo :: IO (), bar :: String -> IO () }
run e = processCommand e =<< getLine
processCommand e c | "foo" `isPrefixOf` c = foo e >> run e | "bar" `isPrefixOf` c = bar e c >> run e | otherwise = return ()
This is much nicer done as functions from String -> String, it becomes much more compassable, removes a sequential style from your code and stops processCommand depending on always working with the "run" function making it a bit more orthogonal. data Engine = Engine {foo :: String, bar :: String -> String} run e = unlines . map (proccesCommand e) . lines processCommand e c | "foo" `isPrefixOf` c = foo e | "bar" `isPrefixOf` c = bar e c | otherwise = "" Bob

Thomas Davie wrote:
You don't even need a type class, a simple data type is enough.
Very true, but I disagree that you've made it functional in any way, IO is all about sequencing things, it's very much not a functional style
data Engine = Engine { foo :: IO (), bar :: String -> IO () }
This is much nicer done as functions from String -> String
Sure, I agree. I was just replicating foo and bar from the OP because I don't know what kind of effect he had in mind. I mean, instead of merely mapping each command in isolation, he could want to accumulate a value or read files or something. Regards, H. Apfelmus

On 5 Dec 2008, at 16:42, Apfelmus, Heinrich wrote:
Thomas Davie wrote:
You don't even need a type class, a simple data type is enough.
Very true, but I disagree that you've made it functional in any way, IO is all about sequencing things, it's very much not a functional style
data Engine = Engine { foo :: IO (), bar :: String -> IO () }
This is much nicer done as functions from String -> String
Sure, I agree. I was just replicating foo and bar from the OP because I don't know what kind of effect he had in mind. I mean, instead of merely mapping each command in isolation, he could want to accumulate a value or read files or something.
Sure, and he could then use a fold instead of a map. Reading files is problematic, but as long as you're only doing it once (the most common situation) is entirely fine wrapped up in an unsafePerformIO. Either way, the question was how to do it functionally, and to do it functionally, and with all of the nice shiny benefits we get with functional code like composibility and orthogonality, you need to do it with String -> String. Bob

On Fri, 2008-12-05 at 16:50 +0100, Thomas Davie wrote:
Sure, and he could then use a fold instead of a map. Reading files is problematic, but as long as you're only doing it once (the most common situation) is entirely fine wrapped up in an unsafePerformIO.
Nooooo! Please don't go telling people it's entirely fine to use unsafePerformIO like that (or at all really). Duncan

On 5 Dec 2008, at 17:00, Duncan Coutts wrote:
On Fri, 2008-12-05 at 16:50 +0100, Thomas Davie wrote:
Sure, and he could then use a fold instead of a map. Reading files is problematic, but as long as you're only doing it once (the most common situation) is entirely fine wrapped up in an unsafePerformIO.
Nooooo!
Please don't go telling people it's entirely fine to use unsafePerformIO like that (or at all really).
Exactly what isn't fine about it? Bob

On Fri, 2008-12-05 at 17:06 +0100, Thomas Davie wrote:
On 5 Dec 2008, at 17:00, Duncan Coutts wrote:
On Fri, 2008-12-05 at 16:50 +0100, Thomas Davie wrote:
Sure, and he could then use a fold instead of a map. Reading files is problematic, but as long as you're only doing it once (the most common situation) is entirely fine wrapped up in an unsafePerformIO.
Nooooo!
Please don't go telling people it's entirely fine to use unsafePerformIO like that (or at all really).
Exactly what isn't fine about it?
It's the antithesis of pure functional programming. It's so unsafe that we don't even have a semantics for it. One needs pretty special justification for using unsafePerformIO and such cases should be hidden in libraries presenting pure interfaces, not used willy-nilly in general application code. Note that I'm not claiming that it's necessarily going to do bad things in the specific case you're imagining using it in. However just because it happens not to do bad things in this case does not mean that it's ok to use it here or in general. Duncan

On Fri, 2008-12-05 at 17:06 +0100, Thomas Davie wrote:
On 5 Dec 2008, at 17:00, Duncan Coutts wrote:
On Fri, 2008-12-05 at 16:50 +0100, Thomas Davie wrote:
Sure, and he could then use a fold instead of a map. Reading files is problematic, but as long as you're only doing it once (the most common situation) is entirely fine wrapped up in an unsafePerformIO.
Nooooo!
Please don't go telling people it's entirely fine to use unsafePerformIO like that (or at all really).
Exactly what isn't fine about it?
It's the antithesis of pure functional programming. It's so unsafe that we don't even have a semantics for it. Yes, but we also don't have semantics for IO, so it's no real surprise
On 5 Dec 2008, at 17:46, Duncan Coutts wrote: that we have none for something that runs an IO action.
One needs pretty special justification for using unsafePerformIO and such cases should be hidden in libraries presenting pure interfaces, not used willy-nilly in general application code.
Note that I'm not claiming that it's necessarily going to do bad things in the specific case you're imagining using it in. However just because it happens not to do bad things in this case does not mean that it's ok to use it here or in general.
No, and I never said that it should be used more generally -- I was very careful that in this case I was following the rules for making sure that verifyItsSafeYourselfPerformIO was indeed safe. Bob

On Fri, 2008-12-05 at 18:50 +0100, Thomas Davie wrote:
On 5 Dec 2008, at 17:46, Duncan Coutts wrote:
On Fri, 2008-12-05 at 17:06 +0100, Thomas Davie wrote:
On 5 Dec 2008, at 17:00, Duncan Coutts wrote:
On Fri, 2008-12-05 at 16:50 +0100, Thomas Davie wrote:
Sure, and he could then use a fold instead of a map. Reading files is problematic, but as long as you're only doing it once (the most common situation) is entirely fine wrapped up in an unsafePerformIO.
Nooooo!
Please don't go telling people it's entirely fine to use unsafePerformIO like that (or at all really).
Exactly what isn't fine about it?
It's the antithesis of pure functional programming. It's so unsafe that we don't even have a semantics for it. Yes, but we also don't have semantics for IO, so it's no real surprise that we have none for something that runs an IO action.
We don't have a (denotational) semantics for Haskell, either. But, in principle, one could be produced if desired. The pure subset is easy; IO only requires a semantics for the underlying OS and --- in the presence of the FFI --- the C language. Unless you add unsafePerformIO or evaluate to the language. I think that, if someone was sufficiently motivated to work out the details, that you could prove that there is *no* denotational semantics for IO which admits either function without changing the definition of one or more pure Haskell types. jcc

G'day all. Thomas Davie wrote:
class IEngine a where foo :: a -> String bar :: a -> String -> String
"Apfelmus, Heinrich"
You don't even need a type class, a simple data type is enough.
data Engine = Engine { foo :: IO (), bar :: String -> IO () }
There is a tradeoff here, that you should be aware of. 1. Using typeclasses. Pro: It works with the SPECIALIZE pragma. Pro: You can put arbitrary data in the concrete data type which is the instance of IEngine. (If you don't, incidentally, this is called a "traits typeclass".) Con: You can't generate instances of IEngine dynamically at run-time (at least without using unportable unsafeCast# magic). So you're limited to only those implementations that you (possibly dynamically) link in. 2. Using records. Pro: Usually simpler, and using fewer lines of code. Pro: You can generate new "instances" at will, and you're not limited to that which you link in. Con: Usually more explicit arguments passed around. Con: If your methods involve polymorphism, then the record will turn out to have a higher-rank type, which isn't valid Haskell 98.
This always works because all object methods expect a "self" argument.
That's not true for OO languages which have virtual constructors. Haskell typeclasses support virtual constructors/factory methods just fine, because the "self" type can appear anywhere in the signature, including being the return value. The monad "return" method is one example of this. Cheers, Andrew Bromage

In other words, I want to provide the same UI across multiple implementations of the engine that actually processes the commands.
You can ofc make my reply somewhat more functional by removing the interact from run, and just making it have type IEngine a => a -> String -> String. That way it will be nice and functional and composable and fit into a larger system. Bob
participants (6)
-
ajb@spamcop.net
-
Andrew Wagner
-
Apfelmus, Heinrich
-
Duncan Coutts
-
Jonathan Cast
-
Thomas Davie