Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Haskell's syntax isn't anything to imitate. It's hostile to IDE features, at least.

Can you elaborate on this? I haven't used Haskell in years but I recall enjoying some of its really cool IDE-friendly features, such as typed holes and the ability to press a key and have whatever is under my editor's cursor show me its type and another key to insert that inferred type into the document.

I have heard that more recent versions support nice record syntax using the '.' operator (but without whitespace, to differentiate it from composition). That's also very IDE friendly since type inference in the editor should be able to resolve the members of a data type that's been defined with record syntax.



The main problem is discovery.

When I hold some object I can easily discover the things I can do with that object—just by typing "." after the object reference.

In something like Haskell I need to know upfront what I may do with some "object". The IDE can't help me discover the methods I need. All it can do is to show me all available functions in scope. Most of this functions aren't what I'm looking for. But the IDE can't know that as it's missing the "context" in which I want to call my functions / methods.

And regarding records in Haskell: First of all you will get beaten when you use them (that's part of the religious movement and has nothing to do with comprehensible reasons). The other part is: Records are no substitute for objects. Not even close. Those are just quite primitive structs. (To learn how unpractical is it to try to build objects from structs without proper language support just ask someone who was forced to use C after coming from a more complete OO language).


> In something like Haskell I need to know upfront what I may do with some "object". The IDE can't help me discover the methods I need. All it can do is to show me all available functions in scope.

Sorry, but this just isn't true. Hoogle <https://hoogle.haskell.org/> searches function by type, fuzzily: ask for functions whose first parameter is the type of the object-like thing, and you'll get just what you're looking for. And it's perfectly possible to run hoogle locally and integrate it with your editor.

Now, the tooling for a language like Java have had several centuries more of aggregate development work done on them compared to Haskell's tools, and if that polish is a difference-maker for you, that's fine! But it's not a fundamental limitation, and claiming it is is just fud.


The main point is that the noun-first syntax of OO languages means that by just starting to write the code that you know you need, you've already given the IDE enough information to give you a list of options for which function to use. That kind of tight integration directly into the editor is hard with Haskell because you have to write out the function name first.

I could imagine an editor having some sort of "holes" capability, where I can hit a key combo to insert a hole where a function should go, provide the function arguments, then double back and fill in the hole with search results from Hoogle. Done right, such a system would be marginally harder to use than Java-style IDE completions, but not enough to be a problem. The main difficulty is that the implementation and UX complexity of such a system is far greater than with a noun-first syntax.


The approach you're describing sounds a lot like proof-search in idris and agda (coq too, I thing), and it's nicer than you think: you're very often working with definition-stubs created by automated case-splitting, which adds holes for you. Jumping around between holes becomes how you program, not an irritating interruption.

But even aside from that, I don't think verb-first needs to be a showstopper: given that we've got type signatures, there's a lot of local context to work with. You're probably calling a function on one or more of the parameters, or else you're typing the leftmost-piece of a composition chain that gives you your result type. So throw Param1Type -> a, b -> ResultType etc at hoogle and populate a completion list. Completion doesn't have to be perfect to be useful. The hard part would be performance: if completion isn't fast, what's the point?


I've seen this point before, but something I've never seen acknowledged is the (incorrect) presumption that it's _possible_ to have an exhaustive list of all the verbs you might want to use with a given noun.

The point of data-oriented (functional) programming is that we believe it's fundamentally unknowable what sorts of verbs a noun should support, and that it's a mistake to try to enumerate them (OOP).

I don't deny that it's very convenient to type . and get IDE autocomplete. And we should absolutely have better solutions than we do for writing functional code in IDEs. But it's a misunderstanding of your problem domain to suppose that a noun's verbs are inherently enumerable.


> But it's a misunderstanding of your problem domain to suppose that a noun's verbs are inherently enumerable.

This presumes to know what my problem domain is, and for many domains I can think of it's simply not accurate.

If my problem domain is traffic signal control, Signal has exactly one verb: transition.

If my problem domain is calendar appointment scheduling, Appointment has easily enumerable verbs: create, reschedule, delete, move. Maybe you could throw a few more in there, but there certainly aren't infinite things you can do with a calendar appointment.

If my problem domain is running a forum, Post has a few easily enumerable verbs: create, delete, edit, reply-to. As with Appointment, you could conceive of more, but quantifiably so.

I'll grant you that if my domain is something abstract like list processing, quantifying the things someone might want to do with a list becomes very difficult. But in the world of business software, it's really not.


> This presumes to know what my problem domain is, and for many domains I can think of it's simply not accurate.

> If my problem domain is traffic signal control, Signal has exactly one verb: transition.

Interesting, so you never define new "compound verbs", such as "transition and then transition again"?


There is no such presumption that it's possible to have an exhaustive list of all the "verbs" you might want to use with a given "noun".

In a C++ / Java / C# like language classes are open by default (even this creates some issues). The reasoning behind that is exactly the one that it's impossible to know all "verbs" upfront.

On the other hand it's a fundamental concept in software engineering to define interfaces. Haskell calls them "type classes". (And Haskell's type-classes are coherent; which by the way creates the mirror problem to open classes).

> I don't deny that it's very convenient to type . and get IDE autocomplete. And we should absolutely have better solutions than we do for writing functional code in IDEs.

I see here the exact same misunderstanding as in the article we're discussing: Whether code is functional or not is not defined by mere syntax. You can use "method syntax" and write perfectly fine functional code.

More modern FP languages even embrace this. The `|>` syntax is quite popular by now. That's a syntax that's actually not so far away from the C++ token `->` for (virtual) method calls.

It's also not only the IDE issue. English, and a lot of western languages at least, use S-P-O as primary word order. Method call syntax mimics that. Whereas P-O sentences actually denote an imperative in English. "Verb noun" syntax is therefore, quite obviously, a tradition form imperative programming: `print("sentence")`, `process(data)`, `do something`… If it were written in declarative form it would look more like `"sentence".printed`, `data.processed`, `something.done()`, I guess.

Anyhow, FP and OOP is on the fundamental level even the exact same thing. ;-)

http://www.javiercasas.com/articles/codata-in-action (You should read the paper, too!)

But there's clearly a difference in practice.

The main point about FP is imho not data as such (and data oriented design is anyway an orthogonal topic) but the primary ideas are immutability of data and, of course, the usage of (mostly) pure functions. This yields the greatest benefits, makes code more robust and simpler to reason about. Syntax isn't relevant to this.


> That kind of tight integration directly into the editor is hard with Haskell because you have to write out the function name first.

Sort of. But you can just write

    _ x
       ^
and get the same sort of benefit as you get from

    x.
      ^


> And it's perfectly possible to run hoogle locally and integrate it with your editor.

Interesting.

Could you point to some demo of such IDE integration? Never seen it.

I'm still struggling to imagine how something like "IntelliSense" would look like in such a system. Do you have e.g. any demo video you could point to? Curious to learn more!


If I made it sound like there's something like IntelliSense today, apologies! We've got <https://github.com/haskell/haskell-mode/blob/master/haskell-...>, but it's type-a-command-and-do-a-search: it's not linked in with completion directly in the setups I've seen.

(In practice, I'm usually starting from a slightly different place: I know I want a Frob and I've got a This and a That, so I do :hoogle This -> That -> Frob and get some options. The thought-process is working backwards from the goal more than forwards from one key object in focus. A different way of working, but I'm not convinced it's less effective.)

My point though was that it's an engineering issue, not a fundamental language limitation. ie not a reason all future languages should shun haskell features. The building blocks to do better at completion than haskell curently does are there.


to quote grugbrain.dev:

>grug very like type systems make programming easier. for grug, type systems most value when grug hit dot on keyboard and list of things grug can do pop up magic. this 90% of value of type system or more to grug

>big brain type system shaman often say type correctness main point type system, but grug note some big brain type system shaman not often ship code. grug suppose code never shipped is correct, in some sense, but not really what grug mean when say correct

>grug say tool magic pop up of what can do and complete of code major most benefit of type system, correctness also good but not so nearly so much


Haha, I love this.

I wrote up something similar [0] but not quite as creative. Discoverability and Locus of Control are two of the best reasons to use OOP that rarely comes up in the academic discussion of OOP as a practice.

Honestly, OOP is many cases is simply more practical.

Even imperative code has its merits; case in point: it's often much easier to debug.

[0] https://medium.com/@chrlschn/weve-been-teaching-object-orien...


> https://grugbrain.dev/

How could I miss that?

Gold! Thanks!


>but grug must to grug be true, and "no" is magic grug word. Hard say at first, especially if you nice grug and don't like disappoint people (many such grugs!) but easier over time even though shiney rock pile not as high as might otherwise be

>is ok: how many shiney rock grug really need anyway?

... I actually needed to see this today


Great, but anybody who says correctness is the point of types has completely failed to understand what types are for.

Good types do work for you, so that you don't need to write the code to do that work. Correctness is a side effect, because being wrong would take more work.


> The other part is: Records are no substitute for objects. Not even close. Those are just quite primitive structs. (To learn how unpractical is it to try to build objects from structs without proper language support just ask someone who was forced to use C after coming from a more complete OO language).

I agree with you about C and Haskell's records, but I'm curious if you'd say the same about Rust's struct + impl system? Are there problems that can only be solved with real, Smalltalk-inspired objects, or can lightweight structs do the job if supported by enough syntactic sugar?


The answer lies in the answer to why Rust added `dyn traits`…

Indeed Rust striked some kind of sweet spot because it got the defaults right.

Syntactic it mimics mostly the OOP approach, but without the conceptional burdens behind it.

Still Rust could not do without dynamic dispatch (which is the—wrong—default in OOP).

In the (admittedly) seldom cases where you need dynamic dispatch & runtime polymorphism you just need them.

You could build this stuff by hand (and people did in the past in languages without this features built-in) but having dedicated language level support for that makes it much more bearable.

Structs + the impl system is static dispatch. This just can't replace dynamic dispatch. If it could nobody would have ever invented v-tables…


Your first point is also true for procedural programming languages, but at least in Haskell you know that if something is, say, a functor then you know what sort of things you can do with that value. I think it should be possible for an IDE to suggest functions if you used the reverse application operator:

  [1, 2, 3] & 
              ^-- suggestion here
Unfortunately this wouldn't be very idiomatic in Haskell, maybe in Idris with |> this would make more sense?


> if something is, say, a functor then you know what sort of things you can do with that value

Sure. Because you memorized all the functor methods and remembered that there is a functor instance defined for the data type at hand… ;-)

> Unfortunately this wouldn't be very idiomatic in Haskell, maybe in Idris with |> this would make more sense?

Or you could just admit that the "OOP style syntax" is superior. (I'm not saying that OOP is superior, though)


With Haskell, you learn to discover in a different way. Just think about the shape of the function you’d want, then type that into Hoogle. I learned Rust before Haskell, but prefer Hoogle to Rust’s searching via dot chaining.


> Just think about the shape of the function you’d want, then type that into Hoogle.

Do you really think this is a adequate replacement for proper IDE support?


All you work with is method calls? No free functions? No library functions?

I perform operations on stuff, if I don't know the name of the operation I intend to perform I clearly have some reading to do. Integrated look up for one specific class of operation which isn't even omnipresent in Class-oriented languages isn't a killer feature for me. Nor is structuring every module even if it's completely stateless as a class, just to benefit from IDE driven development a reasonable price to pay.


> Nor is structuring every module even if it's completely stateless as a class, just to benefit from IDE driven development a reasonable price to pay.

That's a question of language.

  object SomeModule:
     def someMethod(someRandomNumber: 42) =
        s"The answer is: $someRandomNumber."
     
     def someOtherMethod = someMethod(42)


  @main def entryPoint =
     
     val someTheory = SomeModule.someMethod(23)
     
     import SomeModule.someOtherMethod
     import StringExtension.*
     
     (someOtherMethod :: "No!" :: someTheory :: Nil)
        .fold("")(_ + _ + "\n")
        .printLine()


  object StringExtension:
     extension (s: String) def printLine() = println(s)
     // Because methods compose better in a fluent style…
     // You don't need to read your code backwards!

https://scastie.scala-lang.org/Zt6xTucJRyK1IEQQgFNRLQ

That's Scala.

Two cute stateless modules. Nicely wrapped in singletons.

Not quite sure why I had to add the handy `printLine()` method to the String class myself, after the fact…

But as extending arbitrary classes with new methods without touching them is trivial, who cares.

(Frankly it does not compile. It says something like `Found: (23 : Int); Required: (42 : Int)`. No clue)




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

Search: