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

Haskell also changed the way I think about programming. But I wonder if it would have as much of an impact on someone coming from a language like Rust or even modern C++ which has adopted many of haskell’s features?


True. I often think of Rust as a best-of compilation of Haskell and C++ (although I read somewhere that OCaml had a greater influence on it, but I don’t know that language well enough)

In real life, I find that Haskell suffers from trying too hard to use the most general concept that‘s applicable (no pun intended). Haskell programs happily use “Either Err Val” and “Left x” where other languages would use the more expressive but less general “Result Err Val” and “Error x”. Also, I don’t want to mentally parse nested liftM2s or learn the 5th effect system ;-)


>I read somewhere that OCaml had a greater influence on it

Whoever wrote that is wrong.


If we could wave a magic wand and remove Haskell's influence on Rust, Rust would still exist in some kind of partial form. If we waved the same wand and removed OCaml's influence, Rust would no longer exist at all.

You are the one who is wrong, I'm afraid.


Which OCaml features exist in Rust but not Haskell? The trait system looks very similar to Haskell typeclasses, but I'm not aware of any novel OCaml influence on the language.


> Which OCaml features exist in Rust but not Haskell?

Rust's most important feature! The bootstrapped implementation.


I'm not convinced the implementation language of the compiler counts as a feature of the Rust language. If the argument is that Rust wouldn't have been invented without the original author wanting a 'systems OCaml' then fine. But it's possible Rust would still look similar to how it does now in a counterfactual world where the original inspiration was Haskell rather than OCaml, but removing the Haskell influence from Rust as it is now would result in something quite different.


Rust isn't just a language, though.

Additionally, unlike some languages that are formally specified before turning to implementation, Rust has subscribed to design-by-implementation. The implementation is the language.


That just means the semantics of the language are defined by whatever the default implementation does. It's a big stretch to conclude that means Rust 'was' OCaml in some sense when the compiler was written with it. Especially now the Rust compiler is written in Rust itself.


You're overthinking again. Read what is said, not what you want it to say in some fairytale land.



The original rust compiler was written in OCaml. That's not evidence it "had an influence", but it's highly striking considering how many other languages Greydon could've used.


Yes: if a person knows nothing else about Rust and the languages that might have influenced it, then the fact that the original Rust compiler was written in OCaml should make that person conclude tentatively that OCaml was the language that influenced the design of Rust the most.

I'm not one to hold that one shouldn't form tentative conclusions until one "has all the fact". Also, I'm not one to hold that readers should trust the opinion of an internet comment writer they know nothing about. I could write a long explanation to support my opinion, but I'm probably not going to.


It's like trying to say Elixir wasn't influenced the most by Erlang


Was any Elixir interpreter or compiler every written in Erlang?

If not, what is the relevance of your comment?


Elixir’s implementation still has significant parts written in Erlang. I don’t know if it’s a majority but it’s a lot. e.g.: https://github.com/elixir-lang/elixir/blob/aef7e4eab521dfba9...


> SML, OCaml: algebraic data types, pattern matching, type inference, semicolon statement separation

> Haskell (GHC): typeclasses, type families

https://doc.rust-lang.org/reference/influences.html


Haskell has algebraic data types, pattern matching and type inference, too, and has had them since Haskell first appeared in 1990.

Although SML is older (1983), OCaml is younger than Haskell.



I think it does, actually. Python also has many of Haskell's features (list comprehensions, map/filter/reduce, itertools, functools, etc.). But I only started reaching for those features after learning about them in Haskell.

In Python, it's very easy to just write out a for-loops to do these things, and you don't necessarily go looking for alternative ways to do these things unless you know the functional equivalents already. But in Haskell you're forced to do things this way since there is no for-loop available. But after learning that way of thinking, the result is then more compact code with arguably less risk of bugs.


If anything, Python encourages you to use loops because the backwards arrangement of the arguments to map and filter makes it painful to chain them.


    map(function, iterable)
That seems very logical to me, but then, I’m not a functional programmer, I just like map. It’s elegant, compact, and isn’t hard to understand. Not that list comps are hard to understand either, but they can sometimes get overly verbose.

filter has also lost ground in favor of list comps, partially because Guido hates FP [0], and probably due to that, there has been a lot of effort towards optimizing list comps over the years, and they’re now generally faster than filter (or map, sometimes).

[0]: https://www.artima.com/weblogs/viewpost.jsp?thread=98196


Yes, but how do you chain them?

    map(func4, map(func3, map(func2, map(func1, iter))))
vs

    iter.map(f1).map(f2).map(f3).map(f4)
I made up the syntax for the last one, but most functional languages have a nice syntax for it. Here's F#:

    iter |> f1 |> f2 |> f3 |> f4
Or plain shell:

    command | f1 | f2 | f3 | f4


You don't.

Use generator syntax, which is really the more pythonic way to it.

    >>> iter = [1,2,3,4]
    >>> f1 = lambda x: x*2
    >>> f2 = lambda x: x+4
    >>> f3 = lambda x: x*1.25
    >>> [f3(f2(f1(x))) for x in iter]
    [7.5, 10.0, 12.5, 15.0]


First off, writing f3(f2(f1(x))) is painful - keeping track of parentheses. If you want to insert a function in the middle of the chain you have some bookkeeping to do.

Second, that's all good and well if all you want to do is map. But what if you need combinations of map and filter as well? You're suddenly dealing with nested comprehensions, which few people like.

In F#, it'll still be:

    iter |> f1 |> f2 |> f3 |> f4
Here's an example from real code I wrote:

    graph
    |> Map.filter isSubSetFunc
    |> Map.filter doesNotContainFunc
    |> Map.values
    |> Set.ofSeq
This would not be fun to write in List Comprehensions, but you could manage (only two list comprehensions). Now here's other code:

    graph
    |> removeTerminalExerciseNodes
    |> Map.filter isEmpty
    |> Map.keys
    |> Seq.map LookUpFunc
    |> Seq.map RemoveTrivialNodes
    |> Seq.sortBy GetLength
    |> Seq.rev
    |> Seq.toList
BTW, some of the named functions above are defined with their own chain of maps and filters.


An alternative for python is to flip what you're iterating over at the outermost level. It's certainly not as clean as F# but neither is it as bad as the original example if there's a lot of functions:

  iter = [1,2,3,4]
  f1 = lambda x: x*2
  f2 = lambda x: x+4
  f3 = lambda x: x*1.25
  
  result = iter
  for f in [f1, f2, f3]:
    result = [f(v) for v in result]
Then the list comprehension can be moved up to mimic more closely what you're doing with F#, allowing for operations other than "map":

   result = iter
   for f in [
     lambda a: [f1(v) for v in a],
     lambda a: [f2(v) for v in a],
     lambda a: [f3(v) for v in a],
   ]:
     result = f(result)
And a step further if you don't like the "result" reassignment:

  from functools import reduce
  result = reduce(lambda step, f: f(step), [
    lambda a: [f1(v) for v in a],
    lambda a: [f2(v) for v in a],
    lambda a: [f3(v) for v in a],
  ], iter)


Fair, but how would it look if you had some filters and reduces thrown in the middle?

In my F# file of 300 lines[1], I do this chaining over 20 times in various functions. Would you really want to write the Python code your way every time, or wouldn't you prefer a simpler syntax? People generally don't do it your way often because it has a higher mental burden than it does with the simple syntax in F# and other languages. I don't do it 20 times because of an obsession, but because it's natural.

[1] Line count is seriously inflated due to my habit of chaining across multiple lines as in my example above.


My example was just a way to do it with plain python and nothing special. There are libraries that use operator overloading to get more F#-style syntax.

For example: https://ryi.medium.com/flexible-piping-in-python-with-pipey-...

And another mentioned there: https://pypi.org/project/pipe/


I think we can just let this rest. These kinds of operations are not as ergonomic in python. That's pretty clear. No example provided is even remotely close to the simplicity of the F# example. Acquiesce.


You do realize this was in my original comment, right?

> It's certainly not as clean as F# but neither is it as bad as the original example if there's a lot of functions


The fact is the language just works against you in this area if you have to jump through hoops to approximate a feature other languages just have. And I don't even mean extra syntax like F#'s pipe operators (although I do love them). Just swapping the arguments so you could chain the calls would look a lot better, if a little LISPy. It really is that bad.


Generators require a __next__ method, yield statement, or generator comprehension. What you've got is lambdas and a list comprehension. Rewriting using generators would look something like:

    items = [1,2,3,4]
    gen1 = (x*2 for x in items)
    gen2 = (x+4 for x in gen1)
    gen3 = (x*1.25 for x in gen2)
    result = list(gen3)
It's nicer in a way, certainly closer to the pipe syntax the commenter your replying to is looking for, but kind of janky to have to name all the intermediate steps.


You're not using generator syntax anywhere in that example.


f3(f2(f1

This is still backwards?


Don't Haskell and Python use the same argument order?

    filter(lambda x: x<5, map(lambda x: 2*x, [1,2,3,4,5]))

    filter (<5) . map (*2) $ [1,2,3,4,5]
(Technically the Python version should be cast to a list to have identical behavior.)

Same with comprehensions (although nesting comprehensions will always get weird).

    [x for x in [2*x for x in [1,2,3,4,5]] if x<5]

    [x | x <- [2*x | x <- [1,2,3,4,5]], x<5]


Heck, even coming from Python (2) it felt very underwhelming and hugely oversold. (Edit: To be fair, I'd done a bit of Ocaml years earlier so algebraic data types weren't some huge revelation).

Laziness is mostly an anti-pattern.


Check out Swift, too!


Why would I use swift when more cross-platform solutions exist?




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

Search: