python-lenses icon indicating copy to clipboard operation
python-lenses copied to clipboard

Setter -> Traversal

Open Gurkenglas opened this issue 2 years ago • 3 comments

Python's nonlocal state side effects give every setter traversal powers. You should unify them or have a utility to convert them. I say this because using re.sub (which allows a function argument) for a traversal is turning out much harder than the lambda regex: Setter(lambda f s: re.sub(regex, f, s)) it should be.

Gurkenglas avatar Aug 26 '21 13:08 Gurkenglas

I'm not sure I understand what you're trying to say. I don't get how side-effects could unify setters and traversals.

If you want a traversal that focuses parts of a string that are matched by a regex, then you'd write it like this:

import re

from lenses import lens

def regex_traversal(pattern, flags=0):
    def folder(state):
        for match in re.finditer(pattern, state, flags=flags):
            yield match.group(0)
    def builder(state, values):
        iterator = iter(values)
        return re.sub(pattern, lambda _: next(iterator), state, flags=flags)
    return lens.Traversal(folder, builder)

state = "First thou pullest the Holy Pin"
state &= regex_traversal("\w+") + "!"
print(state)  # "First! thou! pullest! the! Holy! Pin!"

If there's a better way to write this I'd like to know.

ingolemo avatar Aug 26 '21 14:08 ingolemo

def setter_traversal(setter):
    def folder(state):
        acc = []
        setter(acc.append, state)
        return iter(acc)
    def builder(state, values):
        iterator = iter(values)
        return setter(lambda _: next(iterator), state)
    return lens.Traversal(folder, builder)

state = "First thou pullest the Holy Pin"
state &= setter_traversal(lambda f,x: re.sub("\w+", lambda m: f(m.group(0)), x)) + "!"
print(state)  # "First! thou! pullest! the! Holy! Pin!"

builder should take a generator of values instead of a list, of course.

For better streaming:

def folder(state):
    g = greenlet.greenlet(lambda _: setter(lambda a: ret.switch(a), state))
    while not g.dead:
        ret = greenlet.getcurrent()
        result = g.switch(None)
        if not g.dead:
            yield result

I spent a few days there trying to stream Traversal.func correctly for an arbitrary Applicative ^^.

Gurkenglas avatar Aug 31 '21 09:08 Gurkenglas

How useful would it be to have a general convenience method for this? Haskell-style setter functions (a -> b) -> s -> t aren't particularly common in python. Does anyone know of any in the standard library other than re.sub?

ingolemo avatar Sep 08 '21 14:09 ingolemo