Coconut: Pythonic functional programming
coconut-lang.org
coconut-lang.org
I’m not 100% sure what that would look like, but I would ditch classes (and thus inheritance) for named tuples, make most things immutable by default, include enums and pattern matching (that the type system would check; I hardly see the point in dynamic pattern matching). If possible, typed code would compile to C extensions or similar. Oh, and exceptions would go away in favor of a Rust-like Result type.
That's not to say that there isn't any complexity in OCaml. The type system and module/functor system have a daunting number of features. But most applications stick to a much simpler subset of the language, which is quite straightforward and easy to reason about.
Made math code oh so ugly.
This will change at some point though. Modular implicits are coming to Ocaml. These are similar but much simpler than Scala's implicits, and allow the same kinds of patterns. If Ocaml gets that and eventually gains a bigger community with better library coverage, it's something I would consider using heavily.
>Effects are thus first-class citizens of Eff and can be seamlessly combined. There is no need for the do notation, no need for monad transformers, and no need to reshuffle your whole program just to read a global flag.
I don't think it's necessarily readability holding me back from Haskell, but I do find Clojure to be highly readable which really helped me get up to speed with it quickly. I'm curious what you find more readable about Haskell.
Clojure has an incredibly simple syntax, but that isn't so much because it removed the complexity but rather that it just moved it away from the syntax imho.
True, but there is a learn Haskell in 10 minutes...
I think this issue is way overblown. The reasons for choosing a particular string representation in Haskell are analogous to the reasons to choose among e.g. byte arrays, streams, string builders, etc. in mainstream programming languages.
> Which prelude?
There is a standard prelude. Unless you actively choose another one, that's the one you'll get.
> Which compiler extensions?
If you want to use certain advanced language features, enable the appropriate extensions.
> Which testing library?
Many mainstream programming platforms (e.g. .NET) have multiple popular testing libraries to choose from. Is this a bad thing?
Haskell has its weaknesses, e.g. the lack of quality tooling available as compared to mainstream programming languages.
But I disagree that Haskell is complex, at least as a criticism. When expressing concepts of a similar complexity, I find Haskell to be particularly concise and expressive as compared to most other programming languages I am familiar with.
And if Haskell undervalues consistency and standardization, I would like to know as compared to what? The only programming languages that I know of where there are not many reasonable choices for e.g. a testing framework are those that either (a) haven't been around that long, or (b) haven't seen wide adoption.
Haskell is many things. Simple is not one of them.
Language extensions are idiomatic Haskell. There is only one mainstream compiler used everywhere. Widely used extensions are a natural result when the compiler, as a testbed, can outpace the language standard.
Enabling extensions is as trivial as including a standard library package. (In fact, the extensions are often better documented than the standard library, as you hint to.)
If you want to use certain advanced language features, enable the appropriate extensions.
Your answers typify a level of comprehension of Haskell which appears to occupy the same brain space empathy would do. The whole POINT is that there are complex language features which have to be enabled if you want them: how does anyone know a priori in a new situation what to enable and what to disable? What side effects? What consequences?
I loved learning Haskell but to pretend it's syntactic simplicity translates to simple in all things is to misunderstand.
Gcc has a million compiler -W options do you think every C programmer knows them all? Do you think that every cat user knows why we joke about cat -v?
You have mastered Haskell and forgotten what lack of complete understanding means to anyone else. Mastering FORTRAN or pascal or lisp was trivial by comparison.
Mastering the underlying concepts of recursion, and tail recursion, and typing systems, and then integrating optional language features is not trivial.
> The whole POINT is that there are complex language features which have to be enabled if you want them
Yes, "if you want them". If you value having a simpler language, don't use them. And Haskell is hardly unique in this regard. You've mentioned GCC, but I think the Babel transpiler is another good example.
> You have mastered Haskell and forgotten what lack of complete understanding means to anyone else.
I've managed to teach my daughter some Haskell, who had no prior exposure to programming languages whatsoever, so I doubt your assertion is entirely true.
The hardest programming language I ever learned was the first one. Each one after that was easier than the one before...until I got to Haskell. It was so different that I was forced to go back to a more fundamental understanding of the nature of code and computation and start from there. It felt pretty challenging, especially at first, but I think that had more to do with my perspective and experience coming into it than the language itself (and the fact that I had a family and career at that point didn't help).
https://en.wikipedia.org/wiki/Cython
There's also RPython which is restrictive and what PyPy is done in.
https://en.wikipedia.org/wiki/PyPy#RPython
Edit: on an unrelated note, sounds like you'd love Go.
Unless you know C it's not always worth using. Without doing any "C stuff" (i.e. just writing Python and compiling with Cython) you get about a 50% speedup in tight loops; sometimes that's good enough, other times it's not.
To go any faster than that, you basically need to start writing C code, using the CPython API, using Cython's badly under-documented features for doing so.
* Poor syntax support in the language. E.g., you have to define TypeVars as variables in a scope outside of the unit you want to use them in, potentially conflicting with other generic functions. No clear guidance from the documentation about how the type system interprets these, either.
* No recursive types; e.g., can't define a JSON type
* Some callables can't be typed. I'm thinking they're those with kwargs and/or args, but I forget the particulars.
* Unions aren't true ADTs; the only way to define single-value variants (e.g., "null" in a JSON type) is to do something verbose and hokey, like creating a type that can only have one value.
* Lots of common libraries can't be spec'ed and therefore can't be typechecked. Things like SQLAlchemy which generate their types/classes at runtime, for example.
* Generally, the implementation is buggy even for mundane cases
Beyond that, some particulars:
* Being terse is a lower priority than usual
* Cool features are a lower priority than usual
* Be careful how you use and then re-use symbols (Haskell can be confusing this way).
* Use English (or whatever spoken language) in your symbols
[1]: I just made those up, but I wouldn’t be surprised if they were real.
That said, all of these things are symptoms of the preferences of the community around the language, and not restrictions imposed by the language design itself. Core Haskell is actually comprised of some pretty easily understandable functions. You can craft synonyms for more advanced abstractions such as applicative fairly easily once you understand the type system. Haskell more or less gives you everything you need to write functional programs in a way that reads like plain english--this is very uncommon however, given the language's origin and close connection with mathematics.
On the other hand, once I did get used to some of the infix operators, I started to really like some of them, i.e. how:
f data data
becomes
f <$> fancyData <*> fancyData
where what makes the data fancy might be validation, being in container like list, or even some sort of reactive-ui-component, like Flare library for Purescript does: http://try.purescript.org/?backend=flare
Some of those are very needed constructions (like algebraic data types with pattern matching), but it's adding an entire sub-language into a language that is built on the goal of being simple. I'm not sold into it either.
The sum types are create by multiple data declarations joined by directives. It's ugly, messy, error-prone and as your sibling comment says, misses the point of enabling static verification.
Everyone feels like X language is so great but if only we could add a feature from Y language to it. They then attempt to trivialize what is needed for that feature not realizing that the benefits of Y are due, in part, to the non-trivial amount of functionality. They almost always turn into a façade of the feature they truly want to support and they never gain traction because their usefulness just really isn't there.
https://www.python.org/dev/peps/pep-0020/
"Explicit is better than implicit."
My understand was that the big issue was the fact that [third party] libraries assume that strings were made of 8bits characters.
How does coconut solve that issue?
Seems a little misleading....
if you import enough things from `six` or `__future__` then you code will run the same on both python 2/3.
some things like fancy tuple expansion aren't available if you work with that subset, but yeah, most things are OK.
https://coconut.readthedocs.io/en/master/DOCS.html#compatibl...
I wonder how they are doing tail call optimization in python
https://github.com/evhub/coconut/blob/07e311fb8f69861d30e58f...
That said, it looks a lot more pleasant than Python. The addition of pattern matching alone makes this seem like a very worthwhile project, a big improvement on Python.
Furthermore over the years Guido has made it pretty clear that he doesn't particularly like or care about functional programming: https://python-history.blogspot.com/2009/04/origins-of-pytho...
I've actually started wishing for the opposite change in my use of Clojure. There, it is currently optional to supply a name for the fn form. Always supplying the name, like in "(fn add-42 [x] (+ x 42))", would make stack traces more readable and the named function object would be easy to identify when it appeared in the REPL / pretty-printed data structures.
Not being able to define a two-line function in the place where it's used because you have to give it a name discourages FP in a way many other modern languages do not.
def something(x):
def myfunc(y, z):
...
return result
reduce(myfunc, x)
So the difference is like having to write x = some_very_complex_expression_that_can_get_quite_long
some_function(x)
instead of some_function(some_very_complex_expression_that_can_get_quite_long)
In many cases this may make code even more readable.I'm not saying that Python's lambdas are perfect as they are or that multi-line lambdas would be a bad thing. I'm just saying it's not as bad as you paint it.
'More readable' is subjective. It's clear that Guido and much of the Python community believe that FP is less readable, less pythonic, and that viewpoint is exactly the sort of hostility towards FP that I'm talking about. And in no small part this is a self-fulfilling attitude, e.g. "python lambdas are ugly because pythonistas believe FP is ugly." They're way uglier in python than in other languages. The ugliness of python's lambda is a trait of python, not a trait of FP, yet it's ugliness cited to justify itself.
> when a for loop would be faster
Hey, that's fine. But I think much of the time for the sort of things I'm working on, that's deep into "premature optimization" territory. I'd rather a slight linear slowdown with more readable code than a slight linear speedup with less readable code. I'd prefer that python not "subtly discourage" me to nudge me towards less readable code because a list comprehension is marginally more performant.
Generators also get you another FP checkbox because you now have laziness in a very accessible form. You get building blocks like infinite sequences and so on. And you have access to all the normal primitives like reduce, take, partial, groupby etc in the itertools/functools std modules.
def reduce(enumerable, fun) do
result =
Enumerable.reduce(enumerable, {:cont, :first}, fn
x, :first -> {:cont, {:acc, x}}
x, {:acc, acc} -> {:cont, {:acc, fun.(x, acc)}}
end)
|> elem(1)
case result do
:first -> raise Enum.EmptyError
{:acc, acc} -> acc
end
end
Seems pretty straightforward to me.https://github.com/elixir-lang/elixir/blob/v1.7.4/lib/elixir...
(define (reduce-left fn init lst)
(if (null? lst)
init
(reduce-left fn (fn init (car lst)) (cdr lst))))
It's clear from that quote that Guido (at least at that time) doesn't have real experience with functional programming and is letting his personal biases cloud his judgement.Okay maybe '"scheme isn't a functional language because it allows side effects and only Haskell and family are allowed to call themselves functional", or whatever. He seems to perhaps hint at that mentality. But the fact remains that a language like scheme makes this much easier than python.
foldl _ acc [] = acc
foldl f acc (x:xs) = foldl f (f acc x) xs
And in Python something like def foldl(f, acc, l):
for x in l:
acc = f(acc, x)
return acc
Which doesn't seem much harder to read or write than any other version, to me (it's all just syntax around the same algorithm).In fact Python taught a generation of programmers to love functional code, because of the big speedup available from stringing together C-native builtins with higher order functions.
One of the reasons I love Python is because of it's syntactic brevity and conciseness, once you get to intermediate python there are some weird forms of syntactic sugar (magic functions and unpacking for instance) but visually the language has less clutter than most popular languages. Coconut really just seems to remove that aspect.
That said, it immediately told me that I wouldn't be interested in Coconut, so I also found the label valuable as a non-Python person.
Edit: Of course I should have read TFA before replying. Of course this is not a Python like language, but a language that compiles to Python. I agree the cute name is confusing, but if the point is to compile to a specific language, saying what that language is is pretty important ;-)
And I don't see how that's a problem? Every language has a subculture around it, language specific jargon, even inside jokes.
Pythonic stems from the idea of simple ways to do "stuff", and that's pretty much it.
JS and Java... well they're not something I think a lot of programmers want to be. JS has a pretty complicated ecosystem that a lot of developers don't like. Java is good at what it's good at, but it's not very fun.
What I see a lot of is "lisp" or "lisp-like" and "pythonic". Those languages are notable for being considered fun by a lot of programmers.
But yes, it's idiomatic. Python is an idiomatic programming language that has sacrificed a lot for that ideology. Now that guido is gone it's less ideological, and we're getting things like type-hinting.
When people try to apply that same ideology to other fields, sometimes they call it pythonic. When they try to apply the lisp ideology to other fields, they just call it a lisp dialect instead of lisp-ish.
Learnt this in Pycon 2014 if I recall correctly.
Are type hints Pythonic? I personally don't think so - to me, they don't match the philosophy of the rest of the language. On the other hand, one could argue that the definition of Pythonic is simply "whatever Guido likes".
I actually much prefer modern JS to modern python. JS is flexible enough to allow a functional-ish style, which greatly improves code clarity in my opinion. And of course you can still write python-style code if you want to. Python is a lot more opinionated!
Meaning, as an exmaple Java and JS aren't known for their constant references and inside jokes relating to Monty Python. That is part of the culture and community around Python.
Saying something is Pythonic instead of saying it is idiomatic, is itself, Pythonic. It is part of the culture grown up around the language. And that's okay.
I've seen it for other languages, including those two, and denying the term wont deny the concept.
I've seen perl, python, and javascript written like C, java written like perl, etc. Its always a bad idea - each language has strengths in the ability to express concepts to other devs, part in syntax and part in conventions, and swimming upstream against those never helps anyone. (Evolution is great, chaos is not)
None of which says that these terms CAN'T be used in an elitist way, but I dont see that as the primary purpose or use (at least in the circles I'm exposed to)
Whenever I write Python I try to keep it 'pythonic' and that mainly means keeping things simple, very explicit, and concise. These are obviously traits that all developers want their code to have, but sometimes the syntactic sugar of a language can make it difficult, I think the Pythonic philosophy provides guardrails against this.
"Pythonic" doesn't just mean "idiomatic". Python has design principles[1] that influence new idioms.
I hate to be negative and I appreciate this may be due to the requirement to make Coconut a superset of Python, but the pattern matching and the partial application look anything but elegant (or Pythonic) to me.
x -> x * 2
It looks much cleaner than the traditional :
lambda x : x * 2
Would a PEP adding this syntax have any chance to pass ?
Python wants to be broadly good enough for everyone, not perfect for any one person's specific domain and expertise level. I think this might be the right attitude.
So where in javascript you would write:
const double = x => x * 2
In python you can simply write: from operator import mul
double = mul(2)
[1]: https://docs.python.org/2/library/operator.html from functools import partial
import operator
double = partial(operator.mul, 2)
And at that point I think it would way simpler to just define a normal function. def double(x): return x * 2
Python's `lambda` construct creates anonymous functions - its limitations mean it should only be used when anonymity is required. If a function is to be given a name, there is no reason to use a lambda over standard function syntax.I'm a big fan of the way Haskell manages operators and partial application.
double :: Num a => a -> a
double = (2 *)foo = a -> b -> a+2*b
It's pretty lispy language.
You docs should show it, not tell it.
[0] https://xon.sh/ [1] https://github.com/xonsh/xonsh/issues/1336
Really really curious, what are those reasons?. Not being sarcastic, just want to know.
Are there performance implications?
Overall, for the majority of use cases I have found Coconut code to be approximately as performant as Python code written by hand. The compiler speed can leave a little to be desired though.
This seems like a cool syntactic layer for people that are already writing Python and wishing for a more functional style. However to call this a functional language would betray the definition.
But I can't help but wonder if companies ever go for these kind if extensions / frameworks / languages.
I will play around with this one though!
Should the order of the second list be reversed?
i.e. |> corresponds to f(args) and |> corresponds to f(args)
http://docs.hylang.org/en/stable/
"hy - A dialect of Lisp that's embedded in Python"