7 ms·
Kind of off topic, but I am curious: is this one of the advantages of functional programming - the notion that you can prove or disprove certain things about th
by fragsworth 15y ago
Kind of off topic, but I am curious: is this one of the advantages of functional programming - the notion that you can prove or disprove certain things about the code and therefore optimize the compiler, based on such proofs, to your heart's content?
- perlgeek 15y agoTo a big part the ability to optimize stems form a static and very detailed type system. In Haskell you happen to know about statelessness from the types, which is an additional bonus. But other statically type languages like Eiffel are also pretty well optimizable.
- bhickey 15y agoCan you elaborate on your question? I don't think what you're saying is intrinsic to FP. There are static analysis tools for non-functional languages. At the end of the day, everything is getting turned into an abstract syntax tree, so style should be irrelevant. Do you mean pure (side-effect free) functional languages?
- fragsworth 15y agoYes, I was thinking of something like Haskell.
- Rusky 15y agoIt's more an advantage of very-specifically designed languages like Haskell, some of which happen to be functional because part of the reason they can be specified in so much detail is because of the lack of side effects.
- jowiar 15y agoThe relevant element of FP is statelessness. That said, statelessness could easily be considered a leaky abstraction because, at some point, the machine you are working on has state. Your machine has CPU registers and memory and such, so the entire "statelessness" of FP is subject to the underlying system-level implementation.
- Peaker 15y ago> The relevant element of FP is statelessness. That said, statelessness could easily be considered a leaky abstraction because, at some point, the machine you are working on has state. Your machine has CPU registers and memory and such, so the entire "statelessness" of FP is subject to the underlying system-level implementation. It is only leaky if you are exposed to the statefulness of the underlying machine. Haskell's pure code does enjoy the abstraction of purity without exposing the non-pure primitives from which it is exposed. Haskell's non-pure code is marked as such, and not supposed to be abstracted away from the nature of the underlying machine. I'm not sure what abstraction leak you're referring to. Can you give an example?
- comex 15y agoDebug.Trace, for instance. More generally, the fact that execution time and memory usage (tail recursion!) sometimes depend on how well the compiler can turn your stateless algorithm into a stateful one, in ways that don't make sense from the perspective of the abstract machine.
- Peaker 15y agoDebug.Trace explicitly breaks referential transparency and its entire purpose is to penetrate the evaluation order out of the abstraction. It's a tool to penetrate the abstraction, much like a debugger. But the existence of such debugging tools does not mean the abstractions themselves are leaky, because these tools do not leak the abstraction as a side-effect, it is their only purpose. Performance analysis of pure algorithms is not a leak of the abstraction, if you consider the abstraction relates to the semantics, the result of the algorithm, and not how much resources it takes to execute. If you wish to know the resource use of your pure code, then none of the nice abstractions hold anymore, but most of the time, you don't need to.
- jrockway 15y agoYour machine has CPU registers and memory and such, so the entire "statelessness" of FP is subject to the underlying system-level implementation. In the end, it's all a few billion transistors that need to be switched on and off at just the right time. C is as far away from that as Haskell is.
- Peaker 15y agoThe more advanced of a type system you have, the more you can optimize indeed. A nice example of this is the zip function. In Haskell: zip :: [a] -> [b] -> [(a, b)] zip [] _ = [] zip _ [] = [] zip (x:xs) (y:ys) = (x,y) : zip xs ys Note how each iteration needs to check both lists for emptiness. In a more advanced type system (or using more advanced type hackery in Haskell) you can have length-indexed lists. That is: lists that have 2 type parameters or "indexes" instead of 1. The 1 is usually the type of element inside the list, the extra 1 is a natural number indicating the length of the list. So the function zip becomes something like: zip :: List N a -> List N b -> List N (a, b) zip [] [] = [] zip (x:xs) (y:ys) = (x,y) : zip xs ys The compiler checks the code and verifies, at compile-time, that both the input lists and the resulting list are all of the same length. This also means that you only need one runtime emptiness check instead of 2. If the N is concretely known at compile-time, you may need 0 (though that case might be caught by inlining/loop unrolling optimizations anyway).
- cwzwarich 15y agoIn an unsafe language like C or C++, you would just assert that the two containers are the same length and use parallel iterators. The type system here lets you optimize away some of the overhead of using a safe language, but it doesn't let you write code that is more optimized than the obvious unsafe code, at least in this example. Also, in this example you're using a singly linked list, which is usually a performance loss compared to a more appropriate data structure. CPUs are designed to favor contiguous arrays.
- Peaker 15y ago> In an unsafe language like C or C++, you would just assert that the two containers are the same length and use parallel iterators. The type system here lets you optimize away some of the overhead of using a safe language, but it doesn't let you write code that is more optimized than the obvious unsafe code, at least in this example. Well, you can choose any two of: 1. Fast (less run-time), 2. Safe (cannot crash), 3. Simple types (None of the advanced type hackery). Many choose 1+3 or 2+3, but advanced types let you choose 1+2 which in many cases is remarkable and somewhat surprising that it is even possible. Also note that runtime checks, in many cases, do not give you safety. For example, normally, you have an array indexing operation like: readArray :: Array a -> Int -> IO a Ignore the "IO" if you are unfamiliar with Haskell's IO type. This is unsafe whether or not you have index bounds checking. A runtime check will only convert one kind of error (corruption/segfault) to another (runtime exception). In these (common cases), your only option of getting safety is advanced type hackery. Something like: index :: (size:N) -> Array size a -> Fin size -> a where Fin N is the type of integer ranged between 0 and (N-1). > Also, in this example you're using a singly linked list, which is usually a performance loss compared to a more appropriate data structure. CPUs are designed to favor contiguous arrays Nothing about this type hackery is specific to singly linked lists, and advanced type hackary is applicable for pretty much any data structure. Also note that the singly linked lists used in zip may not actually be represented by pointer-chasing singly-linked lists. They may be "fused" together into efficient loops that process the input directly.