Hacker News new | ask | show | jobs
Software Mimicry (hillelwayne.com)
70 points by BobbyVsTheDevil 1314 days ago
5 comments

For the StrategyWithSetup example, my FP oriented brain would create another function:

    withSetup :: IO c -> IO d -> (a -> IO b) -> a -> IO b
    withSetup mc md f = \ a -> do
      mc
      b <- f a
      md
      return b
Which is pretty identical except with a closure over functions in place of a class of abstract methods. Classes with abstract methods are effectively higher order functions, except differences in boilerplate.

Another thing worth mentioning are language features that can't be implemented unless you effectively implement a DSL like automatic differentiation, call-cc, algebraic effects.

Hooks in React are sort of like a poor man's algebraic effects. It can't affect the control flow so it relies on immutability and hooks to unconditionally run to operate.

EDIT: after a bit of thinking, I realize that the unconditional ordering of hooks is really a way to identify call sites at runtime, which I don't think is related to algebraic effects.

Enjoyed this, as with most of Hillel Wayne's writing. One bit that puzzled me:

> “Representation” is similar to mimicry, except it’s about encoding a data structure in primitives that aren’t well-suited to it. Examples would be representing a tree in SQL tables, representing a matrix with nested arrays, or representing a graph in json.

I can see the first two exampples (tree in SQL, matrix as nested arrays) because there are limitations in the representation. The last one though (graph in json) doesn't seem obviously limited.

Mathematically, a graph G=(V, E), i.e. a set V of vertices (nodes) and a set E of edges between nodes. That has a direct translation into json:

    {
        "G": {
            "V": ["v1", "v2", "v3"],
            "E": [["v1", "v2"], ["v1", "v3"]]
        }
    }

Perhaps it's that each edge is encoded as a list? Or that lists aren't sets, so there might be duplicates? Enlightenment appreciated.

--

EDIT: fixed typo.

Emphasis on "aren’t well-suited to it", I guess. It gets complicated once you want to parameterize each node.

For the past year or so I've been "typing graphs" into YAMLs. It's definitely doable, but hard to follow, especially with cyclic graphs.

N8N is a very popular nocode workflow builder with a great UI. They store workflows (graphs) as JSON. I use this complicated workflow https://n8n.io/workflows/1534-back-up-your-n8n-workflows-to-... in my homelab but I can't find a public JSON representation of it. It'd be a great example of how to present a complicated graph. I'll add it as an edit if I find it.

EDIT: I found one public workflow backup on github. https://raw.githubusercontent.com/thethanghn/n8n-workflows/d...

It's not that bad.

They are probably alluding to the tree-like structure of JSON where you can't represent cycles without inventing a pointer + identifier format first, and that is indeed not part of the building blocks "JSON-the-language" itself provides.

This seems to be a direct continuation of the SQL example -- wouldn't you be able to represent a tree with eg. tree paths stored for each item?

> That has a direct translation into json:

Sure, but this representation is horrific, and a very clear example of why json is not the appropriate format to store graphs. A more natural format for graphs would replace your punctuation-ridden monstrosity with a simple text file, for example:

    v1 v2
    v1 v3
I take your point, though you're not directly comparing like with like. I'd used a literal translation of (V, E) so listed vertices explicitly as well as edges. Using implicit representation (as you have), the json could be simplified to [["v1", "v2"], ["v1", "v3"]]. Still more syntax, definitely. But not far off, for example, the graphviz[0] equivalent in syntax overhead terms:

    graph {
        v1 -- v2;
        v1 -- v3;
    }

To be clear, I'm no apologist for json. It just seemed to me there was a pretty direct representation. @rzzzt's observation on json being predominantly hierarchical and therefore tree-shaped seems like the most likely reason behond the original statement.

--

[0] http://graphviz.org/

I called this idea of deliberately applying software mimicry as branching libraries in my ideas document (link in profile (the first link))

I experiment with programming language theory and designs as a hobby and one of my thoughts is that the core underlying problem of computing is an arrangement problem of a turing machine. Compiling is "planning what you shall do". Execution is "doing". We have the luxury of many ways of doing things in computing. But it all seems to lead to mess of poor understandability and complexity.

We're still trying to find the best way of doing things.

What am I trying to say? OOP inheritance hierarchy trees are limited and not what you really want to do to represent your problem. I want to define a situation or scenario and define what should happen in that scenario. This is where asynchronous programming and join calculus shines. I also like occam-pi's select statement and golang's select. I also feel every computing problem is the "expression problem" manifested. How do you associate behaviour (what to do) with a type (a situation)? And how do you define them so you don't have to reimplement all the previously existing behaviours with the new thing?

The next section shall be reductive.

Ultimately all loops, function application, methods, expressions, variables, classes, functions, lists, vectors, data structures, algorithms exist as "things" in the imagination of the developer's mind and the compiler. They don't exist in machine code.

We have a grid of memory locations and the arrangement of things in that grid is handled by a memory allocator. We also have references in this grid as pointers, which form a structure of their own. A turing machine.

Thinking of a type and behaviour as being solely one thing only at a time is inherently limiting. I often want to see different things "as" something else to talk about them in a slightly different approach. A vector or ArrayList of objects of multiple types but processed in the same approach, efficient compile time polymorphism.

This is kind of how I imagine object orientated development to really be about, I want to be capable of referring to an arbitrary selection of objects and define new behaviours or relations between the objects. Unfortunately most OOP ties imperative behaviour and stateful manipulation to data rather than modify exhibited behaviours of objects. Imagine being capable of organising and managing load balancers and draining them and devops architecture with code. A point and click GUI where I can right click a load balancer and click drain. (I don't mean code to bring them up or create them but to actually administrate them with OOP)

I think the expression problem is a core problem of modern computing and doesn't have an efficient solution.

We can decouple data structure, layout and algorithm. Unfortunately most programming languages couple data structure + layout (C) and OOP languages couple algorithm with layout. Functional programming languages I'm not sure about.

I've been working with C++ templates recently with C++20 coroutines and multithreading and I am finding template instantiation very interesting. I've been late to come around to it.

This comment mentioned the insight that boxing and template instantiation are related. https://news.ycombinator.com/item?id=14764780

I am also working on a multithreaded programming language which looks similar to Javascript. I use an actor implementation that can send messages between threads.

https://github.com/samsquire/multiversion-concurrency-contro...

One of my programming language designs is the idea that every line of code is a concurrent process and data flow is scheduled by topological sorting. Iteration is handled automatically for you. Every variable is actually a relation and algebraic definition of relations between processes. This is called algebralang. https://github.com/samsquire/algebralang

The idea is that you write the underlying insight into the problem as an expression of what you want to do and let the computer schedule the ordering or arrangement of operations to do it. It's a form of pattern matching on the state of things (objects in the system) and declaration of the desired result. This brings to mind the rete algorithm (expert systems) and differential dataflow.

In Design Patterns in Dynamic Programming, Peter Norvig notes that strategies are just trivially replacable with higher-order functions (HOFs).

So that's where this trope is from! A few things:

1. The "just replace it with a higher order function" thing obviously falls apart when your strategy has multiple methods. In which case, the FP programmer might be tempted to supply a record where each field is a function, and maybe they want to close over some shared implementation details and... you've just re-invented objects, albeit memory in-efficient ones.

2. In many languages that support OO, a higher order function is an object, same way a string is, or an Integer.

I might be being harsh here. Presumably Norvig wrote this in the hey day of "OO flavoured" languages like C++ and Java being completely dominant in industry. But in 2022 we can stop repeating this tired trope.

Isn't your point 2 exactly what OP said?

> I think it’s more interesting to think of the GoF-language as mimicking HOFs with the strategy pattern facsimile. The strategy pattern reimplements HOFs as a code-level idiom.

No more than HOFs are mimicking objects.

It's two schools of thought - everything is functions & data, or everything is objects & methods.

Closures are implemented as objects under the skin, but that doesn't make them equivalent conceptually to me. I suppose it's down to syntax, after all if you ignore that you can have anything you like in assembler, but it won't feel very high level.
Closures are a poor man’s objects. Objects are a poor man’s closures.
What's the "poor man's" stuff? Is there a better way I don't know of
So are the birds mimicking the bees, or is it the other way around? Maybe I'm just missing the point.

I mean, is int[] a mimic of ArrayList<Integer>, or vice versa? Of course they're similar, of course they're different. Is "mimicry" just a pejorative thing?

A definition is given as literally the first paragraph of the post.

> Mimicry is when software X reimplements at a higher level a core feature of software Y. The produced facsimile has some, but not all, of the same properties, enough to “look like” it’s the same thing but missing many of the nuances. This exists in every kind of software. One language can mimic another, a library can mimic a language, a database engine can mimic a product, etc.

I don't see how your question even fits into that definition.