6 ms·
> when I hear "mock object" it's time to run Do Haskell programmers not create mocks to test external components? > OOP is the wrong-minded idea that a progra
by mgummelt 8y ago
> when I hear "mock object" it's time to run
Do Haskell programmers not create mocks to test external components?
> OOP is the wrong-minded idea that a program should be a bundle of many "self-contained" objects. But that's wrong, we're writing ONE program here, not thousands.
The number of programs isn't the relevant metric. Complexity is. Any complex system is going to trend toward modularity. Modularity requires standard interfaces, which inevitably lead to bureaucracy.
A 1MM line Haskell program is going to be similarly bureaucratic. There are going to be standards you have to adhere to in order to play nice with the rest of the system. That's what typeclasses are, after all.
OOP is traditionally defined by three things: polymorphism, encapsulation, and inheritance.
Polymorphism: Modern Non-OOP languages can also be polymorphic, so that's no longer a differentiator.
Encapsulation: You definitely want encapsulation if your data is mutable.
Inheritance: This is the only truly problematic feature, and it's certainly abused, but it has its place. I don't always want to compose and delegate 20 methods when I just want to change the behavior of one.
- tathougies 8y ago> Polymorphism: Modern Non-OOP languages can also be polymorphic, so that's no longer a differentiator. Haskell had ad-hoc polymorphism way before Java was a twinkle in its creator's eye. Before Haskell, Miranda (the language Haskell was based off of) could have kicked Java's polymorphism to the curb. Neither Java nor OOP invented polymorphism. If anything, they butchered it by introducing subtyping. > Do Haskell programmers not create mocks to test external components? The equivalent in Haskell would be having some kind of 'effects' system. An effect system differs from a mock object in that it limits in its totality what kind of interactions can take place. Typically, each layered effect also has a set of laws. Pure interpreters can be written for these effects, but the impure (i.e., real-world) interpreters are not privileged in their consumption of this effect. The pure interpreter also provides a proper implementation, such that you should be able to replace your real program with all pure interpreters, supply all your input at once, and still have a correct program. In other words, a Haskell program is typically polymorphic over which effects it uses in a way that other languages simply aren't. > Encapsulation: You definitely want encapsulation if your data is mutable. Again, Haskell, Miranda, and Lisp had encapsulation long before OOP came about, and Lisp has mutable data.
- mgummelt 8y agoI think we're in violent agreement here. AFAICT, the feature sets of OOP and non-OOP languages have converged so much that inheritance is really the last differentiator. Maybe you could throw dynamic dispatch in there, but there's no reason in principle an OOP language couldn't add dynamic dispatch.
- macintux 8y agoMutability is a huge differentiator for some languages. And dynamic dispatch is already part of Smalltalk and Objective-C, I believe.
- mgummelt 8y agoSure, but mutability is orthogonal to OOP.
- macintux 8y agoAs fond as I am of Erlang, I still find it hard to picture an immutable OO language.
- mgummelt 8y agoWrite in scala using only "vals" and there you have it!
- lgas 8y agoNot quite because you can still have a val that points to a mutable object.
- kbp 8y ago> I still find it hard to picture an immutable OO language. Picture an object-oriented procedural language like Java, and then make everything immutable; for every 'void' method, return an updated copy of 'this'; for every non-void function, return a tuple of the updated copy and the value you were going to return. No change to const methods, obviously. And you're done, and you can still take advantage of encapsulation, polymorphism, and inheritance without any hoops. You can even do it in Java itself as a style thing without too much effort and only a modest amount of boilerplate. Alternatively, it's not that much work to build this same thing out of lambdas and dictionaries, if your language has those but not objects (adding mutation into that object system would then be trivial) (and of course, you can build dictionaries out of lambdas and lambdas out of objects, if need be).
- lmm 8y ago> Do Haskell programmers not create mocks to test external components? Do we create test substitutes, alternate implementations of the same interfaces? Yes. But dedicated mocking frameworks are crazy. In Haskell-like languages if you want an implementation of interface foo that returns bar when called with baz, you just... write an implementation of interface foo that returns bar when called with baz. If the easiest way to do that in your language is some kind of magical reflection-based framework, something is very wrong with your language.
- mavelikara 8y ago> If the easiest way to do that in your language is some kind of magical reflection-based framework, something is very wrong with your language. Languages have strengths and weaknesses. Certain tasks are easy in some languages, and certain other tasks are not. Throwing ones hands up and saying "something is very wrong with your language" because one is not familiar with a technique or tooling popular in another language is immature, IMO. For example, if you explain how Debug.Trace works in Haskell to programmers familiar with Java, and they'd call it crazy.
- lmm 8y ago> Languages have strengths and weaknesses. Certain tasks are easy in some languages, and certain other tasks are not. Agreed, but we mustn't fall into the fallacy of assuming that means no language can ever be better or worse than another. There are good and bad language design choices, and "an implementation of interface foo that returns bar when called with baz" is not some obscure specialized feature, it's the basics of general-purpose programming. > Throwing ones hands up and saying "something is very wrong with your language" because one is not familiar with a technique or tooling popular in another language is immature, IMO. I'm very familiar with the techniques and tooling of mocking frameworks. I do not make these claims lightly.
- mavelikara 8y ago> Agreed, but we mustn't fall into the fallacy of assuming that means no language can ever be better or worse than another. There are good and bad language design choices. Agreed. > "an implementation of interface foo that returns bar when called with baz" is not some obscure specialized feature It isn't some obscure specialized feature in Java either. Foo foo = new Foo() { public Bar method(Baz baz) { return new Bar("bar"); } } What mocking frameworks do is to provide a DSL to describe behavior of such implementations, use dynamic bytecode generation (not reflection, BTW) to create implementations of the interfaces dynamically, and bind them to simulate various test conditions. What the makes the language "worse" to require or allow doing this? My Haskell is rusty, but given Haskell psuedocode like: main :: IO () main = do f <- foo if (f == 1) then putStrLn "Got 1" else putStrLn "Didn't get 1" foo :: IO Int -- ... how would you test that the two branches of main behave appropriately? This is not a snark; I am truly interested to know how Haskell gets rid of the need to bind alternate implementations of an interface for testing purposes.