5 ms·
Well, I'm not sure. First, assignments are evil[1], which means you should avoid setters. Second, inheritance is evil, for similar reasons (thick interface).
by loup-vaillant 16y ago
Well, I'm not sure. First, assignments are evil[1], which means you should avoid setters. Second, inheritance is evil, for similar reasons (thick interface). Third, every time I saw mixins or class polymorphism, lambdas or sum types, would have been simpler, respectively.
Once you like lambdas, functional programming becomes more attractive than a black hole.
[1]: http://www.loup-vaillant.fr/articles/assignment http://www.loup-vaillant.fr/articles/assignment
- barrkel 16y agoSum types are not as dynamically extensible at runtime, or by third party libraries, as class polymorphism.
- loup-vaillant 16y agoFor the particular cases I saw, runtime extensibility never mattered. (Do you have one where it does?) And extending a third party sum type is not very hard: data LibType = Zero | One Int | Two Int Int data MyType = ZeroOneTwo LibType | Three Int Int Int (It of course implies that you use MyType instead of LibType in your code.) Finally, if you really want to extend a sum type, no work around allowed, you might want to look at Ocaml's variant types.
- vetinari 16y agoThe keywords were "extensible dynamically at runtime", not just "extensible, period". Runtime extensibility matters at systems that you cannot shut down, where have to upgrade live systems, for example. If you are doing just "run it once again, no big deal" application, then it does not matter, you are right.
- barrkel 16y agoThe code using LibType is code you don't control; you can't change it to use MyType instead. Indeed, you don't even have the source code to it. That's also why the extensibility happens at runtime.
- loup-vaillant 16y agoSo What? Just do what you would have done in Java without inheritance: aggregate. For each function that matters, you can write a new one that handle the extra case: new_function ZeroOneTwo x = old_function x new_function Three a b c = -- handling new case
- barrkel 16y agoI'm not sure if you're being willfully obtuse or not. The functions that matter are functions you didn't write. You don't have the source code to them. You cannot rewrite them with an extra case because you can't rewrite all the calling sites, because those are baked into executable binaries. The world of shrink-wrapped closed-source software may be a foreign world to many functional advocates, but it's the world I live in, and extensibility here is often done with polymorphism and inheritance. The code in the closed-source kernel (application, framework, whatever) interacts with values polymorphically, with modules and third parties relying on inheritance and overriding to work their behaviour into the system. You want a market for components, where both buyers and sellers are protected; sellers do not necessarily want to reveal their source code, and buyers especially don't. Binary, executable code is the medium of interchange. In order to fit these things together you need protocols: sets of expected messages and documented responses. OO polymorphic interfaces, in other words. Inheritance at the interface level is necessary, and at the implementation level it decreases the burden somewhat - using aggregation instead can lead to problems of identity (the sub-parts of an aggregate each have a different one, since they are mutable).
- loup-vaillant 16y agoFirst, I believe that the world of shrink-wrapped proprietary software should die. Second, I fail to see how inheritance solves your problem: "The code using LibType is code you don't control; you can't change it to use MyType instead." OK, let's try this with inheritance. class LibType { Handles 3 cases } class MyType extends LibType { Handles a fourth case } Now tell me: how would you make the code of the library use an object of type `MyType` instead of `LibType`? If you don't control its code, I see only one way: somewhere, this library expects an object of type `LibType` as a parameter. Interestingly, idiomatic functional programming do just that: passing functions as parameters. Or tuples of functions, in that case. You know that an object is just a tuple, right?. For instance: // Statically typed, Class based OO language class Foo { int bar(int,); float baz(float, int); int x; } -- Haskell data Foo = Foo (Int -> Int) (Float -> Int -> Int) Int -- The same, with record syntax. (for easy access) data Foo = Foo { bar :: Int -> Int baz :: Float -> Int -> Int x :: Int } Note that the functions in objects of type Foo aren't fixed. So I can override all I want. Class Polymorphism is cool, but I can do the same with mere parametric polymorphism if I really need to. Sure, the library must be designed for extensibility in the first place, but the same is true about OO libraries: inheriting from a class that isn't designed with inheritance in mind is dangerous.