Hacker News new | past | comments | ask | show | jobs | submit login
Motivation – Keli Language (gitbook.io)
285 points by azhenley on Aug 31, 2020 | hide | past | favorite | 290 comments



I think it's great that Keli is designed with IDE support in mind. However I believe that this is only half of the reason why FP still doesn't really break through in the corporate world.

The other reason is that many FP users are too enthusiastic about creating abstractions. This is of course something that FP is exceptionally well suited for. An api that was written to simply process a list of Orders into a Report might be abstracted into a fold on some monad, which at first seems a great idea. But if you're not careful, readability suffers a lot. It's much easier to get to know an application when its code deals with business objects that you already understand well, than to read hundreds of lines of code that deal only with abstractions.

Maybe there should be a new edition of Clean Code written specifically for FP, to address this anti pattern.

By the way this problem is not unique for FP, OOP suffered the same problem in the past. But the OOP community learned from its mistakes and now most developers know to 'prefer composition over inheritance', for instance.


> An api that was written to simply process a list of Orders into a Report might be abstracted into a fold on some monad, which at first seems a great idea. But if you're not careful, readability suffers a lot. It's much easier to get to know an application when its code deals with business objects that you already understand well, than to read hundreds of lines of code that deal only with abstractions.

Do you have a code example? It would be interesting to see what this kind of code looks like.

Personally, I find complaining about a ”fold over a monad” to be equivalent to complaining about an ”integer indexed loop over an array”. It’s a pretty straightforward implementation detail that have very little to do with your business logic.

And business logic is far, far easier to model using ADTs (it’s literally just AND and OR, applied to data structures) than confounding object inheritance hierarchies.

In fact, I’ve seen a lot more unnecessarily abstracted garbage (usually “design pattern” workarounds to limitations in the object model) in just about every oop based web framework I’ve worked with.


Well, we're used to the AbstractBeanHandlerFactoryFactories already. A slight discomfort of learning something new can seem much bigger when you're already acclimated to abuse.


Posting to name-drop SimpleBeanFactoryAwareAspectInstanceFactory



They're talking about Existential Crises in another post today.

I think I still have a copy of the book that Johnson wrote to accompany the creation of Spring (which someone wants $300 bucks for on Amazon, I didn't know it was a collector's item). The last time I used Spring, I had the distinct thought that I should go through his book as if it were a bingo card, comparing it with the latest version. It seems like it has reacquired all of the bits of J2EE that it set out to avoid.

If, outside of Spring, there is anything in the Java world that more perfectly embodies Nietzsche's warning about fighting with monsters, I don't know what it is.


The guy next to me on the bus is asking why I'm laughing so hard. I'm not sure how to respond. "These class names are really funny!"


InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState


I'm glad we got the Simple one


Sometimes, though, you can only get the desired effect with SimpleBeanFactoryAwareAspectInstanceFactory_EX.


Thank you! I’m deeply familiar with the phenomenon!


I'd prefer readability to suffer because the fold over a monad needs something clever that requires a bit of reasoning to understand than because vast amounts of "readable" boilerplate (for example, trivial getters and setters) hide a needle in a haystack.


Maybe in the 00s, but these days most languages have collapsed boiler plate down massively.

This comes across as an old fashioned view (bordering on extremely old-fashioned).


I haven't seen evidence of reduced getters & setters in my day to day yet - they still seem pretty prevalent when OOP comes into the picture. I'd even say they got a bit worse because now it's excessive getters and setters with gigantic JavaDoc style comments all around them. If I see "@returns id integer The ID" one more time I'll be tempted to throw my monitor out the window.


What are you talking about? Get setters used to be 10 lines long like:

    int id;
    public int Id
    {
        get {
          return id;
        }
        set(val) {
            id = val;
        }
     }
They're now one liners, and obviously necessary

    public int Id { get; set; }
I don't keep up with Java any more, but C# has super short shortcuts for assignment like:

    public int Id { get; set; } => generateId();
If you, or your company, are misusing a completely optional documentation feature, which you definitely should not be using to annote obvious stuff like IDs, that's your problem, not OOPs.

Switching to a functional language won't save your monitor, you'll still be forced to write bad code if doing stupid things like that are in your style guide.


Your comment is entirely specific to C#. Java has no such shortcuts, its setters and getters look identical to how they looked in 1994:

    private int id;
    public void setId(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }


> If I see "@returns id integer The ID" one more time I'll be tempted to throw my monitor out the window.

I honestly believe we're in a dire need for a more up-to-date metaphor that lets us better express our anger at a computer, due to monitors becoming paper thin and lightweight in the past decade or so.


> > If I see "@returns id integer The ID" one more time I'll be tempted to throw my monitor out the window.

> I honestly believe we're in a dire need for a more up-to-date metaphor that lets us express our anger at a computer, due to monitors becoming paper thin and lightweight in the past decade or so.

My experience may not be representative, but I believe I have encountered variants of "hurl the keyboard at the monitor" more often than "monitor out the window".

OTOH, that phrase doesn't account for laptops.


> OTOH, that phrase doesn't account for laptops.

It is also not anger-ready for people working on tablets with rubber keyboards attached.


Hitting the desk with a fist of moderate force is harmless and rattles lightweight modern computer equipment very satisfactorily.


Putting fist through the monitor?


folding over a monad (or any other combinator) doesn't require extra reasoning or anything clever, that's the point.


I think HelloNurse's point was less that a fold is inherently clever or subtle (as you say, it isn't), and more that, if there is something clever or subtle involved, the lesser verbosity of the fold makes it easier to see than would a more familiar, boilerplate-heavy approach in which the clever or subtle point could be lost.


It does if you are unfamiliar to the pattern. It probably has more to do with experience with the patterns used rather than readability.


I just wrote about some possible pitfalls down the ADT route: https://treetide.com/posts/domain-model-pitfalls-oop-fp.html

> As more operations are demanded, it is more likely that the existing partitioning of the world into the nice distinct cases won’t suit that operation anymore. Then, as a fix, we can introduce more specific cases, or make existing ones more general - leading to ambiguity, bloat and mental load for the existing operations.

Just an aspect.


I like it!

It’s the kind of reasonable discussion that I crave in this field.


> Personally, I find complaining about a ”fold over a monad” to be equivalent to complaining about an ”integer indexed loop over an array”.

This was just an example of a behaviour that, if taken too far, could eventually lead to those 'hundreds of lines of abstract code'. Of course a single fold in itself is nothing to be concerned about. But an API that accepts a type 'm a' instead of '[Order]' has lost some of its connection with the business domain already.

Again, this might not be bad, it can even be good, but the problem is that many FP enthusiasts overdo it.

> In fact, I’ve seen a lot more unnecessarily abstracted garbage (usually “design pattern” workarounds to limitations in the object model) in just about every oop based web framework I’ve worked with.

Yes, this is the same problem which pops up in the OOP world as well. I guess it is a human thing to want to use your latest powerful language tool wherever you see a possibility for it.


> 'hundreds of lines of abstract code'.

Yeah, I’ve spent way to much time digging into “hundreds of thousands” of lines of abstract OOP code. And it’s sadly, largely ceremonial, mostly due to limitations in the language. The less fanatic about OOP the language is, the more useful it is, is a good rule.

But it’s absolutely not exclusive to either paradigm.

> But an API that accepts a type 'm a' instead of '[Order]' has lost some of its connection with the business domain already.

I completely agree, but I’m also curious how any API interface like that would pass code review. Since it’s so obviously too general.

But I guess the perceived upside is that the equivalent oop-construction would not get caught in code review.

> Yes, this is the same problem which pops up in the OOP world as well.

I know. I’ve spent more time working within oop than without. The type based FP-world just have orders of magnitude better tools.

> I guess it is a human thing to want to use your latest powerful language tool wherever you see a possibility for it.

This is kind of a sad way to dismiss people, as if they’re some kind of ephemeral fireflies...

The reality is that trying to force reality into in an inheritance-based, tree-structured taxonomy, is not only stupid, it also creates endless maintenance headaches.

Some problems fit that model better sure, but most things fit a much simpler ADT-based model since it’s much closer to how we think logically. (In terms of logical primitives and implicit recursion)


I think many of the responses to your parent post are focusing too much on `fold` and not the custom monad that is implied to just be a re-implementation of list.


> By the way this problem is not unique for FP, OOP suffered the same problem in the past. But the OOP community learned from its mistakes and now most developers know to 'prefer composition over inheritance', for instance.

This is a great point. I think there are a lot of open questions about basic questions such as how ergonomic effect systems can become and which functional programming abstractions become intuitive through enough practice versus always feeling confusing. I think that stuff will shake out in a decade or two, and we will end up with much better styles to choose from. Who wants to program in 1990s/early 2000s Java style anymore? (Well, okay, a lot of people, but ignore them.) Nobody would judge OOP negatively based on that mess, because we know it can be done a lot better, and languages have evolved to cater to what we've learned. I think functional programming has a lot of evolution to come that will make it more ergonomic and more practical. It will be especially interesting to see what the cohort of programmers introduced to functional programming via modern front-end Javascript will do as some of them transition to the back end and look for statically typed languages that are better than Typescript.


C#, mostly.


>The other reason is that many FP users are too enthusiastic about creating abstractions.

Elm lacks typeclasses, yet I've found this lack is what keeps a lot of libraries at bay that would have otherwise turned out overly abstract (see any mainstream Haskell library, really). It's the same reasoning behind Go: With great power comes great responsibility. At large, programmers shoot their own feet with powerful languages. Therefore, take away their guns, ie make languages less powerful. I still wish there was something like Elm, but tailored for backend/network programming: https://news.ycombinator.com/item?id=21909087


I started with Elm, then I started working through Learn You a Haskell. At first I thought the lack of typeclasses in Elm was a big missing piece, but now I see the potential for me to turn a programming problem into a little-too-philosophical debate about the nature of truth in the universe or some such. In other words leaving out typeclasses may help but not guarantee that I keep my eyes on the screen and off of my navel.


The hard part with Elm’s lack of typeclasses is the lack of do notation. It’s pretty easy to end up in Elm’s version of “callback hell” where you’re nested several `andThen`s deep. And that’s for a language that doesn’t have `IO`. That said, I would love to try an “Elm on the backend” that specifically lacks features compared to Haskell.


Scala seems quite close. It has a lot of features. But it's flexible enough that it doesn't "hurt" as much as writing production code in Haskell.


The advantage (or potential problem as you see it) is typeclasses encourage thinking about the meaning and behavior of what you are doing.

For simple cases, the end result of no typeclasses can be compelling sometimes.

For larger cases, taking away that useful tool to wrangle inherent complexity usually results in more complexity taking the form of gobs more simple code that obsfucates the task at hand.


Overuse of abstraction is penny-wise and pound-foolish.

It is penny-wise in that it saves some code, but pound-foolish in that it makes maintenance harder.

Why? Because when you have an abstraction, your code, possibly in many places, depends on it. Now if you need to make a change, you will need to understand both the abstraction, and your "instance" of it.

Well no rather you need to understand ALL instances of that abstraction, to know that if you change the abstraction you are not breaking ANY of its uses.

Penny-wise and pound-foolish in many cases.


I agree with you because you said overuse - but I wanted to reinforce that this is statement reads: "It's a bad idea when it's a bad idea" which isn't super helpful and might give the impression that abstraction is usually the bad option. There are very very few times when I've seen the correct answer between abstracting code and leaving it as-is being leaving it as-is when a good abstraction is possible - but I've also seen a lot of incorrect misaligned abstractions that abstract functionality that coincidentally looks the same (i.e. sharing similar business rules as an origin) but is actually quite different. If you're calculating the final price on a transaction and that item could be either a donut with a 5% food tax or a t-shirt with a 5% sales tax it's not appropriate to abstract taxes to be 5% - they're different taxes and just because they happen to be the same number making them reference the same logic block or constant is a Bad Idea(tm).

Abstraction is (generally) an investment in the long term health of your system, whether you, the dev, can justify the costs to higher ups or not is usually a pretty big part of the question. But, if there are legitimately abstractable things you can abstract please do abstract them (and add tests over everything).


> Abstraction is (generally) an investment in the long term health of your system

Don't forget the Opportunity Costs. If you spend much time abstracting, it is less time actually producing something that helps the users of the application.

Now if your business is maintaining an existing application it makes sense you should invest in making its maintenance easier.

But if you are programming a new application you want to get it in production fast, so that you can learn about what needs to be different about it.

I would say that the time to think about maintenance is mostly when you are in the maintenance stage, because of the huge benefit of getting early feedback,


if you don't mind losing static types, there's erlang and elixir.

> I want an FP ecosystem that's not rooted in research

Erlang is rooted in practicality. https://www.youtube.com/watch?v=7AJR66p5E4s&t=180s


> It's much easier to get to know an application when its code deals with business objects that you already understand well, than to read hundreds of lines of code that deal only with abstractions.

This feels backwards to me. If I‘m new and join your team I’m much more likely to understand what a monad and a fold are than to understand your domain specific business objects.

So if we are working for an insurance company and see the ‘Insurance’ type is an instance of Monad I now know a great deal about combing different insurance products into new derived ones. By reading a single word of code!


There is plenty of FP in the corporate world, the mistake that many FP advocates make is to make it a war of FP vs anything else.

Meanwhile multiple paradigm languages keep getting most of the features that actually matter to Joe/Jane developer.


> the mistake that many FP advocates make is to make it a war of FP vs anything else.

I don't disagree with this (goodness knows there are plenty of functional purists who come across as religious zealots), but I think it's also worth mentioning that there's an opposing camp of people who are hard-set against ever considering FP for anything at all because they've bought fully into object orientation and stateful computation.

Both solutions are well-suited to certain kinds of problems, and this opposition is well summarized by study of what is called the Expression Problem[0]. A good developer should know both paradigms (in addition to others!) and use each where it is best suited.

[0] There are other issues than those discussed by the EP, but the EP is, I think, the most simple perspective of the distinction between OOP and FP.

Edit: TIL you cannot escape an asterisk on HN. That's a bummer.


In my experience, I have seen much less of the OOP-only group, especially in recent years. The general feel I have seen when communicating with other developers about this is that OOP is a tool and it's probably not the best one, but they are comfortable with it and it's problems.

In general it feels like the overall feeling to FP is either

"I don't have time to learn a new paradigm when my current one is working good enough to make the company money"

or

"I love using FP ideas in my code when it makes sense"

I really feel like this is the ideal state for Software Development, as either side "winning" will only hurt the robustness of the environment.


Oh, sure! I didn't mean to suggest that there were as many as the only-pure-FP crowd, though I can see how my phrasing may have suggested as much. I just meant to bring up that there are people like that, who refuse to (consciously) adopt any amount of FP in their development. I have interacted with some myself. Most of them make arguments about, like, "People think in terms of state so FP is inherently a bad user experience" or something to that effect.


I've had people chew me out for using function objects in Java, saying it's too complicated and the newest thing isn't always great. I wanted to point out that Java 8 is 6 years old now, and qsort() has been a thing for a while.


I remember when I discovered "map" and "filter" in Python around 2000 or so and everybody I showed them to at work (it was a Java shop) thought they were so difficult and abstract compared to

  ArrayList result = new ArrayList();
  for (int i = 0; i < input.size(); ++i) {
    etc.
To them that was the "easy" way, and I was a weird kid into weird shit. I didn't make any progress with them at all trying to convince them that "map" and "filter" were simpler than iterating over an index every single time. Now everybody has forgotten that business logic used to be one for-over-index loop after another, often nested, and debugging an error often meant running through a for-loop in a debugger over and over again examining the partially constructed result at the end of each iteration trying to figure out where it went wrong. FP saved us from that, and it gets no credit. People only think of FP as the scary stuff they haven't learned yet.


The funny thing (that I'm sure you're aware of) is that 'prefer composition over inheritance' is an instance of 'prefer referential transparency', an idea from FP that you should always know what a variable is referencing. In OOP, this means that inheritance is bad because it's not always clear what functions are inherited and/or overloaded.


> an instance of 'prefer referential transparency', an idea from FP that you should always know what a variable is referencing.

The idea of "referential transparency" is that you should always be able to substitute a reference for its definition, or vice-versa, without changing the behavior of the program. This implies both immutability (so that duplicating a value doesn't change the result) and purity (so producing the value has no side effects and gives the same result no matter how many times you do it). It has nothing to do with knowing what a variable is referencing. If anything, functional programming encourages the use of generic functions which don't know or care what their variables are referencing (parametric polymorphism).


> Maybe there should be a new edition of Clean Code written specifically for FP

I’ve posted links to this work-in-progress book a few times in the past, but I’m going to do it again because I think it fits the description perfectly.

Grokking Simplicity by Eric Normand [0] is a great book for introducing the premise behind FP in a pragmatic fashion. It doesn’t get bogged down in monads and type theory, just the benefits of pure functions and modeling applications as flows of data.

All the code examples are in JavaScript, so it requires no prior knowledge of functional programming, and it does a great job of building things up from first principals while highlighting the benefits vs a more object oriented approach.

It’s only half complete, with a nebulous completion date, but I’ve already purchased a copy, and it would the first book I’d recommend for any newcomer to FP (it’s already worth it just from the first half as far as I’m concerned).

[0] https://www.manning.com/books/grokking-simplicity


FP abuse can lead to abstractitis which is sad but nothing the Corporate World doesn't like ... it was the land of J2EE after all.


"... too enthusiastic about creating abstractions."

Like others, I'm eager to hear more of your thoughts.

I also have a theory. A bit of a riff on Rob Pike's observation (IIRC): "Show me your data and I'll understand your code".

Enterprise-y (corp IT) projects are data centric. Functional programming emphasizes flow-of-control over the data flow. Even more so than functional decompensation (imperative programming) and object-oriented.

Donald Norman's notion of "affordances" applies. Any one can certainly "do COBOL" in Scheme, but pushing that rope requires uncommon insight and intention.


The reason why FP still doesn't really break through in the corporate world is a marketing failure, in the strictest sense of the word marketing, which means prioritizing the right features to target the right market.

A large proportion of this talk is based on the book “Crossing the Chasm". The book was originally written for startups, and this talk adopts the book to an open source audience, especially Haskell developers.

Gabriel Gonzalez – How to market Haskell to a mainstream programmer: https://www.youtube.com/watch?v=fNpsgTIpODA


I think the word abstraction here is overloaded. OOP is also fundamentally driven by abstractions. The difference is that FP abstractions are mathematical abstractions, while OOP starts with human-centric conceptual abstractions - the kind every two year old is taught. These are not the same in terms of cognitive understanding. I also use FP approaches all the time in OO languages (like C#), which I believe is more aligned with where the general purpose language world is moving.


Abstractions are drawn along different axes in both OOP and FP.

The showcase example "Circle, Square, Rectangles are kinds of Shapes that have area" represents an abstraction that is present in the conceptual schema (aka Domain Model, aka Universe of Discourse, aka "problem space"), regardless of implementation style.

Likewise, what you call "mathematical abstraction" (and I'd prefer to call a "implementation model") is present both in OOP (eg "visitor pattern, inheritance, facade") and in FP (eg "fold, pattern match, monad"). I don't really think the difficulty in groking "visitor" is much different from groking "fold".


I don't see a reason why some FP languages (e.g. lisp, or elisp) aren't IDE friendly. It might well be because where there is unpopularity, there is less effort to integrate, promote and maintain. If everything is a function and functions have positional parameters and everything named can be in a symbol table, why can't IDEs support (at least the basics of) FP languages?


I don't know much about modern IDEs because I don't use them, but I suspect that the very powerful lisp macro system can be an issue for good IDE integration. Think of something like the "loop" construct in Common Lisp: http://cl-cookbook.sourceforge.net/loop.html

That's related with the problem the parent is talking about: it's very easy to create DSLs in in languages like Common Lisp, so people use them a lot. But for an IDE's parser, or even for a new coder discovering the codebase, it's pretty tricky to figure out what the code does.

If somebody is ultra-familiar with Common Lisp but for some reason never learned how the loop construct worked they might be stumped when they encounter the following code:

    (loop for x in '(foo 2) 
          thereis (numberp x))
What does that do exactly? What will it evaluate? What will it return? Which one of these symbols are special keywords and which one just refer to variables or functions?

Meanwhile if you code in, say, C, the macro system is so crappy and clunky that you basically avoid it for anything complicated. Writing a similar LOOP macro in C would be an absolute shitshow (not that it prevented people from trying). As such C code tends to be much more verbose, but also much easier to follow if you know the core rules of the language.


That's an interesting theory, but not true in practice. Common Lisp IDEs are great and don't have any problems with macros. Even going back to the 70s and 80s, with Lisp machines.

Using Slime, the Common Lisp IDE mode in Emacs, there are at least four ways to answer your loop questions:

1. Ctrl-x, Ctrl-/ to look it up in the Lisp spec

2. Alt-. to jump to the definition

3. Alt-x slime-macro-expand-1 or Alt-x slime-macro-expand-all to see what code the macro generates

4. Execute it in the REPL and see what it does.

Option 1 only works for things defined in the standard, obviously, but options 2-4 work for everything.


Perhaps if Macros where not such a mainstay and powerful element of some FP langs (I am not arguing against them), this argument would not be so strong. Macros in C/C++ are difficult for IDEs to deal with too, save for constants. Seeing though that macros are near fundamental to some FP langs, I understand better the driver for the design of Keli.


What gave you the idea that most FP uses macros? Much code in functional languages uses no macros at all. Perhaps you are confusing FP with Lisp.


Read the OP link.


Fold Over a Monad? You mean like:

``` ourReportAccumulatorFunc :: Monad m => m a -> m a -> m a ourReportAccumulatorFunc = blah

ourFunc :: (Foldable t, Monad m) => t (m a) -> m a ourFunc = foldr ourReportAccumulatorFunc someUnitValue ```

Why do you think a fold would be hundreds of lines outside of the business logic?


Quick tip: indent 4 spaces to get code formatting.


Actually it's only 2 spaces.

  Like this.
    Not this.


I think the problem is FP has built its own set of abstractions that imperative programmers aren't used to.

Imperative languages have slowly been adopting some of these. 20 years ago you had people talking about how things like map and filter are confusing but nowadays most imperative languages have a version of it so obviously it isn't that confusing.


I don't know if this counts as irony, but here goes:

The killer feature of programming with pure functions is that you can guarantee the same output for the same input. If I were pushing this idea 5+ years ago, I would have said "Unfortunately you have to give up your comfortable for-loops and mutable variables* and learn about these weird maps and folds instead, but it's well worth the trade!"

Fast-forward a few years and now maps and folds are everywhere in the mainstream (it's even recommended to use them over for-loops in many cases.) But we never got the same-input-same-output guarantee which was my reason for switching in the first place!

* Even that's being too pessimistic. You can have your mutable cake and keep your guarantees, too: https://wiki.haskell.org/Monad/ST#A_few_simple_examples


> I think the problem is FP has built its own set of abstractions that imperative programmers aren't used to.

What you describe as a problem sounds like a solution to me :)

I'm curious whether you think there are imperative abstractions that could be used over the commonly used FP abstractions.

That avenue of discussion sounds potentially valuable and fun to me :)


When giving an example on how infix notation reads better:

    // This is obviously not too right
    ",".splitBy("1,2,3,4,5")

    // This should be right, because it reads out more naturally
    "1,2,3,4,5".splitBy(",")
It's funny that in Python split works this way but join doesn't. This is because in the case of split both arguments are strings, but for join one of the arguments is a Sequence, which is a general protocol rather than a class.

The proposed solution by Keli is to be inspired by the Smalltalk-style message syntax:

    'Hello world' replace: 'Hello' with: 'Bye'
I think, in general, requiring named arguments is a good thing. Swift does it, and in codebases with a decent amount of TLC, it looks great.

Function calls are one of the weird places were the syntax of the language rarely helps you figure out what is going on, and for the most part is just a sequence of human-written names one after another, and in languages with custom operators it could be even terser.

In comparison, if-statements, loops, pattern matching, etc. were (hopefully) designed to be expressive. I think by requiring named arguments, function calls will also be much more readable, relying less on an active human effort to do so.


In Clojure, for functions that will probably have more than a couple args, I like to pass a map, then destructure it by its keys in the function definition. Two benefits are: you don't need to remember the order of args, and it makes refactoring easier in cases where you are simply adding a new optional arg (you don't need to update all the old calls of that function if they aren't using the new option).


That's a pattern I use in a lot of Typescript, it's pretty useful!


We’ve got to write argument names twice when we do it in typescript though:

  function foo({ bar }: { bar: string }) {
Still a great pattern, but I’d probably use it a lot more if I only had to write the variable names once.


I get this might just boil down to preference but I absolutely hate named parameters. They’re biased towards new users of a language and quickly become painful to write once you’re familiar with the function call.

Plus they don’t always improve writability outside of IDEs because you then have to memorise the parameter names and in some functions there’s several terms that could equally apply (if you’re using an IDE with hinting then the advantages become moot as the same IDE would hint the order of parameters).


I disagree that named parameters are just biased toward new users. They're also biased toward reading rather than writing. I find that as a code base becomes bigger and more non-trivial, I end up reading code many times, and the more I appreciate named parameters.


I understand what you're saying, but something I have noticed over the years is that the amount of code that I can make sense of at any given time is actually proportional to what I can see onscreen at any given moment.

I have had pretty good luck cheating this in a bunch of ways: I use a small font, a big display, and I use a terse programming style.

Once you internalize a bunch of common higher-order functions, you learn how to draw a ton of meaning out of a relatively small number of terms.


Yes, but this strategy is only suitable for a single developer, or a small group of similarly-experienced-with-that-specific-codebase developers. Onboarding somebody into a world full of single-character variable names and such is a headache. Named parameters are for reading code, and if you're not intimately familiar with the code on your screen right this second, they are helpful.

I think what we really need is a sort of "lens" system by which we can modify the syntactic appearance of our code without adjusting the semantics, but do these changes on-the-fly. So say you're doing some debugging or whatever and you're gonna be staring at the same 300 lines of code for a few days — so you switch over to "terse" mode and suddenly the named parameters are gone and the variable names are abbreviated (assume a magic system that picks good terse variable names according to your preference). But then when you're done with that section and ready to venture into the remainder of the codebase (or if you're a new developer who's unused to the team's naming conventions or whatever), you can use "verbose" mode that shows the parameter names and whatnot.

I imagine something like this is not obviously straightforward, but it could be worth investigating!


> Onboarding somebody into a world full of single-character variable names and such is a headache.

The opposite of named arguments isn’t single character variable names. Any organisation with an enforced coding standard would ensure that variables are descriptive irrespective of whether that language uses named arguments or not.


Yes, I agree! I was specifically addressing the parent comment's line about "I use a terse programming style." When it comes to functional programmers, they (more than any other group) will take terseness to the extreme in the form of single-letter variable names in inner functions, match forms, etc.

I didn't mean for my comment to be entirely literal, either. Rather, I just meant to say that terseness can impede readability for those who are not yet familiar with the codebase. (But I have personally been on the receiving end of onboarding into a codebase full of literal single-character names, which I found incredibly frustrating.)


> When it comes to functional programmers, they (more than any other group) will take terseness to the extreme

You should really read about APL and other array languages then. (I don't have a good starting point, but they tend to come up on HN periodically such as [0] [1]).

0: https://news.ycombinator.com/item?id=23055793

1: https://news.ycombinator.com/item?id=16847641


The right balance here can depend on the specific business you're working in.

Some companies earn the privilege of a super tenured core team of engineers who work on their product for an extended period of time. They will choose different tradeoffs from a team that needs to adapt to higher turnover.


For a language that says it'll work hard to be IDE friendly, an IDE can easily show you the argument names and their order for any function. On IntelliJ, it even overlay them.

Thus, you can be writer friendly and reader friendly, considering the reader uses an IDE.


I can't disagree more strongly about this. Many, many times I'm trying to understand a codebase, and what I have is that codebase, not the dozens of dependencies involved, and not a full development/build environment. I may not even know what, if any, IDE the original developers used.

There's also very common sources of bugs when functions take multiple arguments of same (or sadly in some languages, implicitly convertible) types. With named arguments in complex functions, you can sit down, read the code, and spot the bugs. Happens frequently enough in code review that we have a category for it. Without named parameters here, every single function call becomes a game of "mouse over the parameter in the IDE". Moreover, "you can just read the docs inline in the IDE" also causes people to not think much about naming things, which also harms maintainability.

It's a real issue in large, long-lived codebases that may not seem like much in smaller projects.


But why not turn this around? Let the IDE autocomplete everything, so it’s easy for the writer (or even hide the named arguments). And have the named arguments in the source code, then all readers can read it no matter their IDE!


Because it's a pain to navigate with the keyboard, plus accidental completion means more deleting, and other little niggles like that.


They are biased to new readers of a codebase, not of a language, which is really just another way of saying that the code is very readable.


I don’t think it’s fair to say what’s readable to new developers is the same as what’s readable to experienced developers nor even every developer.

For example the terse style of Sexpressions or C-style braces are off putting to some but after a short while they become second nature to visually parse (not saying all code should follow Those idioms, just using that as an example of how readability changes with experience). On the other hand I never found it as straightforward visually parsing the “word soup” of named arguments when using languages that favoured it. While those languages were easier to learn the basics, they quickly became tiresome to write larger code bases with.

But I guess this just boils down to personal preference.


I'm talking specifically about the function calls, which are effectively the same in C, Rust, Python, Lisp, Haskell, and many others. No matter how terse, it's always some form of

<name of function> <value1> <value2> <value3> ...

The absence/presence of parenthesis and commas don't add anything of semantic value here, so that's not really what I'm referring to.

The problem is, is that the values often don't have any meaning attached to them, especially when they are literals. C++ codebases are pretty bad about this with boolean parameters, just having a random `true` at the end of the function call.

Variable names at the call-site are good hints, but we often pass the same variable to different functions, and the name doesn't exactly fit the meaning of each function. This is why I think requiring named parameters, at least by default, is better.


I agree random boolean arguments are unhelpful. Good software development standards would say any uses of boolean values should be substituted with well named constants if the same value. However the advantage of having that baked into the language rather than forcing developers to via code reviews isn’t lost on me


Disagree. Particularly since named arguments are part of the function name and in languages where you use named arguments you will often have very expressive overloaded function names.

You might have 3 related functions like so:

  getStudent(datastore: DS, byId: number)
  getStudent(datastore: DS, byAssessmentId: number)
  getStudent(datastore: DS, firstName: string, lastName: string, birthdate: date)
Without named parameters, you are stuck with function names like: getStudentById, or relying on convention for naming parameters and often wind up with duplicate names or not knowing if a function is out there because your idea of naming is different from someone else's.

Intellisense picks this up too and suggests the 3 different names. Works fantastic.


I get overloading is a popular way of writing DRY code but I’ve never been a fan of overloading either. You might dislike getSomethingBySomething style function names but it’s no different to the examples you’ve given in terms of readability but with the bonus of having fewer surprises.


Should I look for the function called: `getStudentByClassRoomFirstNameLastName` or is it `getStudentByClassRoomLastNameFirstName`, or `getStudentByClassRoomFullName` since you sort that way. They you have to deal with optional parameters, do you have an extra argument for that or is there a `getStudentByClassRoomFirstNameLastNameMiddleName` function out there? Now you are scanning the auto-complete code trying to figure out which function you need to call.

If you have named parameters, this just works:

  `getStudent(classRoom: 15, firstName: "Mary", lastName: "Jane")`
  `getStudent(classRoom: 15, firstName: "Mary", lastName: "Jane", middle: "Anne")`
It can call the same function with optional parameters, or 2 different functions and you don't care from the calling side because the syntax is the same and always makes sense.

(slight tangent)

Far better with overloading in general is being able to have 2 methods with the same name which return the same thing but have different types of inputs. Like in Elixir, you can have an API callback be `handle(apiResult)`, then have 2 functions, one which handles the error and one which handles the success. Zero logic written to filter out bad-calls, it's just the same method with different types (the Error result type and the Success result type). Vastly simplifies and cleans up that kind of code.


Even better is Elixir's Ecto or Linq

    Students |> Students.Repo.get_by(first_name: "Ryan")

    from s in Students where firstName = "X" select s
    
    Students.AsEnumerable().Select(s => s).Where(s => s.Name == "X")
etc.


Elixir is what I was thinking of for much of the above, unfortunately, I only used it fairly briefly and so I was going largely on memory and some of that is likely more Swift than Elixir which shares some of that.

I really loved using Elixir, very frustrating going back to TypeScript after that.


I'm honestly curious what you see as the issue with "getStudentBy"-type function names. Could you expand on that?

Of course I understand a name could become rather verbose in your last case, but I'm not satisfied that it would be much of an issue in practice.


See above.


Hmmm... I see where you are going there, but I don't understand how named arguments solve the problem[0] you have outlined. That is, a problem of discoverability.

Like what, precisely, does the autocomplete show in your editor when you type `getStudent` vs `getStudentBy` that makes the former so much better? In either case you are left to disambiguate either the correct overload or the correct method unless... well... you already know there is an overload that accepts the data you happen to be working with. Presumably you would also then know the correct method name.

I suppose it's possible to start typing your named parameters directly and the editor "fills in" name you are going for? But that poses nearly the same problem again: "Wait... is it 'nationality' or 'country'? Did we decide on 'lastname' or 'surname'?"

I agree the `getStudentByCountryAndName` isn't a great signature, and that wrapping all of the args into a single DTO like `StudentInfo` is not so great either. But I can honestly say, I have never once run into a problem of finding a method in the wild! Even if we had numerous `getStudentBy`-type functions all with similar signatures/names it probably wouldn't take more than a few seconds to narrow down to the one of interest.

I also find, from a consumer perspective, named-parameters to be less ergonomic. I like that when I type `getStudentById(` my editor boldens the argument I am meant to pass next. I don't have to decide which parameter I am going to pass next... my editor just walks me through the function (maybe I'm just lazy!)

To be clear, I am not arguing against named-parameters. Though I dislike that they broaden the public surface of functions, I suppose I would rather at least have the option to use them than not at all. I just don't really see, in a practical sense, how they would improve a code-base beyond some fairly niche scenarios that are probably a code smell anyway.

[0] It seems to me your argument is more aimed towards overloading than named-parameters in particular, but I will accept that there is significant overlap in this space.


I think a better question is:

Why would you want a function that gets students to be called anything other than `getStudent`? Fundamentally, the simpler, easier to parse your function name is, the more readable and editable your code is.

I definitely think overloading functions is more interesting/ important than named parameters. But named parameters are useful both for overloaded functions and for optional parameters where you might not know what the 3rd/ 4th parameter might be.

One pattern I see frequently in languages which lack named parameters is instead of using optional parameters, people pass objects with optional properties. This is used a ton in Javascript/ Typescript. If the language supported proper named & optional parameters, it wouldn't be an issue.


How would function composition work without currying? You'd have to name everything, wouldn't you?

I'm thinking of how you could write in point-free style or even have a `(.)` function to begin with if you had the Smalltalk "message passing" style.

Sure the Haskell syntax is a bit much when you're not familiar with it but, like almost any language, it's often the least-interesting part of the language and the most talked about.


That's the whole point of the syntax changes: to make the tradeoff that sacrifices terseness or efficiency or even composability in favor of readability.

A lot of functional languages -- Haskell as I understand it is the biggest perpetrator -- optimize for code length and composability, trying to make things as expressive as possible, and decrying those who can't read something that has meaning densely packed into every single character as those who are "simply not familiar" with the language.

But that's what OP is saying: OO languages go for readability and not expressiveness, and wouldn't it be cool if there was an FP language that did the same thing?


I feel like that's a value statement and how you qualify readability would change the answer.

I'm curious if Keli plans to maintain function composition in the face of named arguments. It would be quite nice to have both.


After learning some array languages, functional languages like Haskell are actually pretty verbose.

Moreover, I believe that in general, shorter code is more readable code. People just like to make excuses in order to avoid learning anything new.


Just spitballing, but you could have composition eat the name of an input to the outer function: Given f(x,y) and g(z), write

let q = (f .y g)(z=a) then q(y=b) = f(x=g(z=a), y=b)

let q = (f .x g)(z=a) then q(z=a) = f(y=g(z=a), x=b)

Aesthetically it would be kinda like the notation for fiber product.


They are biased towards new users of a codebase, not only the language.

Named arguments improve readability of a code if you are not used to a codebase. By improving readability, you also improve maintanability: you need less experience in a codebase to make a change.

You focus on writability where it's not the biggest issue of the field, it's software debt.


I disagree 100%. I write with more and more named arguments. There are missing features to make it really nice in python though, like kwarg short forms like ocaml has.


Advocating for trading away readability in favor of easing the lives of devs who refuse to use an IDE doesn't seem like a win, especially in the context of a team.


I’m often writing code into vim (without any fancy plugins installed) and never had an issue. As I stated in my previous comment, I don’t particularly believe named arguments make it any easier to write without an IDE because you’re only trading memorising the order of parameters with the names of those parameters. Either way there’s still a minimum level of memorisation required.

I get each developer is different but personally I remember numbers and orders easier than names.

And frankly, if you have a developer who can’t learn the basics of the language they’re employed to work on yet also refuses to use any developer-friendly tools to assist him, then that’s a failing of the the developer and not the language.


and quickly become painful to write once you’re familiar with the function call

JavaScript's more recent structuring/desctructuring arguments has made this pretty seamless as long as you name your variables well.

For the most part switching from ordered to named is just func(arg1, arg2, arg3) is just foo({arg1, arg2, arg3})

You may need to rename or pass in the params on the call but it's generally pretty nice. As an above comment says about Clojure - as soon as I need a third argument in a function I switch from ordered to named and its pretty painless.


>I absolutely hate named parameters. They’re biased towards new users of a language and quickly become painful to write once you’re familiar with the function call.

It is a fair complaint, but don't you think this is offset almost entirely by a good IDE?


First, having spent the past two years in IntelliJ with Java, no. I still would have much preferred named parameters to having to worry about the order (and still occasionally mixing them up when they're not unique types, and only finding out when things fail).

Second, if a language is heavily reliant on features from an IDE, yeah, I'll echo that's a badly designed language.


Sorry, I agree with you. I like named parameters as well. I meant the overhead of having to type them is pretty much entirely offset by a good IDE that fills them out for you. I don't think there is a downside unless you are coding in a text editor.


Unlike positional arguments? ...


I haven't used an IDE in years. Just VSCode. Which is not really an IDE. I haven't missed it. I think that if a language depends on an IDE to be useful, it's a design smell.


VSCode is an IDE. Otherwise, what is am IDE according to you? Is Emacs an IDE? Eclispe?


VS Code (which I like and have used extensively for a number of projects) is kind of an IDE, but it doesn't quite compare to full blown Java IDEs IMO.


It's an editor with syntax highlighter, autocompletion, go-to-def, and go-to-uses that also builds the code, runs the code, runs the tests, and also allows you to run them (code & tests) under a debugger. Oh, and it also has projects/solutions/workspaces/whatever they're called.

Back in my days™ that's what they used to call an "IDE". But today that's just "glorified Notepad++", I guess? What features does it miss that stop it being a full-blown IDE?


I wrote kind of like an IDE :-)

Maybe I'm just a spoiled brat, but I came to Java from C, PHP, Visual Studio (the old one) and learned how nice programming could be before I left for .Net Core and frontend (with much TypeScript), mostly in VS Code but sometimes in Rider and Webstorm.

To me, to be a real IDE these days you need to have proper refactoring support.

And to a spoiled brat like me that excludes almost everything except Rider, Visual Studio with Resharper, NetBeans, Eclipse and IntelliJ :-)

But yes, I too am old enough to remember back when the C/Assembler program we used to program microcontrollers were considered IDEs.

And technically you are of course correct :-)

I guess a better classification would be: IDE level 1,2,...n where what I call IDEs today are really level 3 IDEs.


maybe named parameters only for functions with more than 2 arguments might be a good compromise?


Hmm, to me it seems that 'splitBy ","' should be a function that takes a string and returns a list of strings:

  splitBy "," : String -> List String
so it seems "clear" that the separator should be the first argument. But maybe I've just programmed functionally for too long?

As an alternative, perhaps there's space to introduce types to make this something like

  split : Pattern -> String -> List String


I agree with your intuition. I'll try to make my intuition explicit:

The 'splittee' should be the last argument. The last argument is usually what you want to a) elide as part of function composition, or b) loop over (the tightest).

E.g. Splitting a file into lines:

    lines content = splitBy "\n" content
can be:

    lines = splitBy "\n"
If splitBy used the other order, it would be:

    lines = (\c -> splitBy c "\n")
E.g. Splitting a file by lines and tabs:

    entries content = concatMap splitBy "\t" (splitBy "\n" content)
can be:

    entries = concatMap splitBy "\t" . splitBy "\n"
If splitBy used the other order, it would be:

    entries content = concatMap splitBy (splitBy content "\n") "\t"


I don't have a firm position on the matter, but consider:

  lines content = content .splitBy "\n"
  lines = (.splitBy "\n")
  dec x = x - 1
  dec = (- 1)
It seems like some infix-like things ought to be equivalent to (oper right left) rather than (oper left right). Not sure how that works out in practice, though.


The question is should the writer go through the effort of placing the parameter name, or should the reader go through the effort of looking up the function prototype? Since according to Clean Code (https://www.goodreads.com/quotes/835238-indeed-the-ratio-of-...) code is more often read than written, it should makes sense for the writer to do it.


> The proposed solution by Keli is to be inspired by the Smalltalk-style message syntax

But that doesn't actually solve the problem.

  "Hello world".replace("Hello","Bye") // works fine
  but
  somesequence join: "," // still doesn't work
  // unless every $Sequence implements join explictly


This particular set of examples is especially ambiguous because "splitBy" can be read two different ways: as a description of the result (noun having been split by a separator) or as an action (subject split direct object by a separator). Which one you choose will subtly affect the way you group the arguments. Conventions in functional programming tend to prefer the former, whereas imperative (including OOP) languages prefer the latter. Either way, however, the string which directly follows `splitBy` should be the separator. In the functional example:

    -- Is this correct?
    splitBy ","  "1,2,3,4,5"

    -- or this?
    splitBy "1,2,3,4,5"  ","
there really is no question that the first version is correct. Even oversimplifying as the article does and treating "basic English" as the only applicable domain knowledge, I'm not likely to read "split by ',' …" as an instruction to split the string "," by some other separator. To understand how the second string fits in you need more than basic English, because English doesn't have that sort of syntax. However, once you learn that it parses as `(splitBy ",") "1,2,3,4,5"` the result is pretty obvious.

In the OOP language example the second version with the string knowing how to split itself, as it were, does make more sense than the first version. The target of the method is always the subject, and the method name is usually read as a verb, with parameters as direct or indirect objects. (Read as: String "1,2,3,4,5" (S), split yourself (V) by the string "," (I.O.).) However, in functional programming the bias is exactly the opposite, because functional programming is about programming with first-class functions on data, not performing actions. You could easily arrange to write:

    "1,2,3,4,5" `splitBy` ","
in Haskell using infix notation, and it even reads fairly well reinterpreted as English prose. However, defining `splitBy` this way would imply the curried prefix version:

    splitBy ","
would not be a function that splits its input by ",", as one would expect, but rather one that splits "," by its input. Which just goes to show that not every function is well suited for both infix and prefix notation. Whichever version you choose needs to be used consistently. In general the convention has been to define multi-parameter functions such that they can easily be used as combinators, via currying, which seems fitting to me given the nature of functional programming. Functions which are designed to read well as infix operators usually require operator sections or `flip` to adapt them for use as combinators.

> I think, in general, requiring named arguments is a good thing.

I have no objection to optional named parameters, but making them required would necessarily eliminate currying and simple function composition, and that I have a problem with. Record parameters with named fields are generally sufficient for the situations where named parameters are useful, especially since they're ordinary data which you can manipulate at will and not some special syntax baked into the language just for function calls.


> Functions which are designed to read well as infix operators usually require operator sections or `flip` to adapt them for use as combinators.

It occurs to me that this could have been avoided at the language level by defining infix notation to implicitly flip the arguments. For example,

    x / 3 == (/ 3) x == (/) 3 x

    x ++ "suffix" == (++ "suffix") x == (++) "suffix" x

    x `splitBy` "," == (`splitBy` ",") x == splitBy "," x
(The left and center forms are valid and equivalent Haskell code today; the right form has the arguments flipped.) I think on the whole this would have made it easier to transition between the curried forms and the infix forms, since it's more common to want to bind the RHS of an infix operator than the LHS. I would also say that e.g. `(/) 3` reads more naturally as "divided by three" than its actual definition as "three divided by …". No doubt we could find a few counterexamples where it reads better to have the LHS first, though I can't think of any at the moment.

Of course, it's much too late to be changing something so fundamental at this point. Even as an extension it would cause far too much confusion.


Elm single handedly got me into functional programming. Everything from the syntax to the standard library. I haven’t used a language that was that elegant and delightful to use perhaps since I discovered Ruby.

It makes me really sad that Elm turned to this niche language/framework that it is today. I had hoped for it to grow someday to have a mobile target in addition to web, to be able to use it on the server, hell even to be able to use it to build systems. So much wasted potential.


I also love Elm, but the fact that it is a niche language now doesn't mean it's all it ever will be. There's nothing really stopping anyone from adapting Elm to the server for example, but the reason it hasn't been done yet is because it's best to focus on solving one problem at a time. If Evan just translated the Node standard api 1:1 for example, it wouldn't be Elm or Node and there would be no reason to use it.


There's the core team, which is extremely unfriendly to any kind of user-driven development of the language.

The whole reason Elm has been stuck in a niche when it had _huge_ hype around 2015 and everyone was sure it would be the "next big thing" on the front-end is that the developers have tried to keep full control of the language and keep shooting down proposals by users. It's either their way or the highway.


I'm familiar with those controversies, and most if not all of them I would side with the Elm team. The thing is that, yes it is nice to get user-driven development but they seemed to be proposing to bring back concepts from their OO experience and/or baking in features that should not be part of the core library and are easily implemented if you understand how Elm is wired. Seems like these users are excited to contribute but need to get more familiar with FP before contributing to a higher level FP library. I remember one where the proposal could be easily be implemented generically with ports and any other JS library instead of bringing some weird binding logic to A specific JS library. Add the fact that devs from the FP world are concise with their explanations and you have a recipe for miscommunication problems when trying to explain why that idea is DOA to someone not familiar with core FP concepts.


Eh, it isn't just that though. I recently made a big push to try and get a relatively small change through (setting CSS Custom Properties), which is a big blocker for using a lot of web components (which is officially suggested as a way to deal with interop for the language), and will likely block interaction with future web APIs that use custom properties.

I went through and collected up the reasons to do it, distilled it down and gave examples as requested by the core team and got lots of positive feedback from the community, and just got no response from the core devs and nothing happened.

I agree not all changes people propose are good, I like the fact the language is opinionated and doesn't include half-baked ideas, but there is literally no way to get anything through into Elm core, any attempt to do the legwork is just ignored and thrown away. It is a one-man project and that's it, but they pretend that isn't the case.

Honestly, the answer is probably an unstable fork of the language for more experimentation and development, which to be fair, anyone could do¸ but obviously maintaining that would be a lot of work (I don't want to imply that the core Elm team have some responsibility to do it).


That's not really what I'm talking about. For instance, a few years ago when I was really into Elm, I wanted to implement a WebAudio library. Back when Elm was an FRP language, I thought it would be a great fit with the language's model.

I even got a proof of concept working and showed it off in the Elm Slack.

And then I got told my library would not get accepted on the official package repository because the core team was developing their own "correct" abstractions for the main Web APIs and they did not want third-party libraries to compete with the core team's libraries, as regular web developers don't know what they're doing and are incapable of choosing the "right" abstractions, which apparently only the Elm core team is capable of doing.

That experience turned me off Elm for good.

Flash forward to 2020: There is still no "official" Web Audio library. (There are a few non-official ones on Elm Packages)

It's your right to side with the core team, but they're the ones pretty much killing the language's chance to see any real usage.


Yea it's too bad. If the core team was more open and/or communicative I'd use Elm for production. Maybe you've already seen this but there is a pretty solid blog post detailing the state of Elm: https://lukeplant.me.uk/blog/posts/why-im-leaving-elm/


Is there any reason why a group of users didn't fork the language? Lack of coordination?


From what I've seen in HN people are more interested in complaining about how Evan handles the language than trying to do something about it or suggest a better approach. Just makes me trust him more, personally.


On my end, it's mostly lack of time.


Looks beautiful. Love functional languages (Elixir, Scala). Python is my day-to-day language. Gets the job done. But it‘s not my biggest passion to program in it. Too many inconsistencies for my taste.


This is not my first time hearing someone praising Elm's syntax and standard library. How does it compare to OCaml/F#/haskell?


The author lists F# as not IDE friendly. I find this categorization inaccurate.

F# works great with Visual Studio, and especially well with intellisense.

F# was designed as industrial language from the beginning, and high quality tooling is a large part of that.

It might just be me, though, maybe other people use the language in ways that don't work that well there.


They also admit that it is better than average here: https://keli-language.gitbook.io/doc/specification/chapter-1...

> Keli is created to overcome a problem that is almost undeniable in every functional programming languages: the lack of proper IDE incorporation (perhaps except F#). In short, Keli can be viewed as an attempt to assimilate the Smalltalk's syntactic model into a functional programming environment.


The only issue with F# is that it is a bit the black swan of Microsoft languages on .NET, so it doesn't get all the toys that C#, VB.NET and even C++/CLI get to play with.


I don't think F# needs toys, though. The language is so well put together you don't miss much IDE assist anyway, except to point to you where your code will have problems compiling.


Sure it does, a language alone isn't much help if it only gets a tiny slice of the ecosystem.

No .NET Native, GUI designers, WCF/gRPC, EF designers, Blend, WinUI tooling, ASP.NET templates,... hinders adoption at corporate level.


My experience is that, compared to most functional languages, F# has a great IDE experience in Rider, Visual Studio and VS Code. But when compared to big industry focused languages, like C# and Java, tooling is middling at best.

Ironically, despite .NET core being an excellent runtime, it suffers stigma from being assumed to be too tied to Microsoft platforms. This means, depending on your angle, F# lacking specialized ties to the Windows specific tooling you mention need not be considered a disadvantage.


I'm told F# is where the toys come from.


I think you mean black sheep


I second this. F# is quite IDE friendly, and auto completion works great. Additionally much of the .NET tooling (with the exception of C# specific refactoring tools) are CIL / bytecode level and independent of CLR language. This is one of the reasons having a polyglot solution of C# and F# projects is possible and with (mostly) seamless interop - all the same bytecode in the end.


I personally don’t think keli is fixing the right problems: I’m personally an ocaml user, and I don’t see what keli adds to ocaml.

They cite positional parameters (that they call "prefix notation"… i wonder why ?) but ocaml already has support for named parameters (you can write things like split ~on:"," "a, b, c" for example) Moreover, guessing the order of parameters is generally not that hard :

— If you have good intellisense your IDE will tell you which parameter is which (how they are called in doc)

— If you have good intellisense, your ide will tell you the type of function, which, with strict typing is sufficient in a lot of cases (think "send : Unix.socket -> string -> unit", the type tells you the order of arguments)

— with named parameters, you don’t have to look up the order of parameters… but you have to look up their names, same problem

— This last one is pretty subjective, but in a lot of cases you can guess the order of parameters by thinking "on which argument does it make sense to do partial application ?" `splitBy ","` makes sense, `splitBy "a, b, c"` doesn’t really… so "," is the first argument (but that is subjective)

For IDE support, with the right tools, in my experience it is pretty good (ocaml-lsp or merlin do the job pretty well, and strict typing often allow for better intellisense than on non-functional languages)

Though there we arrive at the real problem I’ve experienced : the tooling. It’s not actually that bad. more like it’s badly explained. how do you know that you are supposed to install

— opam for package management (and reinstall ocaml via opam)

— dune for building

— utop as a REPL, because the basic REPL sucks

— batteries or janestreet-core for "standard library" because the standard library sucks (and which one anyway ?)

Plus these tools are hard to use at beginning / have a learning curve. (why can’t creating a project be as simple as - type "cargo new project" -> now you automatically have a project dir with a local opam switch, an opam file, a dune file, an example program, and basic utilities installed in that switch ? )

Personally, I think that’s what fp lacks the most right now (though I’m probably influenced a lot by ocaml) : easy to use and flat-learning curve tooling


This is impressively compréhensive and covers everything I thought of while reading the post. I also agree that OCaml needs a one click to opam+dune+utop+batteries+Merlin+editor experience, but new tools like https://www.gitpod.io/ help since your project can just describe everything declaratively and spin up a working environment. I expect this sort of tooling will help with a lot of languages which are not really integrated by default


There is https://github.com/avsm/opam-tools which aims to simplify the process of setting up a local opam project with all the tooling required.

On the topic of and janestreet-core/base and named parameters, I don't like how it basically forces you to name your arguments whenever you need to compose functions. I much prefer Containers library which is lighter and stdlib friendly.

* Base/Core: List.range 0 20 |> List.map ~f:(fun x -> x * 2) |> (fun l -> List.take l 5)

* Containers: List.range 0 20 |> List.map (fun x -> x * 2) |> List.take 5


For writing code with IDE support, your comments about named vs positional parameters are spot on. But when it comes to reading code, an IDE should not be assumed and there is no problem with looking up well-named parameters.

For something Keli has that Ocaml doesn't: it is interesting to see a statically typed FP with multiple dispatch built in from the beginning.


On the topic of language IDE support. One of the things I've noticed from working in a few languages professionally (Python, Ruby, Java, Elixir) is that the level of power required in an IDE seems to be a function of the language.

My observation was that to feel comfortable in Java I tended to require a very powerful IDE (Intellej) to deal with refactoring and appeasing the type system.

When I write Elixir, I feel comfortable using a much less powerful (from a language integration standpoint) Vim, mostly due to the constraints Elixir has in the language. In Elixir, there is no mutable state, I can feel confident that the only things affecting a function are the things in front of my face when reading it. Elixir's alias/import syntax make it pretty easy to jump to the file that has a function definition in.

I think FP languages tend to have an edge in how much power an IDE is required to have, because those languages tend to have features like immutability and composable higher order functions)


The biggest advantage of an IDE for me, in any language, is the ability to rename/move/refactor things across multiple files in a project correctly. I feel notably less capable in this regard when using Vim versus a "full" IDE, no matter what the context or project.


Yeah, this exact flow was a pain for me in switching from vscode to vim full time.

My flow now is to search for a bunch of instances using the fzf plugin (https://github.com/junegunn/fzf.vim) and open them into the quickfix list, then do something like

    :%s/old/new/gce | :w | :bnext


Is fzf buying you anything over just using :grep? (If speed is the issue, the magic words are `set grepprg='rg --vimgrep'`, after which it's actually usable.)


I've never really explored the native grep functionality, so its possible I'm missing something.

The two features I really like about fzf are

1.) the preview window (visible here https://github.com/junegunn/fzf)

2.) Fuzzy finding/multi-selection/gradual refinement of search terms


Nope, you're not missing anything, then. Maybe I am -- I use the fzf plugin, but only for finding files. Is this (build_quickfix_list) what you're using? https://github.com/junegunn/fzf/blob/master/README-VIM.md#ex...


Having used IntelliJ, the biggest advantage of IDE for me is the speed with which the errors are surfaced allowing me to correct them way more quickly than I could ever do in vim. It is so much more easier to find where things went wrong and how. That lets me focus on the logic instead of wasting time on things like fixing misspellings. Also totally agree with the ease of refactoring - it basically gives you the time and confidence to iterate freely.


This is true, especially when it comes to array languages. When I was learning kdb+/q, I realized that the language was small enough that I didn't need autocomplete. Also, the language is so powerful that I didn't really even type that much. I would have been perfectly happy writing out the code on a paper napkin it was so terse.


My observation is that I tend to like languages that I don't need powerful tools to work with. That seems to be a mark of a well designed language to me


The author lists Ocaml as "non-IDE-friendly." This is not my experience. Ocaml is one of the most IDE friendly languages I have in my toolkit right now -- not just functional languages, but all of them. Ocaml + Emacs + Merlin is stable, accurate, very fast (responsive), and easy to set up and configure.

Plus, named and named-optional parameters are supported in the language, and can be used to good effect for disambiguating function parameters of similar type (the split, join problem).


Compare that to JavaScript where you can literally just install vscode and node and you’re up and running. Not to mention that you could pop open a console in any browser and execute code.

I love OCaml as a language but we have to admit that the developer experience just sucks.

For something to be simple, a 10 year old kid needs to be able to figure it out. That’s how languages get adopted.


Really? I've had much, much worse developer experiences with other languages. With opam and dune wrapping the default toolchain, honestly it's a pleasure to work with these days.

(I feel obliged to give an example of what I think is a bad developer experience, and I would point at Haskell. There are far too many toolchain variations to choose from, all of which seem to be in active use -- cabal, cabal-v2, stack, cabal-v2+nix, stack+nix; ghcid, ghcide, etc. etc. Once your choices are made and your stack is configured, you'll be okay, except for the inordinately long build times. But in my experience, it can be a real nuisance to come back to a half-finished Haskell project -- say, on a new machine -- unless you've made a singular, personal commitment to a specific technology stack. Leave yourself copious notes, Makefiles, and shell.nixes to remember how to get the thing running! But this is a personal sob-story and a digression.)

My main argument -- which I think is being lost here -- is that Ocaml isn't IDE unfriendly, and especially not so unfriendly that an entirely new language is needed just so that FP and IDEs can happily coexist.

(Well, except for Windows. Ocaml + Windows is more unpleasant than it needs to be.)


> My main argument -- which I think is being lost here -- is that Ocaml isn't IDE unfriendly, and especially not so unfriendly that an entirely new language is needed just so that FP and IDEs can happily coexist.

Right, and I guess my counterargument would be that, yes it is, and you don't need to resort to logic to see it - just do a little googling about its poor adoption.


What an odd comment.

>Compare that to JavaScript where you can literally just install vscode and node and you’re up and running

As opposed to OCaml where you... install opam and merlin and you're up and running?

>Not to mention that you could pop open a console in any browser and execute code.

As opposed to OCaml where you can pop open utop and execute code.

>For something to be simple, a 10 year old kid needs to be able to figure it out.

How many 10 year old kids are there working as professional programmers? Come on...


> How many 10 year old kids are there working as professional programmers?

Many of them in 10 years time, and they will be using concepts they learned when they were 10. And FP enthusiasts would complain why no one uses their favourite language.

My point is, if we want to get more people to use FP we have to make it more accessible. Everything from building better tooling, tutorials, environments. Good languages alone don’t matter.

You can’t tell me that it’s just as easy to get started with JavaScript or Python today as it’s to start with OCaml. That’s just delusional.


It would be nice if 10 year olds can be exposed to functional programming. But it's hardly a necessary prerequisite. There are plenty of technologies that people adopt later in life and which are hugely successful (SQL/RDBMS is a great example).

If you want to expose 10-year olds to functional programming, Logo [1] is a great place to start, and has been for decades. That's where I picked it up (I didn't know it at the time, but looking backwards it's now clear).

[1] https://en.wikipedia.org/wiki/Logo_%28programming_language%2...


I actually think it's easier. dune and opam are so much nicer than python/pip/conda or whatever.

Have you used OCaml? Because you can set up a complete environment with IDE support in like 5 minutes flat. It's actually one of the easiest environments to set up of any language. dune, merlin, opam, and utop provide everything you would possibly need.


>Compare that to JavaScript where you can literally just install vscode and node and you’re up and running. Not to mention that you could pop open a console in any browser and execute code.

That's only because VSCode was developed in JavaScript, and JavaScript is used in Browsers anyway. This has nothing to do with Intellisense friendliness.


No, golang is just as easy to install and work with on vscode. Just install Go and the official Go plugin and you have an IDE experience complete with debugging, formatting, and intelligent autocomplete. It has nothing to do with vscode being written in JS.


You didn't mention plugins, it seemed like not having to do anything besides installing the language and VSCode should be required.

In that case there are many functional languages with good IDE support. E.g. F# through Ionide.


Same applies to F#, a ML based language.


Also F# has amazing IDE support with Ionide in VSCode.


Any language is non-IDE-friendly compared to what you have out of the box for languages such as Java, C# and even Python in IntelliJ's IDEs.

Most languages at most have autocomplete and definitions look up (and even those are often very brittle). That is a far cry from what an IDE should be able to provide.

Granted, recently I only tried OCaml via Reason, but I remember that some of the errors the compiler produces are not IDE-friendly either (something like "Type A provided but elsewhere wanted B" with no indication where :) ). But that's mostly due to the age of the language (most "old" languages are very user-hostile and have very terse/cryptic error messages).


I agree that IntelliJ is excellent for Java, and assume it is for C# (though I haven't used it for that). It's my go-to tool for Java development. In my experience, it's good for Python that doesn't have type annotations -- it's remarkably smart really, considering the challenges of autocomplete for dynamic languages -- but not truly great. Better than nothing, certainly!

I can't speak to Ocaml in IDEs other than Emacs, but in my experience, error messages are quite location-precise, down to the specific word(s) on the line causing the error; and the messages are readable with a little practice. They are not as good as, say, Rust's error messages, but you get used to them fairly quickly! But that's more a condemnation of the compiler, not of the IDE.

There are a few esoteric error types in Ocaml ("Expected a value of type 'a, but you gave a value of type 'a" -- OK, that's a head-scratcher) but those are very infrequent in typical code.


Do you have a recent example of an error of the form "Expected a value of type 'a, but you gave a value of type 'a"?

I have hoped to have eliminated all of them in 4.08.


That is great news, and I am probably remembering errors from the olden times. :) Thanks for your work!


Great to see the motivation spelled out clearly, but something seems strange. If you read the Quora answer, it just rants about how functional programming is obsessed with big words and esoteric concepts that are hard to grok, and that this constitutes poor UX. But Keli’s interpretation seems to be that the moment-to-moment experience in the IDE is poor, and that that’s what the language wants to address with more explicit syntax and intellisense support. These seem like two very different problems under the umbrella term “UX”.


I'm very sympathetic to the problem. I'm working on Dark (https://darklang.com) which is a functional language, explicitly because we believe that functional languages are easier to learn, understand and provide great tooling for.

It seems to me there's a lot of focus on syntax, and I don't think they've gotten to a great place. For example:

  (this Int).square | Int = this.*(this)
In Rust, they do

  impl Int {
    fn square (self) -> Int {
      self * self
    }
  }
I can read the Keli version, but I'm note sure that `(this Int)`, `|`, and `.*` are great inventions. You need a good reason to deviate from commonly recognized syntax, and Keli doesn't feel like it's made things simpler with this.

In Dark, we use piping heavily to get the benefits of the dot-syntax. In particular, it can be type directed:

  8 |> Int::square |> Int::log 2
The autocomplete will only show you integer functions in this case.

For the problem of "which parameter" - our IDE shows parameter names when your cursor is in a position to type the next parameter, so you know what you're adding it is. We also show the parameter names when your cursor is in the completed argument so you know what parameter it is. (I couldn't see if Keli did this, it might not be possible in modern editors).


I find the syntax as the author intended it to be: clean. I agree with the ambiguity of positional parameters at first glance. Although named parameters (e.g. in Scala) somewhat solve this, they are optional. Having the parameters with their explicit name makes it quite readable.

  myList
      .append(5)
      .reverse
      .put(5) atIndex(0)
      .++(anotherList)
      .select(x | x.isEven)
Edit: The same feature makes it look a little weird when put on a single line, because we mentally parse the whitespace as separator but in this case it is not:

  $.name("Keli") age(50).isOld
It looks like a refreshing addition to function programming languages and I'd like to see where it goes from here, in terms of adoption and features.


$.name("Keli" :age 50).isOld

wold've been nicer to me but ah well.


I can understand $.(:name "Keli" :age 50) but not $.name("Keli" :age 50), why should the age bit go inside the parens? This introduces an asymmetry where the first key appears outside parents and subsequent keys appear inside (and as colon-initialed keywords?!)


I agree, having age(50) separate almost looks like a separate function call.


Unfortunately, no direct comparison to Lisps.

The two downsides mentioned regarding FP are not an issue if you are using a REPL and a Lisp:

- excellent IDE support, since you are working inside your program (which can be inspected)

- paradigm a la carte: you want named arguments? Go for it (split :string "foo,bar" :by ",")

  you want different invocation syntax? (3 + 4) or (3 4 +) is just a macro away
and a bit easier to read than: (this Int).square | Int = this.*(this) YMMV

  you want to define your data shape? use your favorite schema language

  you want monads? just use a library

  you want go-routines? just use a library

  you want compile time types? just use a library


Except everything is brittle leaky abstraction.


The following example is kinda funny:

    // This is obviously not too right
    ",".splitBy("1,2,3,4,5")

    // This should be right, because it reads out more naturally
    "1,2,3,4,5".splitBy(",")
Seeing as Python uses the first version for .join()


Exactly what I came here to write about:

  Python 3.8.2 (default, Jul 16 2020, 14:00:26) 
  >>> " ".join(["a", "b"])
  'a b'
vs Ruby

  2.6.5 :001 > ["a", "b"].join(" ")
  => "a b" 
I don't know which one is more natural but I prefer the Ruby version because it's consistent with

  "a b".split(" ")
which works in both languages. One less think to remember.


In Python:

["a", "b"].join(" ")

This would mean the type list has a method join, how would it work with the following ?

[1.5, "hello", None].join(" ")


Principle of least surprise: make it work like

  >>> f"{1.5} {'hello'} {None}"
  '1.5 hello None'
Ruby does that. From [1] "[Array#join] Returns a string created by converting each element of the array to a string, separated by the given separator."

The difference is that nil (Ruby's None) disappears

  2.6.5 :001 > [1.5, "hello", nil].join(" ") 
  => "1.5 hello " 
That's totally expected because in Ruby the conversion of nil into a string is the empty string. Python converts it into the string "None".

[1] https://ruby-doc.org/core-2.6.5/Array.html#method-i-join

Edit: an example with a type that normally wouldn't meaningfully cast to string

  2.6.5 :001 > class Example
  2.6.5 :002?>   attr_accessor :name
  2.6.5 :003?> end
   => nil 
  2.6.5 :004 > e = Example.new
   => #<Example:0x000055a321fa2bb0> 
  2.6.5 :005 > e.name = "example"
   => "example" 
  2.6.5 :006 > e.to_s
   => "#<Example:0x000055a321fa2bb0>" 
  2.6.5 :007 > [1, e].join(" ")
   => "1 #<Example:0x000055a321fa2bb0>" 
  2.6.5 :008 > class Example
  2.6.5 :009?>   attr_accessor :name
  2.6.5 :010?>   def to_s
  2.6.5 :011?>     "#{name}"
  2.6.5 :012?>   end
  2.6.5 :013?> end
   => :to_s 
  2.6.5 :014 > e = Example.new
   => #<Example:0x000055a321f8a4c0> 
  2.6.5 :015 > e.name = "example"
   => "example" 
  2.6.5 :016 > e.to_s
   => "example" 
  2.6.5 :017 > [1, e].join(" ")
   => "1 example"


  >>> " ".join([1.5, "hello", None])
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  TypeError: sequence item 0: expected string, float found
Why would `[1.5, "hello", None].join(" ")` be any different?


Because "list of string" is not a type in Python.

Why would you put a method on a class if only a small subset of it can use it?


Either way it's a partial function that is not defined for most of its domain. The only difference is how its arguments (including self) get arranged at call sites, right?


That happens all the time, since Python is dynamically typed. For example a[10] gives an IndexError if a has less than 11 elements. So with static typing, we should restrict indexing to ensure that the number of elements needed are present, and remove indexing from general lists. But you wouldn't remove indexing from lists, that's one of the main functions...


the key difference is that in ruby, `join` is implemented in the `Enumerable` mixin (which provides a whole suite of functionality to any object with an `each` method). if your own class wants to support `join`, it has to both implement `each` and explicitly mix in `Enumerable`.

in python, `join` is implemented in the `string` class, and the argument is an iterable. therefore, if your class wants to support `join`, it needs to implement the `__iter__` method (python's equivalent of ruby's `each`), but it does not need to also mix in an implementation of `join`.

it's mostly a cultural difference between ruby and python - the ruby ecosystem leans more heavily towards mixins and implementing generic functionality by attaching methods to objects, whereas the python ecosystem leans more heavily towards implementing generic functionality by providing functions (or methods on an external class) that accept generic objects.


Actually join is a method of Array https://ruby-doc.org/core-2.6.5/Array.html

A class that wants to support join has to implement to_s. The definition of join is

> join(separator=$,) → str

> Returns a string created by converting each element of the array to a string, separated by the given separator. If the separator is nil, it uses current $,. If both the separator and $, are nil, it uses an empty string.

If the class doesn't implement to_s Object.to_s kicks in and displays something like "#<SomeClass:0x000055a321fa2bb0>"

Enumerable is really a mixin https://ruby-doc.org/core-2.6.5/Enumerable.html but join is not listed in its methods.

There is another reply of mine with an example of a class can be used in join and doesn't mix in Enumerable.


oops, right you are, it's not defined on Enumerable (though i do wonder why! `each` does define an iteration order after all)


If I were designing a functional language, I'd be thinking about whether data.splitBy or delimiter.split was useful even when not immediately invoked.

Consider this crude csv parser:

using splitBy:

    csv.splitBy("\n").map(csv_row=>csv_row.splitBy(","))
using split:

    "\n".split(csv).map(",".split)
split was the clear winner here. Given splitBy, I basically recreated split as a lambda.

I tried to create an analog to that where splitBy would come out looking better. I figured that if we didn't know the dimensionality of our data, then delimiters becomes an array of arbitrary length and we could pass data.!splitBy into something like delimiters.reduce. When actually writing that, however, I wound up recreating split again:

using splitBy:

    delimiters.reduce((accum,delim)=>accum.deepMap(data=>data.splitBy(delim)),[data])
using split:

    delimiters.reduce((accum,delim)=>accum.deepMap(delim.split),[data])


Yeah Python (and I think JavaScript?) pretty clearly have `join` backwards.


JS uses the second example


It is because in Javascript everything can be translated to a string:

1 + "hello" == "1hello"

Therefore, the type Array can have a join method that have predictable results (unlike Python).


I don't know what that has to do with `join`'s call order.

As mentioned above, it's because Python supports `join` on any sequence, not just the array class. Personally, I find JavaScript's solution neater:

    [..."hello"].join(" ") === "h e l l o"


Because when the Javascript engine will try to concatenate all the strings in the Array (the expected behavior of join), it will first translate every object in the array to a string.

Javascript always falls back to string, that is also why we have === and == operators.

Python's type system is a bit more strict because it will not attempt to serialize your objet if you haven't explicitly done it yourself.

My point is: Python doesn't use this notation because it would not make sense in Python.


This is also the cause of this lovely bit of JS behaviour:

    >>> [1,2,10].sort()
    Array(3) [ 1, 10, 2 ]


I'm not a fan of this:

'Hello world' replaceFromIndex: 0 toIndex: 4 with: 'Bye'

At first sight you might think, oh great don't even need to read the documentation (which is the author argument in favour of this). But, is "toIndex" inclusive or exclusive? Don't know, so now I need to read the documentation anyways and just learn the behavior of the function. And once I did, all those names just become visual noise that my eyes need to skip over when reading and verbosity when writing.

Also, if you're going to go with this style, I'd much rather have function first (as FP should):

replace in: 'Hello world' fromIndex: 0 toIndex: 4 with: 'Bye'

Which brings me to my second point, I prefer languages that just allow both. Some functions are better positional and other named and some a mix of both. Let each function choose the most appropriate one. For example, are we really going to do this?

100 divide with: 5

or

divide theNumber: 100 with: 5

Ya, it reads like an explanation for a 5 year old, but as a professional programmer I'd much rather:

divide 100 5

My point being, certain functions are intuitive even with positional args and others arn't, only the latter should have named args.

Now for the intellisense section I agree, but I think you can solve it with IDE UX. Like just provide a command that lists all locals for you to pick and then lists all functions whose first argument is of that type. Or heck, have it that after you type a var name, it lists functions over it but the list shows to the left and when you pick it autocompletes the function text to the left of it.


I really appreciate the effort of designing the syntax to match common IDE expectations. However, I think it shouldn't be too difficult to add intellisense for Haskell-like syntax to IDEs as well. Especially with all the types available!

The only unconventional thing is that the function might be put in-front of the value and some parentheses need to be added, instead of just putting it after the cursor like with method syntax. I assume you get used to that rather quickly.


> I think it shouldn't be too difficult to add intellisense for Haskell-like syntax to IDEs as well. Especially with all the types available!

On the contrary, this should give Haskell an advantage because all the types are known and the IDE should be able to autocomplete only the correct parameters. But that requires compiler-as-a-service, and GHC is not. And writing your own analyser for a language is quite a task.


> But that requires compiler-as-a-service, and GHC is not. And writing your own analyser for a language is quite a task.

Yep. Haskell-language-server is very nice, though there are some performance issues I've experienced with a template Haskell heavy large codebase.

For many cases though, the experiences is very smooth after install.

Install issues are close to being sorted out with static binaries being downloaded in the vscode extension for instance.


Isn’t that why language server protocol is a thing?


Yup, kudos to MS for coming up with it and standardizing it.

(the following is just what I think, I have no hard knowledge on the matter)

Language server still limits what an IDE can do. E.g., IDEA can do complex large scale refactoring and analysis over a codebase. If you're brave enough, you can write your own language analyzer that converts your language into structures IDEA understands, and you can get (some) of the same benefits. But it's a lot of work.

IIRC, language server can only provide rather superficial type information and autocompletion (which is still way more than many languages had before).


Like Leksah.


I feel like the named reasons, aside from being invalid (it should be really really easy to make a popup that tells you the function parameter names and/or types, like SLIME has been doing for...decades? with Common Lisp), are really bad justifications for creating an entirely new programming language. Surely the effort would be better spent on improving the tooling, because all of their concerns are tooling-related.

That also makes the language not about a "good user experience" as given in the docs, but "being compatible with modern IDEs". No thanks, I would rather improve tooling for an existing language that already has a community and ecosystem.


Anyone else irked by the use of "Intellisense" - a Microsoft brand - when "autocomplete" will do?


Code completion (autocomplete) is a subset of intellisense. Intellisense also provides e.g. info tooltips on hover and more.

But yes, I agree since the author specifically talks about the way OO languages provide a more easily autocompleted syntax and parameter help doesn't really differ, perhaps the better term to use here would be autocomplete or code completion.


Seems like they're complaining (for lack of a better word) about only having word autocomplete available for most functional languages versus a more refined suggestion tool like Intellisense. But yeah, they don't get that specific about that fact. I guess Intellisense has entered the realm of Kleenex in how it's used.


Mostly in .NET communities though, from what I've seen.


[flagged]


These are referred to as "generic trademarks" if you're interested in learning more: https://en.wikipedia.org/wiki/Generic_trademark. Generic trademarks are studied in economics and have legal implications.


I'm not sure those are the things holding FP back. The statement "The user experiences of functional programming languages sucks." sounds really weird and is the exact opposite of my experience. I write Elixir professionally. The developer experience is unparalleled and I definitely wouldn't trade it for a job writing Java 9 to 5.

Maybe it's trying to refer to stuffs such as existing libraries and package management etc. when you want to quickly boot up a real-world project. I once tried to write some web app in Haskell and that experience definitely was anything but smooth. Fortunately Elixir has a really vibrant community and libraries for the majority of common tasks (plus 20+ years of Erlang ecosystem). It's also really easy to roll your own solutions.

I don't think the issues of IDE support and parameter ordering mentioned in this article are problems to FP programmers at all. Powerful language servers exist nowadays for all major functional languages and they're not that different from language servers for OO languages. So yeah I tend to agree with a lot of commentators that this article doesn't seem to make much sense unfortunately.


Haskell Language Server (https://github.com/haskell/haskell-language-server) offers a pretty good IDE experience.

It also mitigates the parameter naming problem. With a type hole, the language server will infer the type. But in the case of two parameters having the same type, you have to consult the definition or doc.


I disagree with all points in the article - the user experience with existing FP languages is superior, and the IDE integration for strongly typed languages is generally very impressive.

Syntactic window dressing isn’t what makes or breaks a language.


While I could get onboard with functional languages being more user friendly I didn't find the motivation to be compelling.

> Ambiguous functions argument positions

It just seems to be advocating for named arguments, something which the majority of languages (OO or FP) support these days. You could say that requiring named parameters is a feature but it's hard to get excited about.

> Not Intellisense friendly

I use Elm and Elixir, both of which have decent autocomplete in vscode (and I believe many other editors).

Critique aside I definitely applaud more towards increasing FP adoption. I think Elm has brought many good ideas to the table already (though admittedly it's bias is more towards ideal than practical). On the flip side Elixir is very pragmatic and easy to get going with for developers coming from OO languages.


Elixir is very good about function positions, too, by being consistent in it's stdlib, and by pinning structs to modules. Almost all library writers adhere to the convention.


People are also consistent because of the pipe operator which was there since the very beginning. So everyone expects functions to work with chaining correctly. That's why you get params that get changed a lot as the first parameter etc.


Why is no one talking much about Scala? (https://docs.scala-lang.org/overviews/scala-book/functional-...)


Scala is unfortunately famous for having a toxic community as well. I've emailed with most of the "famous open source" Scala folks and they've all been really nice. But the subreddit seems to have some nasty folks, especially compared with other communities I've been involved with. It's a shame. It's a great language and the people producing the popular libraries are nice.


Because we're trying to turn people on to functional programming, not off

EDIT: Downvotes already? Look, Scala is NOT noob-friendly, and it's a rational argument, not a preference. Here's an example: There are at least 10 (TEN) different uses of the underscore character (_) in Scala. JFC. I can go on, such as the proliferation of bizarre operators everywhere that are impossible to Google (again, not noob-friendly), the reliance on JVM (ewww... so you end up having to be a Scala expert AND a Java expert... plus deal with JVM windup time and JVM stacktraces... Not noob-friendly), the consistent violation of POLS, etc. etc.


When is the last time you used it? I've been a full time scala developer for over 4 years now spanning versions 2.11, 2.12, and now heading into 2.13. There have been massive improvements across consistency and huge convergences happening across the scala FP community in terms of library usage.

> the reliance on JVM (ewww... so you end up having to be a Scala expert AND a Java expert... plus deal with JVM windup time and JVM stacktraces... Not noob-friendly)

This is just plain weird. This makes me feel like you spent zero time actually working with the JVM and it's incredible ecosystem. Being fully inter-operable with Java is a huge win for both adoption and usability. Not only that but the JVM is one of the most mature runtimes in existence with massive amounts of documentation and help available.


> and it's a rational argument, not a preference

It might have some justification, but it's just your preference. Scala gained a reputation of being a "difficult" language early on, I suppose mostly by people coming from Java, and it has unfortunately stuck.

Singling out underscores in Scala as "difficult" is bizarre. There might be many, but in practice it's very easy to understand what you want, and I've never seen anyone seriously confused by them. What do you mean, "many bizarre operators"? Are you using ScalaZ maybe?

What do you mean, JVM stacktraces are not newbie-friendly? They are a useful tool to troubleshoot many problems. What problems do they have in your opinion?

What's wrong with relying on the JVM?

Scala type signatures are sometimes hard to understand in library code. You are not expected to write code like that unless you are writing general purpose libraries, which you are likely not doing.


> What's wrong with relying on the JVM?

It's that the user needs to learn not just Scala but also Java whenever they want to do anything "real". You might say this is a minor hangup, but for learners having to context switch back and forth between Java and Scala and dealing with interop issues can make for much more cognitive load than using a non-hosted language like Python or Go.


That Scala has a vast array of Java libraries is a strength, not a weakness. Interop issues between Java and Scala are not a big deal, anyway. It's mostly seamless.


Nothing’s noob-friendly if you begin with advanced features.

Scala is eminently beginner friendly when introduced to developers familiar with OOP. This approach was how I first experienced Scala and it ended up becoming a gateway drug into FP proper.

Regarding underscores in Scala, yes there are many technical terms for each thing it has the capability of representing... but in reality, you just use it wherever you want to ignore a value with a placeholder or pass a provided value through.


I learned Scala a few years ago and have some popular open source Scala libs. I like Scala, but wouldn't call it "eminently beginner friendly". Coming from Ruby, it was hard for me to learn SBT, Maven, Scalatest (much more complicated than Gemfiles, rubygems, and rspec in my opinion). With Ruby, you can easily make an Array.

Scala newbies find it hard to even perform basic Array operations. You need to dig into Array, Seq, and List, figure out the differences, try to see what's accepted by the community, etc.


The dominant OO+FP paradigm of Scala code is one of the most powerful, imo. Combining immutable objects with FP constructs is my favorite way of programming and something that only Scala can really do (well OCaml can, but no one uses objects).

Scala is probably the most flexible and powerful statically typed language currently in widespread use and I would probably choose the language for most non-scientific computing greenfield projects.

In my experience of on boarding developers who didn't know Scala or Java into a large Scala project, it's really not that hard to learn. Developers were writing okay code in the first week and completely idiomatic code after the first couple weeks.

The JVM does suffer from non-transparent performance characteristics, but the highly optimized and tested platform makes up for it.

Scala the language is getting better and better and Scala 3 will really be something special, I think.

The worst part of the JVM is dependency management. Upgrading the version of Scala you're using is unbelievably painful and in a lot of cases impossible.

My old Scala shop is still running on 2.10 because no ones bothered to put in the weeks of work to upgrade.


Honestly some of the reasons I stay away from learning Scala are the same one that keep me from learning C++


I've almost misread it as "Keil", which brought bad recollections of their IDE C/C++ IDE.


Another way to solve the problem the author is looking to fix is by convention:

    splitStringBy "1,2,3,4,5" "," 
     ^ action
          ^ Arg1
               ^ Arg2


I am going to call bullshit on this:

1) Function argument order is not an issue in practice. Especially Elixir or OCaml providing keyword arguments. Also, there are mostly conventions eg search term comes first in replace function.

Elixir, F# all provide Pipes, while little more verbose than dot notation, more universal than methods on objects.

2) IDE support: above points apply, although pervasive type inference may make few things hard. It is best practice to annotate function signatures.

The real reasons why FP languages are unpopular are:

1. immutability is often a bad abstraction and some FP languages often make performance/memory reasoning harder[0].

2. Your 9-5 engineer doesn't want to learn anything more than what they learned in school or bootcamp.

3. The impression created by some FP language communities. There are some people always talking some complicated category theory stuff that's rarely useful for programming, or propagating zero information statement like "If it compiles it works". [1]

[0] "bUt ItS lIKe gRaDuAtE aBsTrAcT mAThEmAtICs it must be good and elegant even if the machine doesn't work like this". Go and learn some real computer science, algorithms, data structures, number theory, probability, instead of touting your definitions of undergraduate logic and set theory.

[1] There is more chance it works, sure. But "If it compiles, it works" is dishonest statements.


Yeah I agree that those two points don't make much sense whatsoever. Though I also don't agree with your point 1. I don't know if you have had much experience writing a functional language professionally. The benefit of immutability is exactly to make reasoning about your program much easier. Of course as you mentioned computers are indeed based on the von Neumann model, so in the end we sometimes have to go into memory usage details. However, unless you're programming some high-performance game, the vast majority of programs run totally fine with mostly immutable data structures. It's also especially suited for distributed computing (e.g. actor model) which is becoming the norm nowadays. In 95% of programming activities, the traditional "real computer science (what is "fake" computer science?), algorithms, data structures, number theory, probability" are used surprisingly rarely, while the elegance of functional/mathematical reasoning really makes the programming experience much more enjoyable and bug-free.


> In 95% of programming activities, the traditional "real computer science (what is "fake" computer science?), algorithms, data structures, number theory, probability" are used surprisingly rarely

Yeah, unfortunately, the cheap webshits use reverse loops because they read in some medium article that comparison to zero is shorted, while repeatedly concatenating strings in an inner loop.

Doesn't help with things like - indexing on utf-8 strings is O(n) and often the compiler/JIT is not able to figure out all necessary loop fusions for you.

Basic algorithms, data structure knowledge is a must for every programmer. I don't say they should know how to prove a treap is 0.yz times more efficient than a skiplist. Now the next part.

> while the elegance of functional/mathematical reasoning ...

There is natural reasoning and there is category theory stuff that only serves to signal the dude has studied some aBsTrAcT mAtHeMaTiCs in programming communities.

A best example of this is Haskell. They bastatdized quicksort to show an "elegant example" on their website.

Another example is react. "React is fast omg virtual DOM". Not everyone will have latest macbook pro like hipster webshits.

I know, and I acknowledge the improvements in craft of ~~CRUD WEBSHIT~~ programming that came from FP camps. But there is also lot of bullshit there in FP camps.


Immutability makes updating deeply nested data structures more difficult, since you have to update every level with the updated copy of one of its children.


There was some discussion on Reddit over a year ago that was interesting too: https://www.reddit.com/r/ProgrammingLanguages/comments/acrj9...


Can someone point out how Keli implements purity in the documentation? Haskell requires the IO monad to accomplish this, not sure what Keli does. Its feature list claims to implement "pure functional programming" but it seems as though it's just functional, like OCaml or F#.


On the AS400 there's a 'command' object type that deals with both issues the Keli language is trying to address.

- Argument positions are named, help text, validity checking, special values, etc are defined for each argument.

- Command arguments can be prompted within the editor (intellisense).

As400 commands can also be prompted and executed by a user, thus providing a common user interface for running jobs as well.

I've seen nothing like this in the PC world. The two issues the Keli language is trying to solve were addressed 30 years ago on the as400.


I like the idea but I think the tactics are wrong. The better approach would try to make the experience as much like OOP as possible. Like the named argument, message passing thing is nice, but it's going to be just one more thing that the 95% is going to have to get used to. If the goal is approachability, then have to get it as close to existing experiences as possible.


On the post it says Haskell is not IDE friendly. It is really hard to make an IDE around Haskell or does it just that it lacks support?


It's just a matter of time investment imo. And with the current ghcide effort, that's already happening!

The argument that you need method-style syntax to have good IDE support is short-sighted. Haskell has something more powerful (typed holes), so a common thought is Haskell needs a different sort of IDE support and not expect it to feel the same as Java.


>However, OOP languages also suffers the same problem whenever the number of function/method arguments get more than two, for example:

Well yes except Smalltalk, the original OOP-language. Smalltalk requires you to use KEYWORD METHOD-NAMES whenever you have > 2 arguments. That makes Smalltalk code highly readable.

(I told you so 20 years ago today) :-)


With every new upcoming language, I always have the same ask; Please add a couple of section on what can we do with the language today.

Rust has amazing sites like arewegameyet or arewewebyet.

A single paragraph around what users can realistically do with this language and what are the most used packages in the ecosystem will be a great help to anyone curious about it.


Wow, those sites are a really good idea. I wish tech was more honest with it's pro's and con's. Seems like most project maintainers want to be marketers than engineers sometimes...


If great IDE support and ux for developers is the goal wouldn't it be more reasonable to spend the time on IDE support on existing FP languages rather then creating a new one? It seems that Hindley-Milner type system of many existing languages should lend it self well for language servers in general. But what do i know


Firstly, kudos to the authors of Keli. Since the primer to the language begins by examining what is preventing FP adoption, I'd like to add another key reason: performance.

Haskell code can be optimised to run very fast indeed, but resulting optimised code does not look much like idiomatic Haskell at all.


> Haskell code can be optimised to run very fast indeed, but resulting optimised code does not look much like idiomatic Haskell at all.

It's still Haskell though, which means it can compose nicely with all that idiomatic Haskell!

Further I'd argue that you typically don't have to go to that level of optimisation.

I'd be very confident in my above statement replacing node or Python REST apis at least, but can imagine that not holding true in other domains.

In that case though, my first point still holds and I find that advantage very powerful.


Elixir already solved that problem tho :)


We've built our SAAS on Keli for the last six years and have been amazed by the productivity. Each year we are able to make a net reduction in lines of code. We are now down to only 2 lines, and we expect next year to be the most impressive yet for what we will need to maintain.


Any chance you have a write up about your experience with it??


I'm pretty sure this is a joke. There's no way you can run a SaaS on two lines of code... Unless it's some bizzarely simple calculator or something like that.


Of course it's a joke.

Sibling comments aside (without comment on my part), it would be hard to have 6 years of production use of a language that has its first git commit 2 years ago.


APL programmer bursts through the window

"ACTUALLY! ..."


I agree it's probably a joke, but I'm not so sure that the core idea is actually impossible... I could reasonably see a SaaS that simply provides a quick and dirty key-val store. The code is just a translation from REST to REDIS which could easily be done in 2 lines, the value-add is the hosting.

That'd actually be pretty helpful for initial prototyping, where you want to have state shared across devices but don't want to bother with hosting a backend at first, and localhost wont work because the client wants to play with it too.


Do not underestimate oneliners


Of course this is a joke.


This seems like a dead project. It's fairly unknown and hasn't been updated in over a year.


Maybe they broke up?


No no we haven’t broke up yet


Yea it is indeed halted, because I’m still in search of a better syntax.


Noun verb. Haystack needle.


Wadler's Law at work


reads out more naturally

  set the delimiter to comma
  split 1,2,3,4,5
or

  split 1,2,3,4,5 by comma


I got excited at the idea of smalltalk syntax in functional clothing but it never seemed to deliver?


It won’t be delivered because it’s actually my final year project for my software engineering degree, and plus I’m also not convinced by this language anymore, I still think that there’s a better syntax which I can’t think of right now


Keep going!


Is this being developed? How similar this is to ada ?


Hasn't been touched in over a year. https://github.com/KeliLanguage


Maybe they broke up.


Nope because I’m not convinced by this language anymore


The F# IDE is really good actually


another terrible programming language website ...


i do not agree with the implief premise that functional programming is not inherently the act of joy itself


Off serious topic: Naming it after you girlfriend is a risky move, like getting a tattoo of her. Better get married and stay that way or the next partner is gonna be like: "wtf", you still working on your tribute to your ex programming language!?


This isn't too bad. We live in Git society now.

After the breakup, fork, rename to the new girlfriend name, and put a notice on README.md:

"I broke up with Keli but don't worry, I am with Sandra now. Please follow the new language <a href="https://sandralang.org">here</a>"


Relationships are just like new technologies. Sick of all all the hassle you have to deal with regarding <existing technology>? Use a new, shiny technology!

Five years later...

Sick of all the hassle you have to deal with regarding <formerly new technology>...?

The lesson here is ultimately all technologies suck, but you have to find the variety of suck you're willing to live with.


> The lesson here is ultimately all technologies suck, but you have to find the variety of suck you're willing to live with.

Alternative view is that they all have their uses and if you choose the right one for the use case, it makes your life easier, not harder, especially once you get to know it well.

It helps if you know what do you want. Not a single one is good at everything.


Shop shooting holes through lingering post-adolescent cynicism!


I don't know what the problem is. After that you only date people who have the same name as your programming languages.

"Did something happen?"

"Sorry. I just had the wrong idea about you, Kaley."


But it is too late now.

Her: "Why did you change the name?"

Him: "Because I'm hedging my bets in case our relationship does not outlast my pet programming project".

Her: "..."


Breaking changes after every breakup


debian says hello


there's also, ALIX[0] more commonly known as HURD

[0] https://biblioweb.sindominio.net/telematica/open-sources-htm...


New girlfriend could change her name if she is really devoted


[flagged]


And you can emulate most of C++ in C with macros. Doesn't mean you should.


false equivalence, there is no "emulation" of features they are native to the language


If I want to do functional programming, I can do that just fine in most languages.

If I want to do imperative programming, I can do that just fine in most languages, except for functional programming languages.


I think the key with a functional language is given a large codebase, you can reason about it functionally instead of hoping there's no imperative code in there (on purpose or by bug)


Kind of like if you're wearing a straitjacket, you don't need to worry about accidentally punching yourself in the face.




Join us for AI Startup School this June 16-17 in San Francisco!

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

Search: