16 ms·
Haskell Fan Site
- bitmadness 7y agoUsed to code quite a bit in Haskell, but I've moved on. I believe in the philosophy "Simple things should be easy, hard things should be possible"; Haskell forgets the first part.
- Barrin92 7y agothe post focusses on the practicality of haskell and addresses purity but leaves the biggest problem out: laziness, which is more or less the reason haskell exists, is the wrong default. In particular lazy I/O which can introduce horrible bugs or straight up mess with execution order of critical code, which in my opinion is an absolute no-go in an industrial language.
- theemathas 7y agoLazyness in Haskell can never change I/O (maybe except performance) unless you use unsafePerformIO, right? Also, there's a link to a page about laziness on the left.
- kreetx 7y agoI guess it's explained elsewhere here aswell, but the "lazy IO" discussed is pretty much a bug: you read a file to String, but this String is loaded from the lazily while you consume it. But the problem is: you already get the String thinking all is well, but if something happens to the file handle you get a runtime error. This shouldn't happen with pure values.
- nothrabannosir 7y ago> lazy I/O which can introduce horrible bugs or straight up mess with execution order of critical code I've never seriously programmed Haskell, so honest question: I understand the first point, but how is the second possible? Isn't the point of passing RealWorld around that you enforce the order of execution through data dependencies rather than expression order? It always seemed like a very elegant (and incredibly impractical :) ) solution to me.
- almostdeadguy 7y agoMonadic IO allows you to reliably sequence IO operations, but the results of those operations are not produced until their results are forced. Here's an example of the problems with this: http://dev.stephendiehl.com/hask/#lazy-io http://dev.stephendiehl.com/hask/#lazy-io . It's not really a language flaw, but more of a design flaw of the functions in base. Almost nobody uses lazy IO anymore though. Most IO is done w/ strict IO functions from the bytestring library and often with stream processing libraries like conduit or pipes.
- dllthomas 7y ago> Almost nobody uses lazy IO ... in anything large. If I'm writing something small of the form "read stuff from stdin", lazily consuming the results of getContents is a perfectly fine way of shipping data through the program. As soon as it starts getting complicated, though, it becomes important to refactor (which is often super easy in Haskell). I actually treat unsafePerformIO pretty similar, though with more of a requirement that the project be expected to stay tiny. Worth noting that it's much more important you understand Haskell's evaluation model in that case. ... assuming what you're doing matters. If you're just having fun, it can be a good way to learn a little more about Haskell's evaluation model.
- tsuraan 7y agoOP is somewhat conflating two different things: non-strict function evaluation and lazy IO. With lazy IO, you can get, for example, a String from a file. That string is actually a lazily-constructed chain of Cons cells, so if you're into following linked lists and processing files one char at a time, then it's fun to use. The dangerous bit comes in when you close the file after evaluating its contents as a string: fd <- open "/some/path" s <- readContentsLazy fd close fd pure $ processString s Now, processString is getting a string with the file's contents, right? Nope, you have a cons cell that probably contains the first character of the file, and maybe even a few more up to the first page that got read from disk, but eventually as you're processing that string, you'll hit a point where your pure string processing actually tries to do IO on the file that isn't open anymore, and your perfect sane and pure string processing code will throw an exception. So, that's gross. That's a real issue that will hit beginners. There's been a lot of work done to make ergonomic and performant libraries that handle this without issues; I think that right now pipes[0] and conduit[1] are the big ones, but it's a space that people like to play with. [0] - https://hackage.haskell.org/package/pipes https://hackage.haskell.org/package/pipes [1] - https://github.com/snoyberg/conduit https://github.com/snoyberg/conduit
- ocharles 7y agoLazy I/O is widely regarded as a mistake, I don't really know any Haskell programmers that advocate that anymore. We have plenty of other approaches that have a better foundation.
- vamega 7y agoAre you referring to the various streaming libraries (Conduit, Pipes and the like)?
- whateveracct 7y agoThat is what people use instead of lazy IO. And those libraries aren't just "hacks" to deal with laziness or purity. They're wonderful APIs for writing constant-memory streaming programs. I always wish other languages had libraries on their level.
- lallysingh 7y agoMost popular languages have aspects that you can consider failed experiments. Like java's 1.0 libs or C's declaration syntax.
- whateveracct 7y agoLazy I/O is not especially popular. So I wouldn't really say it's a problem with Haskell since most Haskellers would use other solutions. And Haskell is cutting-edge in more ways than laziness so I don't think this is a big deal. It's _technically_ "why it exists" but in practice there's a variety of other reasons people pick Haskell nowadays besides laziness.
- jeremyjh 7y agoThis is something non-Haskellers worry about quite a lot, and Haskellers not much if at all. Others have mentioned that lazy I/O is not really a thing in real world Haskell. But its important to know that lazy by default has a lot of benefit in the composition of pure functions, e.g. `take 10 . sort`
- dean177 7y agoSorting has to operate on the entire input, how is laziness an advantage here?
- ssijak 7y agobecause maybe later in the program you will deicide that that computation does not need to be executed at all.
- dean177 7y agoCan you give an example of what you mean by this? That’s the kind of thing you would use “if” for right?
- garmaine 7y agoOne was given earlier in the thread: `take 10 . sort` This should be read from right to left: it sorts an (implicit) list, then reads the first 10 elements from the beginning of the now-sorted list, and throws away the rest. Except under the hood in Haskell, sort is not a function that takes a list and returns a list. It is a function that takes a list, and returns the smallest element of that list and a function to fetch the rest of the sorted list. (A function which actually returns the second-smallest element and a function that if called returns the third smallest element and another function, etc.) These ephemeral functions / continuations / "thunks" are allowed to have internal state and thereby perform more complex sorting operations such as quicksort, and might actually be represented by an internal tree of branch points and their returned value or unexecuted thunks. But all of this complexity is hidden by the compiler. It's not some special property of `sort` either, which might look like this: sort [] = [] sort (x:xs) = sort small ++ (x : sort large) where small = [y | y <- xs, y <= x] large = [y | y <- xs, y > x] "The sort of an empty list is the empty list. The sort of a non-empty list is the elements of the tail less than or equal to the head of the list, sorted, followed by the head of the list, followed by the elements of the tail greater than the head of the lest, sorted." The Haskell compiler does the magic of turning this into a lazy-optimized implementation which in the case of `take 10 . sort` doesn't bother to calculate any remaining `sort large` partitions once the first 10 smallest elements have been found.
- 6thaccount2 7y agoI thought it was neat that they talk about trying to parse the "J" programming language.
- tluyben2 7y agoYes, direct link[0]. Though most of the subjects are interesting in my opinion. [0] http://www-cs-students.stanford.edu/~blynn/haskell/jfh.html http://www-cs-students.stanford.edu/~blynn/haskell/jfh.html
- fnord77 7y ago> Programs should be easy to express. in my experience, languages that make it easy to express small programs tend to be counterproductive for large systems with many modules, many people working on the system and the desire for fine-grained control.
- worldsayshi 7y agoI neither want to deny or confirm this but if love to see a more in depth analysis of the idea. Why? Is it because of the "fine grained control" part? Because it breaks the neat abstraction?
- xyzzy_plugh 7y agoExpressiveness is too powerful. I have to understand what the author was thinking. What you typically want is composition. With composition, you have to understand what the author is doing. Painting with acrylics vs Lego.
- agumonkey 7y agofair point, expressiveness shifts idioms toward "single man private jokes" A point I just realize the value.
- worldsayshi 7y agoBut you could just have expressiveness through composition then?
- kccqzy 7y agoIt's basically the same reason Go was created: not many advanced features, lack of abstraction power, leading to highly predictable codebases so that companies can easily onboard new software engineers.
- willtim 7y agoThis was probably the theory behind their decisions. While I agree that the lack of expressiveness might discourage some incidental complexity, for example premature or unecessary abstractions, the inherent complexity of a problem will remain. Removing expressive power, in general, will lead to ad-hoc and cumbersome workarounds. For example, look what the Kubenetes codebase had to do to work around the lack of generics: https://medium.com/@arschles/go-experience-report-generics-in-kubernetes-25da87430301 https://medium.com/@arschles/go-experience-report-generics-i...
- klipt 7y agoGiven the laziness, you'd think memoizing an arbitrary pure function would be easy in Haskell - at least as easy as Python, where you can do it with a one line decorator. But no, it's pretty complicated: https://wiki.haskell.org/Memoization https://wiki.haskell.org/Memoization you even have to involve fixed point combinators. Pretty disappointing.
- afranchuk 7y agoYeah, I think it would need to be a language feature. Otherwise the memoization function itself, with state and conditional execution based on the state, isn't very straightforward.
- rocqua 7y agoBest case scenario would be the optimizer does it for you. Less optimal would be compiler hints saying 'please memoize this function'.
- joppy 7y agoIn simple cases (for example a function UInt8 -> Something) it might be reasonable to expect the optimiser to know how to memoise automatically. But how about functions of the form (Integer -> Something), which have an essentially infinite domain? How much space is the memoization allowed to use? What kind of data structure should be used for the lookup? This could be very hard for the application programmer to communicate to the compiler. Memoising a function f in Haskell can be done like this: create some lazy map where the keys are arguments, and the associated value is f(argument). The function f will only be evaluated once you actually try to use the values of the map.
- felixschl 7y agoI thought the first example in this blog post on TH was an interesting idea: https://www.parsonsmatt.org/2015/11/15/template_haskell.html https://www.parsonsmatt.org/2015/11/15/template_haskell.html
- kevmo314 7y ago
- cloudhead 7y agoNice, but it misses the point that `IO` is actually pure (referentially transparent) in Haskell: let x = putStrLn "hello" in do x ; x is equivalent to: do putStrLn "hello" ; putStrLn "hello"
- rocqua 7y agoI though purity was about having no side-effects. Or, in other terms. If you call the a pure function twice with the same arguments, you get the same output.
- TylerE 7y agoThose two statements aren't actually equivalent. Haskell isn't pure. A truly pure programming language would be completely useless as it wouldn't be able to actually manipulate state at all.
- arianvanp 7y agoNo! Haskell programs are actually pure! The main function returns a _description_ of the actions it's going to do; it doesn't actually execute those actions. You can pass these descriptions around as values, extend them, match on them and what not. Think of it like Command pattern in OOP. When you pass a command around the command doesn't actually execute. It's a value that at some later stage will be executed.
- szc 7y agoBen Lynn is a winner of the 26th IOCCC https://ioccc.org/2019/whowon.html https://ioccc.org/2019/whowon.html with a tiny Haskell Compiler. We will be releasing the code in early July.
- SkyMarshal 7y agoHe's also the L in BLS Signatures and is currently at DFINITY building their system using both BLS and Haskell. https://dfinity.org/team/ https://dfinity.org/team/
- quickthrower2 7y agoI read the article, but I am confused. Where is the "Fan Site". Is this a tutorial for creating a fan website?
- projektfu 7y agoI believe it’s all the links at the bottom represent his fan site.
- wellpast 7y agoHaskell is fun, lots of fun, but it is not a practical language by virtue of it being strongly typed. By practical I mean strongly typed PLs are not forgiving as your world and perceptual view of the world changes over time -- as it does for any business. Your program must pass a proof to execute. For the most part you do not need to explicitly express your types; Haskell can infer them. This is often pointed at by Haskell enthusiasts as some kind of nicety. But it's not because (1) the types are still there; your program still has to cohere wrt to the type system; (2) strongly typed programs with explicit type annotations are much easier to read and manipulate, so you should use them anyway. As your world and assumption changes over time you will find yourself having to do a ton of work to have everything cohere for the type proof again. Haskell enthusiasts also like to say things like with strong typing "Refactoring is easier". This is incorrect. What they are pointing at is that Haskell will of course catch certain (type mismatch) bugs during a refactor, but it will also find many many things that would not be a bug were you using a lighter or no static type system. So ironically this in my experience discourages refactoring because you become exhausted from all the unnecessary labor required to make rather nominal changes to your domain model. Dynamic languages on the other hand, when in the right hands, don't obligate this whole unnecessary labor of effort. I feel that there's an elephant in the room whenever I talk to someone who is enthusiastic about strong typing in a business or real world context. And that's this: that their enthusiasm is more to do with the pure fun of manipulating a type check/prover and playing in these weeds than actually getting real stuff done over time. Another common retort from the strong typing enthusiast is they will point back at a dynamically or loosely typed system they've worked on and point out what a mess it is and then proceed to show how they don't run into certain classes of bugs any more, like null pointer exceptions. What goes unanalyzed is the competence of the team that made the mess, that it was the fault of the skills at play and not a necessary fault of not having types. What also goes unanalyzed is whether the extreme cost of playing the strong typing game is worth an occasional NPE (which are of the immensely easy and fixable kind of bugs) here or there -- in a standard business (ie, not mission critical) context. I realize this is going to be hotly contested and that I'm stepping on toes here, but I think all of this is true. Learn Haskell by all means, but be honest with yourself when comparing it to the successes of more pragmatically designed languages. This should also be no surprise; Haskell is a research langauage born in academia, NOT born out of long practitioner experience. Compare to Elm -- another strongly typed language for the frontend -- which was born out of a doctorate with limited real world experience. Then compare to, say, a Clojure (another esoteric language) but which was born from extensive pragmatic experience and look at the choices it made and how in many cases they run against Haskell's.
- boskdot 7y agoWhat is the time we are in it
- boskdot 7y agoI wish you wher a fish in my dish
- boskdot 7y agoWhat the fuck is it?
- pteredactyl 7y agoFrom my experience Haskellers spend more time talking about how perfect and pure Haskell is than building real-world applications.
- dmix 7y agoWhich doesn't necessarily mean it's not useful for building real world things. Lumi [1][2] was built with Haskell (and PureScript) and looks like a great product. To me it just means more people need to a) build more real things in Haskell b) write about their experience and help advance the state for non-academic newbies and professionals c) avoid the weeds of the language theory stuff and stick the simple established stuff to stay productive (which does exist). You could also make the same argument for JavaScript. So many people are trying to reinvent the wheel every other week in the frontend world, it's just as easy to get distracted by the language/framework noise. Just be a mature developer and don't get suckered into the latest shiny objects. [1] https://www.lumi.com/ https://www.lumi.com/ [2] https://www.lumi.dev/blog/purescript-and-haskell-at-lumi https://www.lumi.dev/blog/purescript-and-haskell-at-lumi
- pteredactyl 7y agoCorrect. Interesting you bring up JS, as the Haskellers I spoke of loathed JS and treated it and its developers as below them. But that's just my experience. I hope Lumi was able to gain efficiencies using PureScript and Haskell. And that they're able to attract talent. Thanks for sharing.
- alexbanks 7y agoIn my experience Haskellers speak that way about every language that isn't Haskell or Rust, which I found to be probably the biggest turnoff for me wrt learning Haskell.
- merijnv 7y agoDon't forget: XMonad, pandoc, and ShellCheck
- 7y ago
- leoh 7y ago> I have seen languages that practically force the programmer to run a heavyweight specialized IDE, and that require 14 words to print 2 words. I picked up Haskell again last week after a long hiatus and although one does not need a heavyweight IDE, the build system still felt like a bit of a mess. After adding `parsec` (a popular library for parsing) to a stack project, my machine spent over an hour compiling dependencies at which I point I gave up.
- thinkpad20 7y agoTry using nix instead. Aside from being a fantastic package manager for many languages it’s a godsend for Haskell. The lengthy compilation times with Haskell are one of the not so wonderful parts of the language, but they largely disappear with nix, and the Haskell ecosystem on nix is very well supported, with a large community. Honestly I’ve been developing Haskell for years and have never once used stack. I did use cabal before I discovered nix, but since then I’d hardly dream of using anything else.
- mlevental 7y agohow does nix eliminate compile times???
- thinkpad20 7y agoIt doesn’t eliminate them entirely; it’s just very smart about allowing packages to be built exactly once, and having a deterministic output such that it can be determined before a package is built whether a prebuilt version is available. This prebuilt version can either be the result of a previous build on your machine, or on another machine served from a repository. Of course, there are times when you need to compile yourself, but most slow-to-compile packages, such as Aeson, Lens, Servant or the aforementioned Parsec, and many more on top of that have prebuilt binaries available when built from community snapshots (nixpkgs/nixos). You can even pin your package definitions to guarantee that you’re building something that will have prebuilt versions available. New project build is usually less than a minute or two, sometimes substantially so (anecdotally). Again, not always, but usually.
- 29athrowaway 7y agoLanguages influence each other. Few people have used OCaml but most will get to use OCaml-influenced features in a mainstream language like Swift, Rust, Kotlin, Scala, etc.
- hardwaresofton 7y agoHere's a chunk of code from a haskell project I work on that shows how concise and nice haskell can be: class EntityStore b m => TagStore b m where -- | Add a new tag addTag :: b -> Tag -> m (Either DBError (ModelWithID Tag)) -- | Get all tags getAllTags :: b -> Maybe Limit -> Maybe Offset -> m (Either DBError (PaginatedList (ModelWithID Tag))) -- | Find tags by a given ID findTagsByIDs :: b -> [TagID] -> m (Either DBError (PaginatedList (ModelWithID Tag))) That bit at the start (`EntityStore b m => TagStore b m`) roughly reads "Given that types 'b' and 'm' exist that satisfy the typeclass 'EntityStore', they satisfy the typeclass 'TagStore' if they implement the following methods". If your eyes glazed reading the sentence above -- this is a way of composing interfaces. Any TagStore-capable thing is required to be EntityStore-capable. And here's that being used: createTag :: ( HasDBBackend m db , HasCacheBackend m c , MonadError ServantErr m , TagStore db m ) => SessionInfo -> Tag -> m (EnvelopedResponse (ModelWithID Tag)) createTag s t = requireRole Administrator s >> validateEntity t >> getDBBackend >>= \db -> getCacheBackend >>= \cache -> addTag db t >>= ifLeftEnvelopeAndThrow Err.failedToCreateEntity -- invalidate cached tag listing & FTS results >>= \res -> invalidateTagListing cache >> invalidateTagFTSResults cache >> pure (EnvelopedResponse "success" "Successfully created new tag" res) I prefer the explicit 'bind' syntax (>>/>>= are pronounced 'bind') to do notation, because of the clarity you get when the code is laid out, and how it encourages modular functions All those `HasDBBackend m db` and `TagStore db m` incantations mean that this function knows about the world is that it has a DB backend, a cache backend, a tag store, and it knows something about the kind of errors it can throw (`ServantErr`). Then come what the actual function does (after the =>) -- Take in a SessionInfo, a Tag, and produce an "action" that when run will produce a EnvelopedResponse (ModelWithID Tag) value. This style is called mtl and it's just one of the big patterns in the haskell community, but the big feature here is that isolation -- this function can only do things that it knows about by way of constraints (`TagStore db m` means a `TagStore`-capable thing is accessible to you) -- this is much safer than having functions that can just do anything at any time. I've said it numerous other times, but the kind of stuff you do in haskell trickles into other languages -- see Florian Gilcher's talk from RustLatam 2019[0] -- it's basically on this same concept but in Rust (one of the reasons I absolutely love rust, they've bolted on a fantastic type system). [0]: https://www.youtube.com/watch?v=jSpio0x7024 https://www.youtube.com/watch?v=jSpio0x7024
- revskill 7y agoDue to the purity, programming in Haskell is like learning how to configure a system, instead of coding. Configuration is always pure, only the underlying runtime system is not.
- dgellow 7y ago> Writing code should be comparable to writing prose. A plain text editor with 80-character columns should suffice. Coding should feel like writing an email or a novel. If instead it feels like filling out a tax return, then the language is poorly designed. So this page has nice statements like those ones, but no justifications. Why should writing code be comparable to writing prose or writing a novel? I don't see why that would result in a better implementation. Prose is full of obscure and non-well defined rules that often require subjective judgement. That's something I want to avoid when writing programs. Program source codes are better written as clear and unambiguous as possible, limiting potential misunderstanding whenever possible, where a person writing prose will play with rhythm, symbolism, rhymes, atmosphere development, etc and can use potential misunderstanding and multiple meanings to add depth to their work. Filling a tax return seems to be better IMHO, I don't need to develop my own style over years and years of writing, I learn the (quite strict) rules, apply them, anyone who also knows the rules can easily fix/extend/improve what I've done.
- scrumper 7y agoSurely the point there is that you should feel like you can write your idea down any way you want, instead of being bound by an extremely tightly constrained form that only lets you put very specific things in certain places. Your tool is a blank sheet of paper and a pen; it's not in your way at all. "This year was a good year: I made $100,000 at my job although I did lose $500 in the stock market."