From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=unavailable autolearn_force=no version=3.4.4 Path: eternal-september.org!reader01.eternal-september.org!reader02.eternal-september.org!news.eternal-september.org!mx02.eternal-september.org!.POSTED!not-for-mail From: Paul Rubin Newsgroups: comp.lang.ada Subject: Re: Haskell, anyone? Date: Mon, 16 Nov 2015 15:23:16 -0800 Organization: A noiseless patient Spider Message-ID: <87si45msuz.fsf@nightsong.com> References: <14533506-4289-4148-b8c4-e970f5778b26@googlegroups.com> Mime-Version: 1.0 Content-Type: text/plain Injection-Info: mx02.eternal-september.org; posting-host="811568160cd9f4c22b2423e1fab39dcc"; logging-data="16390"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX18cj6WHw2ou7VcgZxs9tDoH" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/24.3 (gnu/linux) Cancel-Lock: sha1:K/HhpKmMhUZemWdPjhuXhgoi4k8= sha1:M/u8vQ9nJ809hD0bB7ZmvXGL6P8= Xref: news.eternal-september.org comp.lang.ada:28412 Date: 2015-11-16T15:23:16-08:00 List-Id: Hadrien Grasland writes: > *** Rant warning, all FP fans please close your eyes for a second *** I peeked ;). The post is full of errors and misconceptions that might clear up with more experience. > Functional programming is based on the core tenet that mutable state > is evil and should be obliterated. Ermph, that's an overstatement, I'd say that the tenet is to separate effects from denotational values. State mutation (like i/o) is considered an effect so FP tries to use it sparingly. But Ocaml has reference cells whose contents you can write to in ordinary expressions, and Haskell has cells you can access with i/o-like actions, plus ways to thread state through purely functional code. > in practice, all functional programmings always make some compromises > in their war against mutable state. We would say in Haskell that the Haskell program computes a pure value called an i/o action (e.g. an action that prints something) and the runtime then executes the action. > "For all i such that 2 < i < N - 1, B[i] = A[i - 1] + A[i] + A[i + 1]" > - A copy of B with its first element set > - A copy of B with its first and second element set... No of course you wouldn't do anything like that. An idiomatic Haskell two-liner if A is a list would be b = zipWith2 add3 a (tail a) (tail (tail a)) where add3 x y z = x+y+z or you could write something similar using recursion. For immutable arrays, you'd use toList and fromList in combination with something like that. There are also mutable array types which would let you write an imperative-looking loop. > "The compiler and the user are both clever enough". It's true that there are FP idioms you have to get used to if you're coming from imperative programming. > "For all elements E of A, display E" > In a functional programming program, loops are considered evil, Typcally you'd write something like "mapM_ display a" which is Haskell for running the display action over all the elements of a. You don't even need that intermediate variable E cluttering your code. > "let f : List a -> List = > .....if a is empty then > .........return empty list > .....else > .........display first element of a > .........return f(rest of a)" > Notice that this example is long, much more difficult to understand, That doesn't look like Ocaml to me, but I don't know Ocaml that well. The Haskell implementation if you didn't want to use mapM_ would be: f :: [a] -> IO () f [] = return () f (x:xs) = display x >> f xs which is probably shorter than comparable imperative code. Note that the type annotation (first line) is not required, since the compiler can figure it out if you omit it. > imagine how a typical functional programmer feels when he has to debug > this : > http://benchmarksgame.alioth.debian.org/u64q/program.php?test=pidigits&lang=ghc&id=4 I'd say that code has been golfed a bit (since one of the benchmark metrics is how short the code is) and also has a few speed optimizations that clutter things a little, but it is fairly straightforward Haskell, and the main obstacle to debugging is probably understanding the algorithm. I'd guess that the sub-expressions were written and tested separately before being combined to that big function. That's not so safe in imperative coding since the side effects of the different sub-expressions could interfere when you combine them. But it's less of a problem in FP code that has no side effects. The Ada equivalent is here: http://benchmarksgame.alioth.debian.org/u64q/program.php?test=pidigits&lang=gnat&id=2 It's 5x-10x as much code, depending on how you count. It's a pretty safe bet that the Haskell version took less time to code and debug. > What about the claim that functional programs are always correct ? That's silly, there's no such claim. There's a justified claim that FP makes it easy to avoid certain classes of bugs that happen all the time in imperative programs, but that's much weaker. You can also use FP techniques in imperative programming to avoid some of those bugs, and I do that all the time these days. That's why I think it's worth learning some FP even if you real work uses imperative languages. > But there is a price to pay for type inference. And to understand > which price, we have to look no further than the most successful > implementation of type inference worldwide, C++ templates. > Whenever someone passes the wrong parameter to a C++ template library, > that person is inundated by a wall of compiler diagnostics, Oh that's way overblown, those awful diagnostics are because C++ templates are basically glorified macros and C++ does a lot of automatic type coercions (like adding int to float gives float), bloating the number of combinations of type assignments in a big expression. GHC's error messages can be messy and hard to understand, but it's nothing like C++ template errors. GHC messages are often "error at line 145, blah blah blah blah [long-winded traceback]" where the blah blah blah isn't that informative, but the line number is usually accurate, so you just look at line 145 and figure out what is wrong (that takes some experience, I admit). Haskell has no automatic conversions (int plus float is a type error), which simplifies things too. > The reason why this is the case is that because type > inference is used througout the whole code hierarchy, compilers can > only detect errors at the very bottom of it. It's not exactly like that, it's a unification algorithm so it's like discovering that a crossword puzzle has no solution. I've been told that ML/Ocaml programs are still ok relying on inference since ML's type system was historically simpler than Haskell's. Haskell programmers generally will write annotations (polymorphic ones when appropriate) on top-level functions, while usually letting the compiler infer types for the stuff inside the functions. > C++ people are now shifting away from it, adding some extra layers of > type check to templates in a future version of the standard with > "concepts". That idea (bounded polymorphism aka type classes) has been in Haskell since forever, and the idea is to make the types more precise, not particularly to clean up error messages. > even though many FP languages support type annotations, I have yet to > encounter real-world functional programs which take the time to use > them properly. Just what real-world functional programs have you looked at? Again I know that Ocaml programs tend to use less of them than Haskell programs but that's because they're of apparently less benefit in Ocaml. > The great thing about FP, though, is that the resulting program will > be much easier to test, precisely because it has little to no mutable > state. But I'm not sure if all the pain you have to go through in > order to reach this point is worth it. I've generally found it easier to code stuff in Haskell than in low-level imperative languages, because of the higher abstraction, shorter code, and larger amount of runtime services integrated with the language. The main cost is that the Haskell code runs slower and uses more memory, but it still suffices for lots of things. > For example, my running average example couldn't be implemented with > the list comprehension syntax above, because it is a gather operation, averages = [x+y+z | x:y:z:_ <- tails a] works for me. Maybe that's nicer than the zipWith2 version I gave earlier. > So, don't only read my somewhat negative opinion, nor that of the > Haskell zealots who will try to handwave away all the problems of the > functional programming model. The problem isn't that your opinion is negative, it's that it's uninformed. There are some well-informed critiques of Haskell around including Bob Harper's (he's a designer of SML and rails against Haskell all the time) and Ben Lippmeier's (he designed a language called Disciple that tries to address what he sees as Haskell shortcomings, that is quite interesting). But I think you've only taken a superficial look at the topic and come away with some strong opinions that aren't validly grounded in facts.