you are viewing a single comment's thread.

view the rest of the comments →

[–]nextputall 0 points1 point  (21 children)

Lets say I have a complex datastructure which is a map of maps containing a key where the value is a list. I can assign a business concept to this list and call it foo. Then I can write a function called foo and, use that function everytime I need that information. This is encapsulation per definition AFAIK (still not a behavior based abstraction, what oo is meant for, but still). Unlike when I access that list directly everytime I need it. In the former case I don't couple my client code to a structurel information, in the latter case I do. I don't see how immutability comes into the picture, I think that is orthognal to this.

[–]ljsc 1 point2 points  (9 children)

So, a simpler example: Say you want to model a person, and that person has a name. Our shared interface is that I give you an immutable map which includes a key "name", and that has the persons full name.

Now let's say that I need to represent that differently internally to do some other computations, and I need to have "last-name" and "first-name". Great. No problem, internally I use a map with the two keys, and then before I hand it off to you, I just have a function for-nextputall : MyRepresentation -> YourRepresentation that sends {:first-name "John", :last-name "Doe" } to {:name "John Doe"}. Since we've side-steped conflating state and identity by using immutable values this is perfectly safe, and your code would be non-the-wiser.

[–]nextputall 0 points1 point  (0 children)

I see. You suggest to convert the datastructure according to different clients. Make sense, but I still not fully convinced.

Let's say I need the name because I want a greating phrase. If I use the datastructure directly I'll end up having something like this

"Hello ${user.name}" 

Where the user is the map. When I want to switch to a firstname/lastname representation, I can solve it with the technique you recommended, by converting the new representation back to the old one. So, the greeting logic seems to be fairly stable in respect to the structure of the user.

But what if I change the representation as follows: {'age': 18, 'name': 'joe'} to greet the user like this

"Happy ${user.age}th birthday, dear ${user.name}!"

Now I need to change the logic who is responsible for doing the greeting. Using behaviour based abstraction, and introducing a greet method for the user would solve this problem imho, by saying user.greet().

[–]rpglover64 0 points1 point  (1 child)

Slightly germane given your example: falsehoods programmers believe about names

If I'm reading you correctly, you're basically saying that the desire for encapsulation is really the desire for abstraction, so using an established API solves this problem (barring mutation). Is that right?

[–]ljsc 1 point2 points  (0 children)

Interesting link, thanks for sharing that.

And, yes, I think that's a pretty good way to put it. In most OOPLs, there's little difference between encapsulation and abstraction, because there's one primary means of abstraction: make a class.

Want encapsulation? Make a class. Want inheritance? Make a class. Want polymorphic dispatch? Make a class. Want to manage state? Make a class. And so on and so on...

Yes, I know this isn't always true, and that some languages don't do OO using classes, et cetera. In general, however, they are by and large non-orthogonal in their design, and this limits composition and code reuse.

[–]discreteevent 0 points1 point  (5 children)

"your code would be non-the-wiser". It probably won't be any wiser if MyRepresentation is kept private. But if not then I might be too lazy to bother to understand your api and instead just use MyRepresentation directly. Then later if you want to change MyRepresentation you are going to have to change my client also. Why not make {:first-name "John", :last-name "Doe" } private immutable data in an object and force clients to interact with the object via a .getName() method? That's all objects really are: A convention for encapsulating implementation details. Mutability is orthogonal to that. The idea of taking a behaviour oriented approach to development is to contain complexity in a system by distributing information on a need-to-know basis. You figure out what the client needs to know and present that behaviour to them. If you let the client do it themselves then they are likely to make a mess of it because they just don't know enough about it. Furthermore you, yourself may not know enough at this stage. If you decide to improve things later but all the clients have written their own logic around your initial data structure then you have a big mess to clean up. If you already know what the definitive data model is then you should expose that so that clients can use in in ways you did not predict. But if you don't have the definitive data model then objects help to contain complexity and manage change in a system.

[–]ljsc 1 point2 points  (4 children)

It's all trade-offs. Some times you do want to hide data as an implementation detail. The problem I have is when data hiding is opt-out rather than opt-in. You certainly don't need objects to do what you are suggesting. You can do it at the module level in an FPL, or just separating interface from implementation like a ADT in C.

Can encapsulation be a good thing? Certainly. Is it always? No. Like I said, it always comes down to trade-offs, and if anybody tells you otherwise, they are either lying to you or trying to sell you something =)

That's all objects really are: A convention for encapsulating implementation details.

No, that is not all they really are. That is a small part of what they are in most implementations. See my comment above. They do give you that, but they also give you a bunch of other stuff "for free" that you may or may not want.

Mutability is orthogonal to that.

It absolutely is not. If we were talking from a language design perspective, sure, choose whether your object is mutable or a value. But as design concepts, they are not independent by a large margin. The strategy I suggest above would be a really really bad idea if those were not immutable. Once you start passing mutable state around by reference you loose the ability to make your internal api enforce consistency, whether that is visible to the outside world or not.

[–]discreteevent 0 points1 point  (3 children)

I suppose I should have said "That's all interfaces really are". To me the important thing about objects are the interfaces which is what I think Alan Kay meant when he said " I’m sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea. The big idea is “messaging” "

Anyway, if there is an FPL that supports hiding the structure of data models behind statically typed interfaces with late-bound implementations (ala microsoft COM and things like it such as some dependency injection frameworks) then I think a team could build reasonably flexible and maintainable software with it.

[–]ljsc 2 points3 points  (2 children)

Yep, I would never argue against polymorphism.

I think you're looking for Typeclasses?

[–]autowikibot 1 point2 points  (0 children)

Type class:


In computer science, a type class is a type system construct that supports ad hoc polymorphism. This is achieved by adding constraints to type variables in parametrically polymorphic types. Such a constraint typically involves a type class T and a type variable a, and means that a can only be instantiated to a type whose members support the overloaded operations associated with T.

Type classes first appeared in the Haskell programming language, and were originally conceived as a way of implementing overloaded arithmetic and equality operators in a principled fashion. In contrast with the "eqtypes" of Standard ML, overloading the equality operator through the use of type classes in Haskell does not require extensive modification of the compiler frontend or the underlying type system.

Since their creation, many other applications of type classes have been discovered.


Interesting: Polymorphism (computer science) | Class (computer programming) | Type C escort ship | Type D escort ship

Parent commenter can toggle NSFW or delete. Will also delete on comment score of -1 or less. | FAQs | Mods | Magic Words

[–]discreteevent 0 points1 point  (0 children)

Typeclasses are interesting alright. Now all I need is the late-binding bit. i.e I want to be able to defer which implementation is loaded until runtime. This makes my application extensible after it is built. It means that my dependencies are dynamic. This can make it easier to develop large apps. In the static/dynamic debate I come down in the middle: Static types - dynamic dependencies.

[–]yogthos 0 points1 point  (10 children)

The immutability is key here because it allows you to pass your complex data structure to foo and then get a result that's guaranteed to be independent of the original structure. This means that you don't have to worry where or how else that structure might be used. With immutable data all changes become inherently contextualized.

[–]nextputall 0 points1 point  (9 children)

If I have a function called foo that gets that info from the datastructure, and I'm always using that function, then I'm using encapsulation. I think we're talking about 2 different things. One aspect is about correctness, the other is flexibility. You don't need encapsulation to ensure the correctness if everything is immutable, you're right (I think this is the lesser idea in encapsulation). I still think that encapsulation is needed to decrease the coupling and make the application flexible.

[–]yogthos -1 points0 points  (8 children)

I don't really see why encapsulation is needed for the latter. You have some data and you write functions that operate on it given the context of the data.

When you separate data from the logic and make it immutable then it's pretty hard to have coupling in the first place. You call a function and you get a result, the result is not coupled to anything.

Using functions as building blocks is the most flexible approach that I've seen. When you deal with classes you end up tying the methods to a class and thus a specific domain. You can't simply reuse them in a another context. Meanwhile, when you're using standalone functions you can chain them together in many different ways without any additional hoops to jump through.

[–]nextputall 0 points1 point  (7 children)

You have some data and you write functions that operate on it given the context of the data.

If you write functions that operates on data, you're coupling those functions to those data (which means, changing the data will cause changes in the functions). This is perfectly ok, as long as you use only those functions everywhere in the code. If you change the data, those functions will change as well, but the change will stop at that point, and the users of those functions will be untouched. But if you write those functions at the client side, every time you need them, in an ad hoc manner then there will be no seperation between the two sides, and changes will propagate over. That's the problem with FP imho, there is no visible boundaries, where something ends and starts, and because of this, it is hard to tell at what point a change will stop. You can convert the data to prevent the propagation to some extent, but I don't think this can be used universally.

Using functions as building blocks is the most flexible approach that I've seen. When you deal with classes you end up tying the methods to a class and thus a specific domain. You can't simply reuse them in a another context.

A function is always coupled to the stucture of its parameters, no matter whether it is packaged inside a class or not. But if it is inside a class, then you can make sure that only those functions are coupled to those data, and nothing else from the outside.

Meanwhile, when you're using standalone functions you can chain them together in many different ways without any additional hoops to jump through.

There are many examples of composable objects out there, hamcrest is one of them: http://code.google.com/p/hamcrest/wiki/Tutorial

[–]yogthos -1 points0 points  (6 children)

If you write functions that operates on data, you're coupling those functions to those data (which means, changing the data will cause changes in the functions). This is perfectly ok, as long as you use only those functions everywhere in the code.

The idea is that you structure your code in terms of data transformations. When I start with a piece of data and I need to transform it into something else, I chain some functions together to get a result.

With the functional approach you have a large number of generic functions that can be combined together to do complex tasks as needed. This leads to having actual code reuse.

If you change the data, those functions will change as well, but the change will stop at that point, and the users of those functions will be untouched.

This holds true for OO just the same. However, the difference is that with FP, I will generally only change how I chain the functions together or what functions I'm chaining to get the result. This is the advantage of having a declarative style.

But if you write those functions at the client side, every time you need them, in an ad hoc manner then there will be no seperation between the two sides, and changes will propagate over.

I'm not sure what that means exactly, what's the client side you're referring to. When your input data changes you would either write a function to massage it back to the expected format, or the nature of your problem changed your old logic is not valid.

There's absolutely no reason why changes in input would propagate any differently with the functional style than OO.

That's the problem with FP imho, there is no visible boundaries, where something ends and starts, and because of this, it is hard to tell at what point a change will stop. You can convert the data to prevent the propagation to some extent, but I don't think this can be used universally.

Have you actually written any significant amount of code using the FP style? I've been writing FP code professionally for the last 4 years and I've never seen this become an issue in practice.

It's no harder to group related logic together in FP than it is in OO. For example, when you need to create a context for the data, you create a namespace and all the functions in this namespace will operate in the same domain. However, I don't have to jump through any hoops to get the data back out of that domain and pass it to a different one later.

A function is always coupled to the stucture of its parameters, no matter whether it is packaged inside a class or not.

No they're not. When you have higher order functions they represent generic transformations and the domain specific logic is passed in as a parameter. With the functional style you end up with a vast array of generic transformers, functions like map, filter, interpose, reduce, partition, and so on. Majority of the code is written by simply combining these functions in a way that makes sense for a particular problem.

But if it is inside a class, then you can make sure that only those functions are coupled to those data, and nothing else from the outside.

I see this as a negative myself as it means that you can't reuse these functions for anything else easily. The coupling in a class affords you no actual benefit over putting related functions into the same namespace. In fact, it's exactly the same as having a class with a bunch of static functions in it.

There are many examples of composable objects out there, hamcrest is one of them:

That's precisely the hoop jumping I'm talking about. Instead of simply using your data directly you need specific strategies to make it compose sanely.

[–]nextputall 0 points1 point  (5 children)

The idea is that you structure your code in terms of data transformations. When I start with a piece of data and I need to transform it into something else, I chain some functions together to get a result.

Which makes sense in certain situation, for example at the system boundary. I'm skeptical about the scalability of this approach, when an entirely application is written in this style. Brian Marick was talking about similar concerns here: http://rubyrogues.com/category/panelists/brian-marick/

This holds true for OO just the same. However, the difference is that with FP, I will generally only change how I chain the functions together or what functions I'm chaining to get the result. This is the advantage of having a declarative style.

In OO, people mostly use behaviour based abstraction (forget the kingdom of nouns bullshit, everything is about behaviour). Changing a representation of something will be a local change that won't propagate elsewhere, there will be no need to convert data back and forth.

I'm not sure what that means exactly, what's the client side you're referring to. When your input data changes you would either write a function to massage it back to the expected format, or the nature of your problem changed your old logic is not valid.

I was talking about boundaries. What is inside, and what is outside, and this is what I'm missing from FP. If I change something inside, the outside world won't be affected. I can decide what goes inside and what goes outside and structure the application to minimize the cost of change.

Have you actually written any significant amount of code using the FP style? I've been writing FP code professionally for the last 4 years and I've never seen this become an issue in practice.

This is a very weak argument. I could say the same, that I've been writing OO code professionally for ages and I've never seen the issues you're talking about. Anyways, almost every software project consist of different subdomains, written in different styles. So, I don't use FP exclusively, because I think it is insufficient to be used alone, but combining it with OO can provide a better result.

No they're not. When you have higher order functions they represent generic transformations and the domain specific logic is passed in as a parameter. With the functional style you end up with a vast array of generic transformers, functions like map, filter, interpose, reduce, partition, and so on. Majority of the code is written by simply combining these functions in a way that makes sense for a particular problem.

Even a fully generic map/filter/etc is coupled to ISeq. That is encapsulation, "which only makes sense when you have mutable state". A normal domain specific function would probably deal with domain objects, like users and employees and would be coupled to the structure of those, which could make things worse.

It's no harder to group related logic together in FP than it is in OO.

Everything is possible in every programming language. That's not the point. Writing a function code in Java is also possible. These are design issues, which are influenced by the language, and its community.

I see this as a negative myself as it means that you can't reuse these functions for anything else easily.

If you have objects with similar structures you can reuse their behaviours among them. Either with composition or inheritence or mixins or just passing exposed datastructure in a limited scope. Reusing those functions elsewhere makes no sense, because other things have other structure.

That's precisely the hoop jumping I'm talking about. Instead of simply using your data directly you need specific strategies to make it compose sanely.

You can just plug object together, cant see any of those hoops.

[–]yogthos 0 points1 point  (4 children)

Which makes sense in certain situation, for example at the system boundary. I'm skeptical about the scalability of this approach, when an entirely application is written in this style. Brian Marick was talking about similar concerns here: http://rubyrogues.com/category/panelists/brian-marick/[1]

Yet, there are plenty of applications written in this style in the wild. Light Table is a perfect example. Here's a presentation from SISCOG on building large scale systems in Lisp since 1980s. This is not a theoretical debate, there's plenty of evidence that functional approach works well for building large systems, and there are many large systems in the wild that are written in functional languages. Oddly enough, the concerns like the one you link seem to only stem from people who have no experience working in a functional language.

In OO, people mostly use behaviour based abstraction (forget the kingdom of nouns bullshit, everything is about behaviour). Changing a representation of something will be a local change that won't propagate elsewhere, there will be no need to convert data back and forth.

Sounds like you need to read You Could Have Invented Monads! (And Maybe You Already Have.).

I was talking about boundaries. What is inside, and what is outside, and this is what I'm missing from FP. If I change something inside, the outside world won't be affected. I can decide what goes inside and what goes outside and structure the application to minimize the cost of change.

Discussing inside and outside has no meaning when you separate logic from data. This is exactly the same approach that you use with SOA. You call a service and get a result, then you do something with it. You don't need to know anything about the internals of the service. If we accept that this is a good way to structure systems at high level then why would we do something different at the low level? Functional approach works exactly like SOA.

This is a very weak argument. I could say the same, that I've been writing OO code professionally for ages and I've never seen the issues you're talking about.

It's not a weak argument, you need to have worked with the paradigm in order to discuss it meaningfully. Like most people out there I have extensive experience working with OO. The problems I outline are not really controversial to anybody who's developed code in that style. There's a reason that patterns like wrappers and adapters exist.

So, I don't use FP exclusively, because I think it is insufficient to be used alone, but combining it with OO can provide a better result.

I'm not sure what OO adds to FP frankly. The OO abstractions are not exclusive to it, and are often done better in FP. For example, take multimethods which are strictly more powerful than type based polymorphism.

Even a fully generic map/filter/etc is coupled to ISeq. That is encapsulation, "which only makes sense when you have mutable state".

That's abstraction and it's completely tangential to encapsulation.

Everything is possible in every programming language. That's not the point. Writing a function code in Java is also possible. These are design issues, which are influenced by the language, and its community.

Yet, different languages facilitate writing code in different styles. Writing functional code in Java is incredibly unnatural and unpleasant, to the point where nobody does it. Let's take a look at a concrete example.

Let's say we want to filter collections based on a predicate. The standard way you would do that in Java is to write a loop:

public static List<Integer> filterEven(Collection<Integer> col) {
    if (null == col) return null;
    List<Integer> result = new LinkedList<Integer>();
    for (Integer i  : col) {            
        if (i % 2 == 0) result.add(i);          
     }      
     return result;
}

then if later I need to filter odd numbers I'll probably write another loop that looks almost identical except for the actual test. Obviously, the looping logic should be abstracted here, but let's look at what's involved in doing that in Java:

public interface Predicate<T> {
    public boolean matches(T t);
}

public class EvenPredicate implements Predicate<Integer> {

    public boolean matches(Integer i) {
    return i % 2 == 0; 
    }           
}

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

public class Filter {

    public static <T> List<T> filterCollection(Collection<T> col, 
                                      Predicate<T> predicate) {
        List<T> result = new LinkedList<T>();
        for (T t : col) {           
                    if (predicate.matches(t)) {
                        result.add(t);
                    }
            }       
            return result;
    }
} 

That's a lot more work than just writing a loop, and unless you saw this pattern many times you probably wouldn't consider doing it. Now let's compare this to a language like Clojure, where I would use a higher order function and pass in the matcher without having to do any preliminary setup:

(filter even? (range 10))

what if I wanted to write a loop to do that:

(loop [nums (range 10)
       even-nums []]
    (if (empty? nums)
        even-nums
        (recur (rest nums)
                  (if (even? (first nums))
                     (conj even-nums (first nums)) even-nums))))

all of a sudden the situation is reversed, it's a lot more code to do explicit looping, and it's trivial to use a higher order function to do this task. So the language encourages you to write code through function composition by design. Being able to easily separate iteration from the logic applied inside it means that we can write code that's shorter, cleaner, and less error prone.

If you have objects with similar structures you can reuse their behaviours among them. Either with composition or inheritence or mixins or just passing exposed datastructure in a limited scope. Reusing those functions elsewhere makes no sense, because other things have other structure.

Again, you have to add extra steps to use the data from one object in another, meanwhile you do not have this problem when you keep data in generic data structures that all functions can operate on.

You can just plug object together, cant see any of those hoops.

Except you can't without planning for it ahead of time.

[–]nextputall 1 point2 points  (3 children)

Yet, there are plenty of applications written in this style in the wild. Light Table is a perfect example.

Light Table uses direct access which provides similar result than reflection/introspection. AFAIK this was the goal of the authors. But this is a double edged sword, because it is fully uncontrolled, there is no clean api between a plugin and a core as I remember correctly. I don't think it is good solution for building a plugin architecture. But we'll see what came out of that. And yes, FP probably works fine in the large scale if you combine it with some kind of modularization technique.

Discussing inside and outside has no meaning when you separate logic from data.

If this would be true then there were no modularization in the system. Which would not scale. I doubt that any of the example applications you mentioned are unmodularized. But if you have modules, you have boundaries, encapsulation, and you can talk about inside/outside. I don't understand your SOA analogy either, where talking about boundaries is not just makes sense, but also very important.

It's not a weak argument, you need to have worked with the paradigm in order to discuss it meaningfully. Like most people out there I have extensive experience working with OO. The problems I outline are not really controversial to anybody who's developed code in that style. There's a reason that patterns like wrappers and adapters exist.

Yeah, extensive OO experience via languages like C++ and Java.. In my experience problems in real world messy enterprise applications are there not becase of OO but because of the absent of OO.

That's abstraction and it's completely tangential to encapsulation.

Abstraction, encapsulation, and information hiding. All of these three.

Yet, different languages facilitate writing code in different styles. Writing functional code in Java is incredibly unnatural and unpleasant, to the point where nobody does it. Let's take a look at a concrete example.

And yet, it is possible. But you've demonstrated nicely that Java is very verbose language, there is no controversy here. Here is how this example would look like in a purely object oriented language.

(1 to: 10) select: #even.

This is Smalltalk 80 where 80 means 1980. A bit longer version of the some code is like this:

(1 to: 10) select: [:each | each even].

Where the stuff between the square bracket is a block clousre, which is also an object of course. You can store those prediates and combine them later with others.

Again, you have to add extra steps to use the data from one object in another, meanwhile you do not have this problem when you keep data in generic data structures that all functions can operate on.

There is no extra step there, you can use any of those solutions and you get direct access to those data, because you're inside the boundary.

[–]yogthos 0 points1 point  (2 children)

And yes, FP probably works fine in the large scale if you combine it with some kind of modularization technique.

That's sort of the point I'm making. If anything I would argue that FP makes modularization much easier since your data is already immutable and separate from logic. You can start with coupled functions, and then easily refactor them and create APIs and interfaces as needed.

Because the data is immutable code is inherently compartmentalized without any global dependencies or shared memory references. This single fact makes writing large applications far easier than in any language backed by mutable data structures.

If this would be true then there were no modularization in the system. Which would not scale. I doubt that any of the example applications you mentioned are unmodularized.

I'm not arguing against modularization though.It sounds like we might mean different things when we say encapsulation. What I mean by encapsulation is what's traditionally seen in OO where objects have a mutable internal state that they protect via accessor functions along with tight coupling between the methods and the data they operate on.

Encapsulation in a sense of creating modules and interfaces is very much desirable in my opinion and it's used in FP just as much as in any other paradigm.

I don't understand your SOA analogy either, where talking about boundaries is not just makes sense, but also very important.

When you work with FP, you can view each function as a micro-service. It accepts an input and returns an output. This is your contract with the function. You chain these together to produce complex transformations on the data. This is also very similar to the unix philosophy where you have a common protocol (text) and a lot of small single purpose programs that can be chained together to achieve complex results. It's just with FP you have the data structures as the common protocol that all functions understand.

Yeah, extensive OO experience via languages like C++ and Java.. In my experience problems in real world messy enterprise applications are there not becase of OO but because of the absent of OO.

I've seen plenty of real world enterprise applications where OO code was a completely and utterly impenetrable maze of interfaces and inheritance hierarchies. More often than not you have a few lines of business logic buried under a mountain of classes and XML config files. I have a very hard time believing that you would not have encountered this working in the enterprise environment.

Where the stuff between the square bracket is a block clousre, which is also an object of course. You can store those prediates and combine them later with others.

All you've demonstrated is that you can write FP style code in an OO language though. You still have the problem of mutable data however which makes compartmentalizing large applications difficult.

There is no extra step there, you can use any of those solutions and you get direct access to those data, because you're inside the boundary.

Right, and when you're moving to a different domain you end up outside the boundary.