Hacker News new | past | comments | ask | show | jobs | submit login

So overly complicated. The "state of the art" of FP now means heavy type systems and heavy machinery to deal with them. The 80/20 pareto of FP is just pure functions + composing those functions and it works in any language. Here is all of it in python:

    from __future__ import print_function
    import json

    struct = json.loads(open('staff.json','r').read())
    staff = struct['staff']
    salaries = struct['salaries']

    def visit_pets(staff, cond, visit):
        for person in staff:
            for pet in person["pets"]:
                if (cond((person, pet))):
                    visit((person, pet))

    # Find all cats owned by any of our employees
    visit_pets(staff, lambda t: t[1]["type"] == "cat", lambda t: print(t[1]))

    # Find each pet and their owner
    visit_pets(staff, lambda t: True, lambda t: print("{} belongs to {}".format(t[1]["name"], t[0]["name"])))

    # Give a $5 raise to anyone who owns a dog
    dog_owners = set()
    visit_pets(staff, lambda t: t[1]["type"] == "dog", lambda t: dog_owners.add(t[0]["id"]))
    for staff_id,salary in salaries.items():
        if staff_id in dog_owners:
            salaries[staff_id] += 5
In my view it gets you 80% of the power of optics in exchange for zero machinery and can be understood by almost anyone. For doing these kinds of ops on a deeper structure, using Scala/Javascript or something with first-class support for HOFs, folds/filters/maps would work better.

I wish more people promoted this view of FP - just pure functions, higher order functions and referential transparency. It gives you the ability to reason locally and extend code well. The remaining type system and architecture astronomy buys very little in relation to what it costs.




For me, the remaining power of optics (what you call 20%):

1. let the compiler reason about my code to catch errors when the requirement changes (say, when some record fields change their names or types),

2. thus lower my cognitive load, and

3. allow me to reason about more important aspects of the project.

At work, the use of a powerful and expressive type system allowed me to work on multiple projects at once, achieving at least 2 times boost in productivity (definitely more than 20% gain).

Moreover, if optics is not well-known to everyone, I can just link to this blog post as a documentation, and give more examples if needed (I think people underestimate how coworkers could be productive without completely understanding everything).

It is hard to explain the experience of “pair-programming with the compiler”, but such complication is worthwhile for the productivity gain (and developer happiness).


This example has nothing to do with type systems. The same system exists e.g. for Clojure (see Specter).

The fundamental idea behind the approach in the article is the following:

Treat the path through a nested data structure as a first-class thing.

This gives helps us mainly in two scenarios:

1. Allowing for easy field-update-like code to exist for immutable data structures in addition to mutable data structures. This is especially valuable for updates.

2. Unify everything that can be "field-like" under a consistent field syntax.

In most class-based languages we can write something like

    myObject.field0.subfield1.subsubfield2
but `field0.subfield1.subsubfield2` isn't a first-class item that we can pass around and use. We can certainly write something like the following:

    def myPath(someObject):
        return someObject.field0.subfield1.subsubfield2
but (to the first point) if someObject is deeply immutable, we can't use `myPath` to actually update `subsubfield2` and instead have to cumbersomely write out the entire reconstruction of an immutable data structure at every level.

To the second point, even if someObjet were mutable, there are a variety of things that would be nice to have as field accesses, but aren't, simply because they don't fit into a class definition. As an example, consider the bits of an integer.

    # Imagine .permissions.bitMask.lastBit to be a path 
    # through user
    set(user, .permissions.bitMask.lastBit, 0)
is so much nicer than the corresponding bit shifting code.

If path access through data were a first-class concept, there's no reason that paths should be restricted to only fields in classes. You can fit the same interface to anything that supports the notion of "field-like" access.


This is exactly how we shipped our JQ-like product feature in 62 lines of Clojure. Just pure functions composed into a single predicate and and a decent grammar to interpret the input JQ.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: