Hacker News new | past | comments | ask | show | jobs | submit login
Moka - Minimalist functional python library (github.com/phzbox)
53 points by phzbOx on Nov 19, 2011 | hide | past | favorite | 34 comments



Clickable link for the doc: http://phzbox.com/moka/index.html

Moka is still in an alpha stage; but that being said, I'd love to hear some feedback. Feel free to browse the code, it should be pretty straightforward to any python dev.


You've invented your own incompatible dialect of python. You've broken composition, objects and the general design choices of python.

1. You break Composition

Your magic flag within a list changes all the semantics of the methods between immutable and mutable lists.

> x = List([1,2,3]).saving()

So now, if you're passed a list you have no idea if the operations you do will mutate the list.

This breaks composition entirely. You can't pass a mutable list into a function built for immutable lists without destroying things.

2. You break objects

Picking another example, this breaks duck typing and inheritance and polymorphism.

>def user_logged(users): > return List(users).all(User.is_logged)

this does not have the same semantics as:

> def user_logged(users): > return all(user.is_logged() for user)

Because the method lookup is done per instance, rather than assuming everything is the same class.

3. You're writing jquery in python

Python chose not to demand that all iterables implement a series of operators, but provides them as functions within a module. The rationale is that it is easier to add new functions within itertools, and there is far less to do to correctly implement the iterator protocol

You can see this in the "".join(foo) operator too. Instead of demanding all iterables support join, string takes an iterable as argument.

Making readable and maintainable python comes from using the existing idioms within the language and used within the community. Your proposed solution isn't readable, and it isn't pythonic.


What is with all the hate lately on anyone who dares to write Python with different semantics than employed by the stdlib builtins?

'You've invented your own incompatible dialect of python. '

It isn't a "dialect." You don't need a different parser. There are no macros. It is simply a container with slightly different semantics than the stdlib ones.

"You've broken composition, objects and the general design choices of python."

No those all still work and if you don't like his semantics don't use them. They are perfectly readable and easily understandable if you having a passing familiarity with the pattern he is using.

tl;dr : I don't see what the big deal is, people are allowed to write Python however they want.


tl;dr: you didn't read my post. I am pretty sure I made it clear how composition breaks.

'people are allowed to write Python however they want.'

people are encouraged to write python other people understand.


No I did read your post. Actually I read all of them. My point is he didn't "break" composition. In isn't like you import this guy's library and suddenly function composition no longer works. Breaking isn't the right word. It is agressive and wrong on a semantic level.

You could say "You functional list does not properly support composition. Here are some problems." You posts were needlessly combative. I only spoke out because I have seen this kind of thing cropping up recently with respect to Python functional libraries.

"people are encouraged to write python other people understand."

As I said, the semantics of his library are reasonably clear on inspection doubly so if you read the docs.

EDIT:

I just re-read some of your other posts on this thread. You really do keep banging on "He made his own 'dialect.'" To clarify, no he didn't. Making a library isn't a dialect. A dialect needs to be a significant enough departure that you would actually need a different parser/interpreter. That is a "dialect." If you would like I can point you actual Python dialects. This is a library. Deal with it.


"the semantics of his library are reasonably clear on inspection"

does .remove() mutate in place or not ? depends on the constructor!

this would be an example of breaking composition - you can no longer use a function built for an immutable list on a mutable list. I am sure I explained this too.

a dialect is a style, in linguistic terms, if I speak a dialect of english, I still speak english.

if I needed a different parser/interpreter I am pretty sure that is the stage of 'new language' where new syntax and semantics are introduced.

the 'dialect' he is introducing is a arc/clojure inspired syntax for what /already/ exists in python.

thanks for the semantic pedantry! the long and the short of it is that if you want to write python, write python that looks like python. not like scheme or clojure.

as someone who gets paid to maintain shitty code, i'd rather people stuck to the existing idioms of the language, rather than blindly copy paste them from another language.


"if I needed a different parser/interpreter I am pretty sure that is the stage of 'new language' where new syntax and semantics are introduced.

the 'dialect' he is introducing is a arc/clojure inspired syntax for what /already/ exists in python."

No a dialect is another version of the language. For instance python3 is a dialect of python, just as python2 is. They are not 100% compatible.

"as someone who gets paid to maintain shitty code, i'd rather people stuck to the existing idioms of the language, rather than blindly copy paste them from another language."

It is not his problem you see a lot of shitty python. It is also not his problem if you want every one to stay stuck in the poor design choices that were made in the stdlib a long time ago. You can fight evolution of languages or you can embrace them. I don't see you advocating for "thou" in English ;-)

"the 'dialect' he is introducing is a arc/clojure inspired syntax for what /already/ exists in python."

"syntax" --> no thats is what is defined by the grammar of python. This is python syntax.

"a dialect is a style, in linguistic terms, if I speak a dialect of english, I still speak english."

No it is a mutation. But I agree with you if you speak a dialect you still speak the "language." Olde English is still English.

---

However my main point remains. You are needlessly combative in this thread. He made a library you attacked him like he insulted the Pope of Python. I am sorry you maintain shitty Python but you don't need to take you anger about that out on some poor guy on hacker news.


Thanks for the feedback, these are interesting points. I'd like to clarify a couple of things about your #2 and #3.

    def user_logged(users): > return List(users).all(User.is_logged)
    # If you want per instance:
    def user_logged(users): > return List(users).all(lambda x: x.is_logged())
And, about the "".join; you are right. However, Moka's construct allow you to use whatever you want, i.e.:

    moka.List(['a', 'b']).do(string.join, '').last_value
Or, you can still use:

    ''.join(moka.List(['a', 'b']))

However, about the #1, I have to agree. I've added the saving() recently and had a bad taste about it. The right way to do it might be to make it mutable by default and only toggle it off in certain circumstances.

Please note that everything done by the community is still perfectly usable; in fact, it's even easier to use useful high-level functions.

I.e. map(str, range(1,10)) or List(range(1,10)).map(str); is syntactically different but does the same job.


#1 the correct way to do it is to have the methods perform the same action on mutable and immutable objects.

this is the only way to guarantee composition. you do not change the semantics of shared methods.

#2

for both of these examples you give 'to use whatever you want'

"".join(['a','b']) is how everyone else does it in python code.

it isn't about things in the community being usable within your library, it is about your library being /unusable/ within the community. You re-invent new and awkward ways to do standard things without standard idioms.

'i've just invented a whole bunch of new semantics for things so it will be readable'

readability is about /convention/. readable to whom? pushing your own love of jquery method chaining only serves to ostracise those already somewhat knowledgable within python.

it is not pythonic in any way shape of form.


if you are inventing a new way to do existing things outside the idioms of the language there is no conceivable way you can claim to be pythonic.

you are replacing the pythonic style with your own taste.

don't confuse the two.


I'm not sure why you created a fake account to answer this but thanks again for your time. You're right that it was built for my own taste using simultaneously other languages (clojure, arc and js for instance). At the end of the day, what's important is that it increases the quality of the code and this is my goal with Moka. This is still in an alpha stage, but I'll definitely tweak it based on good feedback (Like yours) Feel free to drop me a line: phzbox at gmail if you want to continue this discussion.


It's totally legitimate for you to write this according to your own taste and use it. I don't think it's bad for Python, or anything like that. You are not an idiot and I am sure you can write interesting and useful programs in Python.

But I do agree that it is not 'Pythonic' - except maybe in the trivial sense that it's written in Python.

I would be happy to go into more detail if you really want it.


I'm not sure why you accuse me of having a fake account?

Re-inventing a uniform syntax for python is the least pythonic thing you can do.


give it a break. sure, we should be pythonic when we're programming at work and our code is likely to be maintained by others. but there's absolutely nothing wrong with pushing boundaries and learning by exploring and even - shock - getting things wrong.

you made some good points (particularly the fact that you lose dispatch by instance), but you don't need to keep jabbering away about the same points.


writing your own dialect of the language can be fun, but he is presenting his own style as 'pythonic' as opposed to the actual pythonic style built from functional composition - rather than method chaining.

I am banging on about a lot of the points because it seems very hard to explain to him that using clojure/arc/jquery styles is very very unpythonic.


you seem to go a long way to reinvent python builtins like reverse() any() all() enumerate(), itertools and functors. another python style you violate is that mutable methods return None in general.

from the outset you haven't made any attempt to learn python style. go and read the zen of python.

I only picked on a handful of examples, but wait! theres more - almost every example on your page has a way to do it in python. that other python developers use and understand.

#chaining example:

you say 'we believe chaining constructs are easier to read and maintain than deeply nested expressions.'

zen: flat is better than nested

> Dict(a=1, b=2).update(c=3).rem(lambda x, y: x=='a')

becomes

> d = dict(a=1, b=2)

> d['c'] = 3

> del d['a']

#'partial application' example

> List([1,2,3]).map(string.zfill, 8, _)

becomes

> [str(i).zfill(8) for i in [1,2,3])

# magic argument names

zen: 'explcit is better than implicit'

different methods have different magic attached: it isn't obvious from the outset why update takes named args but keep takes args are named operators

> List([1,2,3]).keep(gt=1)

becomes

> [x for x in [1,2,3] if x > 1]

# you reinvent all

> List(range(1,10)).all(lambda x: x < 100))

becomes

> all(x < 100 for x in range(1,10))

# 'compact' example

> List([None, 0, 2, []]).compact()

becomes

> [x for x in [None, 0 , 2, []] if x]

# 'list is empty' example

> List([]).empty()

becomes

> bool([])

# 'sort'

> List([5,3,1]).sort()

becomes

> sorted([5,3,1])

'uniq'

> List([1,1,2,3,2,1]).uniq().sort()

becomes

> collections.Counter([1,1,2,3,2,1])

for every example you give, there is an equivalent piece of python code to do it, designed in mind with the rest of python. the built in operations give you flexible control over the evaluation too - you can have generator expressions and list expressions. many iterable versions of the standard operators exist in itertools.

really, this is the least pythonic thing since ruby came out. it seems I can only spell this out to you by elaborating through your jquery library and presenting you with python code python developers understand.

please stop re-inventing python without trying to understand why it looks that way first.


Thanks for all the good points. To be honest, I'm not sure what you're trying to say. I've been coding in python for years and Moka was built from my annoyance using functional paradigms with the stdlib.

As you clearly showed, Python doesn't have an uniform syntax to deal with this paradigm. I.e. there are lots of different constructs and, as you said, common idioms or patterns. Have you already argued with a Java programmer saying that these 'Design Pattern' are just a limitation of the language.. whereas in Python you'd probably just use a simple Dict (or whatever)? I'm sure you did. I feel the same with the idioms and patterns.

See, in Clojure (And Arc, and even Ruby), there is an uniform syntax.. whereas in Python we've got itertools, list comprehension, builtins map/filter, random builtins functino such as sorted().

I have to agree with you that Moka is not Pythonic in the There's only one way to solve a problem as we add a new way. However, Moka was created because there was so much inconsistent alternatives..

However, Moka is Pythonic in how it behaves. God knows I could have use all nifty hacks to make it behaves magically.. but I chose to take the explicit route by overriding list/dict. I could have used string interpolation for function (See http://osteele.com/sources/javascript/functional/); but instead went the Pythonic way with standard lambda functions. Maybe you are right about the operator keywords shortcut (i.e. using List().keep(operator.eg) instead of List().keep(eg=); However, I still feel it was a way to make it even easier to integrate with existing tools from the stdlib.

Lastly, you said:

    zen: flat is better than nested
    > Dict(a=1, b=2).update(c=3).rem(lambda x, y: x=='a')
^^^^^^^^^^ This is not nested, this is chained.

This, however, is nested: # Taken directly from the itertools stdlib page. next(islice(iterable, n, None, default)

    # Here, this is not nested.. this is chained:
    def logged_user(self):
        return (self.users
                      .keep(User.is_logged) 
                      .keep(lambda u: u.is_active)
                      .map(lambda x: User.objects.get(id=x)))


so you're admitting you're writing an incompatible dialect of python in python?

that was my point. if you think it is worthwhile, well - enjoy :-)

if you want to write in something with a uniform syntax, use scheme or clojure. python things look different for a reason.


Maybe I don't understand what you mean by "incompatible dialect". These are simple classes inheriting from list/dict. Basically, it simply add a couple methods to these objects. I.e. You can still use all the idioms/patterns; these are list/dict. I'm not creating a new paradigm, or writing a new language by tweaking the syntax. There's really nothing magic going down here. I.e. you can do

    return List(..).update(..).update(..) 
Instead of;

    l = List(...)
    l.update(...)
    l.update(...)
    return l
You can still use lisp comprehension, itertools, etc. In fact, I could pass a moka.List to your existing code and you wouldn't even notice it. (And you do, it's a mistake that I'd fix).

I do agree that I might have been overkill with some useless methods and I'm thinking about removing them. (I.e. such as 'join' as it's really not needed)


Yeah, get rid of saving(). Not only are the semantics dangerous and confusing, but the name suggests the opposite of what it actually does.


A good source of inspiration: Ruby's Enumerable[1] and Scala's Iterable[2]. Toguether, they have one of the most complete functional collection methods library.

[1] http://ruby-doc.org/core-1.9.3/Enumerable.html

[2] http://www.scala-lang.org/api/current/scala/collection/immut...


two small suggestions: change .saving() to .mutable() and .rem() to .remove() (or filter(), reversing the semantics, since that is already in python) (every other method is a full word).

also, maybe .mutable() would be better as a flag in the constructor. it makes no sense to use it in a chain (in fact, that could be confusing) and is the kind of thing you should probably fix on construction rather than changing later.

ps. it's not clear to me that this is better than list comprehensions, which already do much of what you have.


Thanks for the feedback; I agree with you. Here's the reasoning behind these odds choice..

List is-a list, and Dict is-a dict.. and thus, I'd like to make it possible to safely pass these objects to already existing code. If I rename .rem to .remove, I'd break that.

The reason for the chaining saving() is because it's useful to be able to toggle it. For instance:

  self.x = Moka.List(range(1,10)).mutable()  
 
  return (self.x.map(lambda x: x*2)
                      .immutable()
                      .keep(gt=5))
So, basically, you change self.x with the map, but don't necessarily want to change it with the keep().

(I'm not saying it is the right thing, just why it's done that way.)


What if your mutation functions accepted a flag that set the saving behavior, and you used a sensible default?


You should really give Ruby a try.


Here's a gist comparing the Moka front page example to a slightly more typical Python way of doing things:

https://gist.github.com/1380898

EDIT: using the timeit module on these, the Moka version is about 18 times slower on my machine... not that this matters much if it's a much nicer way of expressing the program


Thanks for sharing. And ya, it's freaking slow right now.


The doc at http://www.phzbox.com/moka/index.html is pretty complete. The only thing I find is missing is a few words about efficiency. Does calling List(l) copy the list l for example?


Optimizing for performance is the next big step of Moka and I understand that it's the breaking point for lots of python developers. So, I'll make sure to work on that and provide realistic benchmarks.

As for the copy, yes it does as it tries to act like a builtin list as much as possible. (In fact, moka.List inherit from list). When it's in immutable state, a new list is returned at each step. If it's in mutable, self[:] = (..) is used.


Looks like it.


This is indeed minimalist, there's less code in it than I'd expected.

The idioms it introduces do clash with Python ones, but on the other hand, I've been learning Clojure lately and my Python code has more comprehensions than previously. I'm not sure what to think.


Needs a README and examples without a hop.


This is pretty interesting looking. I'm tempted to try using it on my next project...anyone else used it yet? Seem stable/bug free?


It's fairly new but feel free to browse the code; it should be pretty straightforward to you. I've tried to provide good examples to make it easy to learn/use on phzbox.com/moka/.




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

Search: