4 ms·
I find both mental models useful. For example, "accumulate all of the monoids in this collection" (https://hackage.haskell.org/package/base-4.9.0.0/docs/Data-F
by chadaustin 10y ago
I find both mental models useful. For example, "accumulate all of the monoids in this collection" (https://hackage.haskell.org/package/base-4.9.0.0/docs/Data-Foldable.html#v:msum https://hackage.haskell.org/package/base-4.9.0.0/docs/Data-F...) makes total sense as a functional operation. You have a collection and some pure operations you want to apply to combine it all into one thing.
But then, sometimes you just want to iterate across a list of directories, read some files, and put some results in lists. Tail-recursion here feels dumb. Like, dammit, I'm the human and you're the computer, I know you can turn a sequence of instructions into a tail recursive loop for me, so don't make me think.
Haskell, due to its effectful / monadic vs. pure syntax distinction isn't terribly great here. Ideally I'd write all of my code in an "imperative style" all the time, but a row-typed effect inference system would determine which functions were pure, or in IO, or async, or in ST, or threw exceptions, etc. Koka [1] is an interesting language that explores some of these ideas.
[1] http://research.microsoft.com/en-us/projects/koka/ http://research.microsoft.com/en-us/projects/koka/
- johncolanduoni 10y agoSome other projects that are doing cool things in this space are Idris[1] and F-Star[2] (the latter is also a Microsoft Research project). I am particularly impressed by Idris's algebraic effect approach, since it gives you a general effect system that combines the ability to easily extend that monad transformer approaches provide with the ease of use and inference of F*'s effect system. I think the theory behind algebraic effects is a key step in making it easy to develop code with more stringent and customized safety properties than is available in languages like Rust. [1]: http://www.idris-lang.org/ http://www.idris-lang.org/ [2]: https://www.fstar-lang.org/ https://www.fstar-lang.org/
- ayberkt 10y agoThat is definitely right. For things involving the machine, doing things declaratively is wrong. Also, thanks for telling about Koka. It sounds very interesting!
- greenrd 10y agoI'm not sure I agree. Functional Reactive Programming is a declarative way of doing user interaction, but it's still a computer science research topic (Elm used to claim to be FRP, but has now decided to move away from its old allegedly FRP-like style). I need to play around with the latest FRP research libraries some time! In addition there is also machines/pipes/conduits, which all mix imperative I/O with declarative style code in an interesting and new way.
- ayberkt 10y agoFRP is awesome and is an example of how something previously thought of as an inherently imperative problem can be much easily done declaratively; we completely agree on this! What I was talking about is: almost all lower-level abstractions are designed with the view of computer as a state machine. If we are solving a problem at an abstraction low enough to make us deal with this analogy, it is easier not to have a declarative layer in between. machines/pipes/conduits are all very interesting and I was going to mention them too, but I still don't think they solve this problem in the way OP was referring to.
- tome 10y ago> Ideally I'd write all of my code in an "imperative style" all the time, but a row-typed effect inference system would determine which functions were pure, or in IO, or async, or in ST, or threw exceptions Yes, this would be a really nice approach (and it's not one that Haskell lovers would be upset by, BTW).
- lmm 10y agoI would be upset if it meant no visible distinction between effectful and non-effectful calls. When refactoring it's really important to know when the order of calls is significant vs when it's incidental.
- daxfohl 10y agoAgreed, I just recently took some recursive F# code from a photobooth (display countdown, async sleep, take photo, download from camera, recur with new byte[] list)->reverse list, and rewrote it it an imperative loop appending to a normal List<byte[]> and the code is much more intuitive. Some things are more intuitive as a list of things to do and changes to make.
- lgas 10y agoAren't IO Actions just a list of things to do and changes to make? I'm asking genuinely to try to understand the difference.
- daxfohl 10y agoYes they are, but I think "for loops" and "mutable lists" a'la Java's ArrayList and Vector and C#'s List<T> don't exist even in IO (I'm not a Haskell expert). That kind of thing has to be done with recursion. They exist in F# but are not idiomatic. However sometimes they're more intuitive (to me at least).
- acomar 10y agoforM is a for each loop over anything iterable (Traversable in Haskell) that will execute a given monadic action on each element in the collection. A more traditional for loop is this over a range of integers or whatever else you feel like. forM (range 5) println Mutable collections exist in various forms depending on what kind of mutability you want. For example, there's transactional mutability that gives you mutable data structures inside an STM but forbids IO. And there's IO mutability that removes all the bounds but restricts you to the IO Monad. TVars, MVars, and the various kinds of Refs are what you're looking for here.
- jbooth 10y agoIf someone is asking for mutability for reasons of performance and clarity, giving them STM is like a kick in the teeth.
- Zpalmtree 10y agoI find writing recursive code is often very short and easier to read and show intent than the same code written non recursively.
- greenrd 10y agoAdditionally, in languages with good, convenient support for functional programming like Haskell and Scala, I find that I tend to write lots of short functions, often making use of higher-order functions to decompose code into really simple, and potentially reusable, pieces. And like you say, this really helps to show intent. The same sort of thing, in fact, that object-oriented programmers try to do with design patterns such as the Strategy pattern - but with fewer lines of code.