The Intellectual Wilderness There is nothing more useless than doing efficiently that which should not be done at all.

2020.09.24 11:02

Erlang: Dispatching Closures (aka: why we don’t do OOP)

Filed under: Computing — Tags: , , , , , — zxq9 @ 11:02

Oh my! It’s like “Creating nouns in the kingdom of verbs“!
(The link above is to a great post from Steve Yegge — read it and come back if you aren’t already familiar with it.)

A video talk-through of the code below and a bit of discussion

I wrote a funny little module today to demonstrate to a friend why FP folks sometimes refer to OOP objects as “dispatching closures” and occasionally make quips like “objects are a poor man’s closures”. I thought it would be fun to share it and also wanted a reference page to which I can refer people who have questions because this comes up every so often when a new OOP programmer starts pondering the nature of the universe after reading some introductory FP material.

Quite a lot of extra functionality could be added to this, such as distinguishing between functions that update the state of Self and functions that don’t to alleviate the annoyance of having to ignore the NewSelf return element of any “methods” beyond the pure built-in ‘get’, but we can leave that for another day as I really don’t expect anyone in their right mind would use the module below in real world code — it just introduces complexity, mental overhead, and potential weirdness if you pass an object between two different processes (send it to a process on another node and boom!), and in Erlang funs really shouldn’t be all that long-lived anyway.

In the below code a syntactic rewrite is needed to understand how to call the methods:

Pseudo PythonOOPsy Pseudo Erlang
class MyClass:
# stuff
MyClass = class(Data, Methods)
my_object.data_elementMyObject({get, DataElement})
my_object.data_element = valueMyObject({set, DataElement, Value})
my_object.do_something()MyObject({do, Something})
my_object.update(stuff).do_something(){ok, NextObject} = MyObject({do, update, Stuff}),
NextObject({do, something})

Here is the definition bit of the code, there is also a slightly more extensive and commented snippet on GitLab that has an additional demo/0 function that demonstrates one way dispatching closures can be called, manipulated, and extended through inheritance:

-module(oop).
-author("Craig Everett <zxq9@zxq9.com>").
-export([class/2, class/3, demo/0]).


-record(s,
        {data    = #{},
         methods = #{}}).


class(Defaults, Methods) ->
    fun
        (data) ->
            maps:keys(Defaults);
        (methods) ->
            maps:keys(Methods);
        (Data) when is_map(Data) ->
            State = #s{data = maps:merge(Defaults, Data), methods = Methods},
            object(State);
        ({subclass, NewDefaults, NewMethods}) ->
            class(maps:merge(Defaults, NewDefaults), maps:merge(Methods, NewMethods))
    end.


class(Inherits, Defaults, Methods) ->
    Inherits({subclass, Defaults, Methods}).


object(State = #s{data = Data, methods = Methods}) ->
    fun
        ({get, Label}) ->
            maps:get(Label, Data);
        ({set, Label, Value}) ->
            NewData = maps:put(Label, Value, Data),
            object(State#s{data = NewData});
        ({add, Label, Method}) ->
            NewMethods = maps:put(Label, Method, Methods),
            object(State#s{methods = NewMethods});
        ({do, Label}) ->
            F = maps:get(Label, Methods),
            F(object(State));
        ({do, Label, Args}) ->
            F = maps:get(Label, Methods),
            F(object(State), Args);
        (data) ->
            maps:keys(Data);
        (methods) ->
            maps:keys(Methods)
    end.

While this is an interesting construct, it is absolutely insane that anyone thought it was so utterly and all-encompassingly important that an entire new syntax should be developed just to hide the mechanics of it, and further, than entire languages should be created that enforce that this is the One True Way and impose it on the programmer. It’s cool, but not that cool.

2015.09.29 10:19

Iterators? We Don’t NEED No Stinking Iterators!

Filed under: Computing — Tags: , , , , , , — zxq9 @ 10:19

Every so often a request for “implementation of iterators for maps” over hashes/maps/dicts or some other K-V data structure appears on mailing list for a functional langauge. I’ve spent years making heavy use of iterators in imperative languages, and the way they fit into Python is really great. For Python. I totally understand where some of these folks are coming from, they just don’t realize that functional languages are not where they came from.

So… “Is this post the result of some actual event”? Yeah, you got me. It is. On the erlang-questions mailing list someone asked “Are maps ever going to get an iterator?” Again.

Erlang is definitely not Kansas, but people thinking either that it is or (more dangerously) that it should be and then trying to influence the maintainers to make it that way (and then the powers-that-be getting in a panic over “market share” and doing horrible things to the language…) worries me a bit.

There is no faster way to paint a functional language into a corner than to try making it occasionally imperative. Conversely, consider the syntactic corner C++ and Java have painted themselves into by trying to include functional features as after-thoughts where they really didn’t belong.

(I know, I know, death-by-kitchen-sink is a proud C++ tradition. It is becoming one for Java. Even though I hate Java there is no sense in making it worse by cluttering its syntax and littering it with gotchas and newbie-unfriendly readability landmines in the interest of providing features few Java coders understand the point of, especially when the whole concept of state management in a bondage-and-discipline OOP language like Java is to keep everything in structs with legs (not anonymous closures over state that is temporarily in scope…). The lack of such problems were previously one of the main points that favored Java over C++… well, that and actual encapsulation. Hopefully Rust and D can resist this temptation.)

This frustrates me. It is almost as if instead of picking a tool that matches a given job, people learn one tool and then try over time to make a super-powered Swiss Army knife of it. This never turns out well. The result is more Frankenstein’s Monster than Swiss Army knife and in the best case it winds up being hard to learn, confusing to document and crap at everything.

What’s worse, people assume that the first tool they learned well is the standard by which everything else should be judged (if so, then why are they learning anything else?). It follows, then, that if a newly studied LangX does not have a feature of previously used LangY then it must be introduced because it is “missing”. (I do admit, though, to wishing other languages had pattern matching in function heads… but I don’t bring this up on mailing lists as if its a “missing feature”; I do, however, cackle insanely when overloading is compared with matching.)

Let’s say we did include iterators for maps into Erlang — whatever an “iterator” is supposed to mean in a list-are-conses type functional language. What would that enable?

-spec foreach(fun(), map()) -> ok.

That sort of looks pointless. Its exactly the same as lists:foreach(Fun, maps:to_list(Map)) or maybe lists:foreach(Fun, maps:values(Map)). Without knowing whether we’re trying to build a new map based on the old one or get some side effect out of Fun then its hard to know what the point is.

Maybe:

-spec map(fun(), OldMap :: map()) -> {ok, NewMap :: map()}.

But… wait, isn’t that just maps:map/2 all over again?

I think I know where this is going, though. These people really wish maps were ordered dictionaries, because they want keys to be ordered. So they want something like this:

-spec side_effects_in_order_dammit(fun(), map()) -> ok.
side_effects_in_order_dammit(F, M) ->
    Ordered = [{K, maps:get(K, M)} || K <- lists:sort(maps:keys(M))],
    ok = lists:foreach(F, Ordered).

But wait, what order should the keys be in, anyway?

This is a slow, steady march to insanity. “Give me iterators” begets “Let’s have ordered maps” begets “Let’s have ordered iterators for maps” and so on, and eventually you wind up with most of the Book of Genesis in the Devil’s Bible of Previously Decent Functional Languages. All the while, totally forgetting that these things already exist in another form. There are more data structures than just maps for a reason.

This just gets ridiculous, and it isn’t even what hashes are about to begin with.

Powered by WordPress