My best Elixir in Python

After using Elixir for a long time I can’t wrap my mind around Python’s APIs.

So with the help of functoolz curry, flip, and pipe, I can write HTML parsing code like this:

    def parse_title(dom: XmlResponse) -> Title:
        name = pipe(
            dom
            , html.xpath("//TITLE-TEXT") 
            , text.titleize
        )
        number = pipe(
            dom
            , html.xpath("//TITLE-NUM")  
            , text.split(" ")                  
            , seq.get(1) 
        )
        children = _parse_divisions_or_articles(number, dom)
        url      = _source_url(number)

        return Title(name, number, children, url)

I’m not re-writing any of these functions. I’m just wrapping each of them with a little code to give them a consistent interface. And once they each have this new API, they can be easily chained together.

Anyone else adapting other languages to the Elixir fluent API style? I don’t get much love from the Python community when showing this kind of code. :smile:

I think You would get the same kind of love from the Elixir community when showing Object Oriented Elixir code :slight_smile:

7 Likes

Definitely no! In programming that’s the only one thing I call my personal anti-pattern (i.e. that’s my opinion without linking to some resources). I’m 100% against forcing solutions known from other languages in a way you wrote. Simply, every language is different. It have other styles and expectations. There was a talk about pipes in Erlang, but it was hard to implement, because in Erlang the data we work on is in the last function argument instead of first. Forcing Elixir-like pipes into Erlang have no sense as simply there would be no reasonable use case.

Speaking generally the problem often is not what you do, but how you do. You can force other language features and deal with expected hate or write a new features inspired by other languages. It’s not shame for others to improve, but nobody would agree to change habits because something is better in other languages. Same goes for law. You do not force law articles from country you born as it’s simply stupid. However you can instead propose changes to existing law system.

That’s endless topic i.e. assimilation vs integration

I got to attend the annual American Geophysical Union (AGU) fall meeting that was in San Francisco a few weeks ago, thanks to a pass from Matt Rocklin (author of functoolz and also dask). Someone there was also sharing similar positive sentiments about this kind of chaining, and said how it felt functional and Lisp-like, to which Matt responded something like “that’s exactly why I built it, I was re-writing in Python pieces that were missing for me that I was used to having in Clojure”.

I’d call it cross-pollination :slight_smile:

2 Likes

Hello and welcome to the forums!

I have nothing wrong with cross-pollination so long as its adopted by a language’s community as a whole. I also whole-heartedly applaud doing experiments. But I’m otherwise with @Eiji’s here. Coming across business code that someone decided to write in an idiom completely foreign to the language I’m working in is the worst. I’m actually getting a little worked up thinking about the several times this has happened to me :cold_sweat:

Is that how it’s ", ".join(["Hello", "World!"]) or something? I’ve heard Pythonistas say this makes more sense (somehow). I honestly don’t know much Python, though!

2 Likes

That’s very even-handed and balanced—a hard act for me to pull off. :slight_smile:

I don’t think it’s language design differences. I attribute it to their ages: Python is decades older than Elixir and it carries some baggage:

  • Parts of Python core are fluent (Elixir pipe-style) api’s, read left-to-right.
  • Other core libs require a right-to-left function-invocation style reading.

I’ve researched this deeply and the best I can see is, Python’s battling paradigms are present due to historical reasons.

In contrast, Elixir was designed with advantage of seeing was Python, Ruby, etc. did well and not so well. Ruby, in turn, was designed looking at Python. Even though everything in Python is an object, method chaining couldn’t be universally applied. Ruby sought to correct that in its design.

1 Like

I totally agree. I would never produce code like this for a client. This is my personal indie project, and I prioritize my productivity.

Doh! yeah, it’s join() that kills me. It’s implemented only on string objects, not on lists or iterators.

I want to break up and refactor code where it’s meaningful. Not because a decision made in the 1980’s hasn’t been overturned. I like thinking about language design, and ponder these issues.

Concrete example from my code

I found this in about 3 minutes of looking. Incredibly, I see four different paradigms present in this one section of code. (!)

    # Return an HTML document's plaintext, cleaned up.
    raw_paragraphs         = rule_div.xpath("p")[1:].getall()
    cleaned_up_paragraphs  = [p.strip().replace("\n", "") for p in raw_paragraphs]
    cleaned_up_paragraphs2 = [re.sub(r" +", " ", p) for p in cleaned_up_paragraphs]
    non_empty_paragraphs   = list(filter(None, cleaned_up_paragraphs2))
  • Line 1: Fluent, chained method calls
  • Line 2: Mixed modes: A list comprehension containing chained method calls
  • Line 3: Mixed modes: A list comp containing a namespaced core regex library call
  • Line 4: A right-to-left “chain” of global function calls.

The worst part of this, for me, isn’t the poor maintainability from the need to shift between different styles. It’s that these “semantic roadblocks” of intermediate variables were essentially forced on me by the paradigm changes.

E.g., global replace is a string method when the pattern is a string. When the pattern become a regex, it’s now a library call conventionally namespaced with re.. So whatever data flow you were coding needs to shift paradigms due to this implementation detail.

Ruby (really, Matz) saw this, and fixed it by simply making String.sub() accept either a string or regex pattern.

Ok … wait, no! That’s not “ok”! :sweat_smile:

Umm … I never know Python is so … well … Why do “we” force it for children in schools? What do “we” teach them? :smiley:

I heard Python is good for beginners, but now I see something else … XP - memes are too old for “young blood”:

“WindowsXP or better” - so Linux will do the job!

so they decided to introduce new one:

“Required 10 years in Python or better” - so 1 day in Elixir will do the job!

Now I better understand why Python community does not like your idea. The 5th paradigm would be a hell for them even if it’s Elixir’s paradigm. Please let them rest at least for now at the start of 2024. :joy:

Happy New Year! :fireworks:

This should be qualified with context: many people said that learning an immutable FP language made them write more understandable, clearer, easier to read, easier to debug and test code in imperative OOP languages.

It’s not about trying to make Python stand on its head. It’s about writing Python that is… less ugly and hard-to-work-with Python.

Languages should indeed cross-pollinate as @ivanov said.

One prominent example: after Rust made enums (sum types) prevalent in compiled languages – nevermind that OCaml and Haskell had them for ages before that but oh well :003: – then many people started wanting them in other languages.

3 Likes