7 ms·
Why I still reach for Lisp and Scheme instead of Haskell
- ggm 5mo ago> Actually, in my opinion, Scheme (and Lisp) allows you to express complex systems and problem domains in more simple terms than any other language can. Short article. Worth reading. But all I swallowed was this one sentence. Its the sytax. If you like semicolons, thats why you like Pascal-like languages.
- reikonomusha 5mo agoFor all practical purposes, the syntax of Lisp isn't just a cosmetic choice, though.
- rauli_ 5mo agoLisp was meant to be written with M-expressions instead of S-expressions anyway.
- reikonomusha 5mo agoFor a brief period of time over 60 years ago, yes. :)
- SideQuark 5mo agoM-expressions were never implemented and never used.
- Grosvenor 5mo agoExcept in mathematica - which isn’t formally a lisp, but practically it’s used like one a lot of the time.
- ux266478 5mo agoHaskell's syntax comes from ISWIM, which was motivated quite a lot by m-expressions.
- vincent-manis 5mo agoActually, variations on M-expressions have been created many times in the Lisp world. (Look what you can do with macros!) So far, none of them has caught on. The latest attempt for Scheme is SRFI-266, which creates a very nice infix expression sublanguage. If I were working on a team, I would encourage them to use this, but I don't know if it has enough traction to become widespread.
- pfdietz 5mo agoIt's a common mistake to think that the syntax of Lisps are a problem. People solving the supposed problem then discover it wasn't something that needed to be solved.
- drob518 5mo agoIf you want a Lisp that basically has M-expressions, try Dylan. It even started with an S-expression syntax initially and then converted to infix.
- attila-lendvai 5mo agoit's not just the syntax. the entire language, and even the ecosystem in general, has relatively few atoms that can be combined with a higher degree of freedom than the alternatives. it has both upsides and downsides. the upsides mostly win for me.
- busterarm 5mo agoI learned Scheme before Haskell and as much as I enjoyed the experience, I still wouldn't reach for Haskell first. It's pretty much limited to my xmonad configuration.
- nathan_compton 5mo agoI have written a very large codebase in Scheme (gambit) and in the end I really, really, wanted a type system to catch bugs.
- rahen 5mo agoJank looks promising if you want a typed Lisp. It’s essentially native Clojure without the JVM: https://jank-lang.org/ https://jank-lang.org/ In case you're into machine learning, I'm also building something similar - a tensor-first, native Clojure-like ML framework.
- busterarm 5mo agoI get where you're coming from but I talked to a few folks working in large Haskell codebases and I'm not sure I would make that trade.
- nathan_compton 5mo agoYeah, its genuinely a case of "software hard."
- tmtvl 5mo agoThat's why I switched to Common Lisp, its type system isn't perfect but it works well enough for my needs (especially with the occasional (describe 'sycamore:tree-insert) in the REPL).
- nicoty 5mo agohttps://github.com/carp-lang/Carp https://github.com/carp-lang/Carp might be of interest. It's a statically typed lisp.
- 5mo ago
- wild_egg 5mo agoIf you know lisp, just reach for Coalton instead of Haskell
- anonzzzies 5mo agoCoalton has some evolution to go before that, but it is good and flexible enough.
- reikonomusha 5mo agoWhat evolution in particular do you think? The developers use it for commercial products in quantum computing and defense [1]. That doesn't mean it's done in some complete language ecosystem sense (which is discussed in [1], and one could argue Haskell also never feels "finished"), but it also doesn't seem like an unfinished hobby project. Given that it's embedded in Common Lisp, there's always a way to fill in the library gaps, sort of like how if a "native" library doesn't exist in Clojure, one can always reach for Java. [1] From Toward Safe, Flexible, and Efficient Software in Common Lisp at the European Lisp Symposium, "[Coalton] has been used for the past 5 or so years [...] first in quantum computing and now a serious defense application." https://youtu.be/xuSrsjqJN4M&t=9m14s https://youtu.be/xuSrsjqJN4M&t=9m14s
- anonzzzies 5mo agoI am an avid sbcl and coalton user (and sponsor of both when I can) and never said it was not a great thing; comparing it to Haskell is, outside the theoretical type system roots, just a bit early type system wise. I agree with you further and you did an excellent promotional comment for Coalton and CL; keep doing that please. I have said many times here before that I did not like my time away from CL and Coalton makes it even better.
- z3ratul163071 5mo ago[flagged]
- kolme 5mo ago> Of course, to be completely fair about my toolkit, standard Scheme can sometimes lack the heavyweight, “batteries-included” ecosystem required for massive enterprise production compared to the JVM. I was thinking the whole time, "this person would _love_ Clojure".
- nathan_compton 5mo agoKawa is a Scheme which runs on the JVM and is pretty great. https://www.gnu.org/software/kawa/index.html https://www.gnu.org/software/kawa/index.html I am one of these people who cannot countenance a Lisp that doesn't have `syntax-case`.
- arvyy 5mo agokawa is unfortunately a somewhat shoddy project. Alot of halfbaked features / abstraction ideas (eg trying to support CL for whatever reason), dubious tooling for a java project (autotools), unclean and inconsistent code formatting. It's missing some features that are expected in a real scheme like multishot continuations; someone wrote research about it as a MSc thesis, but due to mentioned shoddiness its integration to upstream stalled and hadn't been merged. At some point I thought of forking it to then cut out and polish the core, but then my attention got caught by graal's truffle framework as a plausibly better path for implementing scheme in java
- nathan_compton 5mo agoIts funny, I can definitely sympathize with wanting multishot continuations, but I can't think of many times where I have wanted them to solve a problem.
- packetlost 5mo agoas a part time schemer, I also love Clojure and reach for it more often than Scheme these days.
- evdubs 5mo ago> Lisp hackers have been effortlessly reshaping the language for decades using the powerful macro system and extending and bending the language to their will. I've written a bit of Racket code (https://github.com/evdubs?tab=repositories&q=&type=&language=racket&sort= https://github.com/evdubs?tab=repositories&q=&type=&language...) and I still haven't written a macro. In only one case did I even think a macro would be useful: merging class member definitions to include both the type and the default value on the same line. It's sort of a shame that Racket, a Scheme with a much larger standard library and many great user-contributed libraries, has to deal with the Scheme/Lisp marketing of "you can build low level tools with macros" when it's more likely that Racket developers won't need to write macros since they're already written and part of the standard library. > But the success of Parsec has filled Hackage with hundreds of bespoke DSLs for everything. One for parsing, one for XML, one for generating PDFs. Each is completely different, and each demands its own learning curve. Consider parsing XML, mutating it based on some JSON from a web API, and writing it to a PDF. What a missed opportunity to preach another gospel of Lisp: s-expressions. XML and JSON are forms of data that are likely not native to the programming language you're using (the exception being JSON in JavaScript). What is better than XML or JSON? s-expressions. How do Lisp developers deal with XML and JSON? Convert it to s-expressions. What about defining data? Since you have s-expressions, you aren't limited to XML and JSON and you can instead use sorted maps for your data or use proper dates for your data; you don't need to fit everything into the array, hash, string, and float buckets as you would with JSON. If you've been hearing about Lisp and you get turned off by all of this "you can build a DSL and use better macros" marketing, Racket has been a much more comfortable environment for a developer used to languages with large standard libraries like Java and C#.
- moron4hire 5mo agoSometime back 15 years ago [0], I hit a bit of an existential crisis regarding my career and the kind of work I was doing. I thought the particular technology I was working in was "part of the problem", as I felt pigeon-holed by .NET and C# to always be a corporate-monkey CRUD consultant. So, I went out in search of something better. Different programming languages. Different environments. Just something that wasn't working for asshole clients who thought it was okay to yell at people about an outage in a hotel on the complete opposite side of the country that was more due to local radio interference than anything I had done in the database code that configured things. Long story involving missing a holiday with my family over something completely outside of my control and yet I still got blamed for it. The problem wasn't the technology, it was the company I was working for, but at that time in my life, I didn't understand the difference. Racket was a life preserver at that time. It's really hard to explain, because I never actually ended up working in Racket full-time and I haven't even touched it in probably 10 years. But it still has this impact on my identity as a software developer. I learned Racket. I forced myself out of being a Glub programmer and into someone who saw the strings that underwrote The Universe. The beauty of S-Expressions and syntactic forms and code-is-data and all that. It had a permanent impact on my view of what this job could be. I still work primarily in .NET. Most of the things that were technological issues about .NET Framework got absolved by what was first .NET Core and what is now .NET. So, I no longer feel like my tools are holding me back. And I'll forever be thankful to Racket (and the community! The Racket listserve was amazing back then. Probably still is, I just don't interact with it anymore) for being there for me. Edit: Haskell was in fact another language I explored at that time, in addition to Ocaml and Ruby and Python (ugh! Don't get me started on Python!) and many other things. They were all "cool" in their own way, but nothing felt like Racket. They all had their own weird rules that felt like being bossed at again. Racket felt like art. Racket felt like it was there for me, not the other way around. [0] I still think of this time as the "mid-point" in my career, but it's now been long enough ago that I've been more past the crisis than I was ever in it. Strange feelings.
- anthk 5mo agoI tried some ML language once, it's difficult even to write a basic factorial example, which in Scheme I could do it iteratively and recursively with ease. Either with S9 Scheme for quick fun (it has Unix sockets and ncurses :D ) or Chicken Scheme for completeneless (R5RS/R7RS-small + modules), I always have fun with both. Oh, and well, Forth, too, but more like a puzzle (altough it shines to teach you that you can do a lot with a fixed point). Hint: write helpers for rationals -a/b where a is an integer and b a non-zero integer- and complex numbers by placing two items in the stack for each case (for rat helpers you need four (a/b [+-*/] c/d) . You can have a look at qcomplex.tcl (either online or installed) as an example on how can it work even under JimTCL itself by just sourcing that file. Magic, complex numbers under jimsh thanks to the algebraic properties. So, you can implement the same for yourself in some Forths, even under EForth for Muxleq. Useless? It depends, under an ESP32 it can be damn fast, faster than Micropython.
- zarakshR 5mo agoI don't see how: Racket: > (define (fact n) (if (= n 1) 1 (* n (fact (- n 1))))) > (fact 6) 720 OCaml: # let rec fact = function | 1 -> 1 | n when n > 1 -> n * (fact (n - 1)) in fact 6;; - : int = 720
- kubb 5mo agoWhenever someone complains about not being able to use a slightly different syntax, I assume they just don't have any neuroplasticity anymore.
- drob518 5mo agoI think syntax matches with our brains or not. I think anyone is capable of learning any syntax. The question is whether they want to. At some level, programming is art.
- nine_k 5mo agoEven as simple as fac 1 = 1 fac n = n * (fac (n - 1)) which is a working Haskell implementation? I mean, in Scheme it is longer to write. I enjoy Lisps and use Emacs for everything, but Haskell can be as terse, or even more terse. (Which is not always a good thing.)
- deleted 5mo ago[deleted]
- crabbone 5mo agoI don't believe monads are a "heavy handed abstraction" and that's what prevents people from prototyping in Haskell. What really prevents people from writing in Haskell at a reasonable speed is the poor language design. Programming languages are supposed to aid in reading by emphasizing structure. It's important to emphasize that a particular group of "words" constitutes a function call, or a variable definition, or a type definition -- whatever the language has to offer. Haskell is a word salad. Every line you read, you have to read multiple times, every time trying to guess the structure from the disconnected acronyms. It belongs to the "buffalo buffalo buffalo buffalo" gimmick family. This is a huge roadblock on the way to prototyping as well as any other activity that implies the ability to read code quickly. And then it's also spiced by the most bizarre indentation rules invented by men. This is not at all a problem with eg. SML or Erlang, even though they are roughly in the same category of languages. Haskell would've been a much better language if it made its syntax more systematic and disallowed syntactical extensions s.a. introduction of user-invented infix operators, overloading of literals (heaven, why???) and requiring parenthesis around function arguments both for definition and for application. The execution model is great, the typesystem is great... but the surface, the front door to all these nice things the language has is just some amateur level nonsense. * * * As for the upsides of using languages from the Lisp family for practical problems... I don't find (syntax-rules ...) all that exciting. I understand this was an attempt to constrain the freedom given by Common Lisp macros, and I don't think it worked. I think it's clumsy and annoying to deal with. The very first time I tried to use it, I ran into its limitations, and that felt completely unjustified. To prototype, you want freedom of movement, not some pedantry that will stand in your way and demand you work around it somehow. The absolute selling point, however, is SWANK. Instead of editing the source code, you are editing the program itself, that can be interacted with in points of your choosing. I don't know of any modern language that offers this kind of experience. I think, even still in the 80s, this approach to programmers interacting with computers was common. At school, we had terminals with some variety of Basic, and it worked just like that: you type the program and it instantly shows the effect of your changes. Then, there was also Forth, which also worked in a similar way: it felt like you are "talking" to the computer in a very organized and structured way, but real-time. Most mainstream languages today sprouted from the idea of batch jobs, where the programmer isn't at the keyboard when the program runs. They came with the need to anticipate and protect the programmer from every minor mistake they might've easily detected and fixed during an interactive session far, far in advance. Whenever I think about writing in C, or Rust, or Haskell, I imagine being tasked with going to the grocery blindfolded: I'd need to memorize the number of steps, the turns, predict the traffic, have canned strategies for what to do when potatoes go on sale... I deeply regret that programming evolved using this evolution path, and our idea of what it means to program is, mostly, the skill of guessing the impossible to predict future, instead of learning to react to the events as they unfold.
- coldtea 5mo agoBecause they're elegant. Haskell is a conceptual and syntax mess.
- ngruhn 5mo agoCompared to lisp? Ok fine. Syntax doesn't get more simple than Lisp. But compared to JavaScript? C++? C#? Haskell is top tier when it comes to syntactic and conceptual elegance. The biggest problem is tooling, I would say.
- coldtea 5mo agoI don't think: "Haskell: more elegant than Javascript and C++" would make a good promotional motto. That's like bragging how prettier you are than Danny Trejo.
- jes5199 5mo agoI could not agree less. People used to call Python “executable pseudocode” - in that spirit, Haskell is executable pseudo-math. If you’ve done enough higher math that a professor’s whiteboard notation feels natural to you, then Haskell might feel like a reasonable approximation of that style. Otherwise: it’s line noise. (I write Haskell professionally)
- anthk 5mo agoThat's what I felt with MLite, my intro to ML languages (A quick start in Ocaml barely counts, so didn't configuring XMonad back in the day). It's like declaring math statements, observations and rules as if it were a Math textbook. https://www.t3x.org/mlite/index.html https://www.t3x.org/mlite/index.html
- isatty 5mo agoHaskell is very elegant and pretty. It's hard to describe what pretty is when it comes to programming languages, but imo golang is ugly, rust is good, and Haskell the best.
- qsera 5mo ago
- tmtvl 5mo agoSeems a bit similar to 'Why I prefer Scheme to Haskell' (<https://news.ycombinator.com/item?id=3816385 https://news.ycombinator.com/item?id=3816385>, 2012). Seems a bit plagiarized, but that may just be a coincidence.
- harrisi1 5mo agoIt is odd. Especially since the author of the article states they’ve been working with Haskell for years, although they only have a few years of experience and only have a single Haskell repository on GitHub with some simple leetcode problems. The number of both nearly and exactly identical phrasing makes it feel like someone told an LLM to slightly rephrase that older article. I didn’t even know the internet was sick.
- privong 5mo ago> You can pause, inspect objects, change values, and even redefine a broken function on the fly to test a fix in any environment (yes even in production, while running). I see this mentioned often, and it sounds amazingly useful (especially the part about fixing in production!). But how truly widespread is it among the Lisp dialects to be able to connect to a running program, debug, and hotfix it? I understand Common Lisp has it, but I struggled to figure out how to do it in, say, Racket. Admittedly I'm am relatively inexperienced Lisp programmer, so maybe I wasn't looking in the right place or for the right words. Which Lisp dialects do indeed support the extreme version of this capability to inspect and edit running programs?
- dmux 5mo agoI also see this mentioned often and have wondered the same. I can sort of envision this working in a single threaded application, but how would this work in a web application for example? If a problematic function needs to be debugged, can you pick what thread you're debugging? If not, do all incoming requests get blocked while you debug and step through stack frames?
- Jach 5mo agoBeing paused in the debugger is per-thread. If the server's using a thread-per-request model, and you're stopped in the request, then other requests can proceed just fine. If some of those requests also trigger the debugger, they'll pause and have to wait, they won't interrupt your current debugging view. Extra care should be taken in any sort of production debugging, of course. (At a Java BigCo, production debugging was technically allowed but required multiple signoffs, the engineer wasn't the one in control but had to direct someone else, lots of barriers to prevent looking at arbitrary customer data, and of course still limited to what you can do with a standard JVM restarted in debug mode. (Mainly setting breakpoints and walking stack traces.)) But the nicest part is that once you connect to the production application, apart from network lag it's no different than if you were developing and debugging locally on similarly specced hardware to the server, you have all the same tools. Many of the broader activities around "debugging" don't need to happen in a paused thread that was entered with an explicit breakpoint or error, they can happen in a separate thread entirely. You connect, then you can start inspecting (even modifying) any global state, you can define new variables, you can inspect objects, you can define new functions to test hypotheses, redefine existing functions... if you want all requests to pause until you're done, you can make it so. Or if you want to temporarily redirect all requests to some maintenance page, you can make that so instead. A simple thing I like doing sometimes when developing locally (and I could do it on a production binary too) is to define some (namespaced) global variable and redefine a singly-dispatched method to set it to the self object (possibly conditionally), and once I have it I might redefine the method again to have that bit commented out just so I know it won't change underneath me. Alternatively I can (and sometimes do) instead set this where the object is created. Then I have a nice variable independent of any stack frames that I can inspect, pass to other method calls, change properties of, whatever, at my leisure without really impacting the rest of the program's running operation. Another neat trick is being able to dynamically add/remove inherited mixin superclasses to some class, and when you do that it automatically impacts all existing objects of that class as well. Mixin classes are characterized by having aspect-oriented methods associated with them; you can define custom :before, :after, or :around methods independent of the primary method that gets called for some object.
- jgalt212 5mo agoIrrespective of the language, I love the REPL. For this reason, among others, I just cannot get into Agentic Coding. It seems like a step back to batch processing.
- jimbokun 5mo agoMany people here are saying AIs work great with the REPL.
- galaxyLogic 5mo agoOr that the AI is the REPL?
- jgalt212 5mo agoI think it does, but Agentic does not.
- SoftTalker 5mo ago> Remember that later, just adding a simple print somewhere is not going to work without refactor (welcome to the IO monad). This hits home for me. Print statements are essential to the way I code. I use them to debug, to examine variables and parameters, to trace execution flow. I rarely use debuggers.
- qsera 5mo agoDebug.Trace.trace and friends can help here. This can work from pure code. But the lazy evaluation does imply that trace functions only execute when the statement is actually forced evaluation. But it is actually quite helpful during debugging..
- dunham 5mo agoFor caveman debugging, if I'm not sitting in a monad, I usually reach for something like Debug.Trace. Typically that's in Idris or my own language, but I see that haskell has it too. For my own language, I have the syntax highlighting set to put the `trace` keyword in red, so I can easily clean up.
- galaxyLogic 5mo ago(In Haskell) > just adding a simple print somewhere is not going to work without refactor Interesting. How do people cope with this in practice? Does it mean you can't really use log() -statements for debugging?
- floxy 5mo agoThere is always `trace`. https://wiki.haskell.org/Debugging#Printf_and_friends https://wiki.haskell.org/Debugging#Printf_and_friends
- buggymcbugfix 5mo agoWhoa you were faster than me!
- spockz 5mo agoYou could wrap it in an unsafeIO function to make it return `()` again. However, I’ve had very little use of printing for debugging. In Haskell you write small (ish) and pure functions that you can test extensively with property based testing. The types already help a lot as well. So basically the only place where you deal with unexpected input is at the communication boundaries of the app where you are in some form of IO already and printing just available.
- mchaver 5mo agoThat's fine for a library or locally run executable, but I've worked on distributed systems in Haskell and you really need logging in place to track what is going on. Of course, you will have IO somewhere in a executable where you can handle logging so just separate pure and IO and make sure you have good tests for the pure functions. Also, linting to catch partial functions and dangerous lazy ones (or use an alternative prelude).
- spockz 5mo agoSure you want logging and tracing (in the RPC sense not Debug.Trace.trace). Most of this can still be done from IO places where the pure functions collect enough error information bubbling up (e.g. content and line/col of parser errors etc.) to not need ad hoc print statements for debugging.
- ReptileMan 5mo agoHaskell is godsend when using LLMs though.
- rootnod3 5mo agoI don't even know where to begin here... Using Haskell, the one language where you really DESIGN by types and use your brain and then using LLMs....
- bitwize 5mo agoPicking a language is a matter of selecting the best fit given the constraints of the project. For stuff I like to work on on my own time, "how I like to work" is a major forcing constraint. So it's no surprise that I have a large number of Lisp projects sitting around. Maybe it's because I'm auDHD, but the ability to evolve a program through active dialogue with the machine (and not of the sloppotron variety) just fits better with how I think through a problem and its solution.
- imdoor 5mo agoI think I much prefer Haskell DSLs over Lisp macros as the basis for APIs in foreign code. That might be due to my relative inexperience with Lisps, but macros just seem to make all the bad aspects of dynamically typed langues much worse. Looking at some piece of code in isolation, not only is it often impossible to tell what is the type/shape of data that are coming in (as is common with dynamic type systems) but with macros added to the mix I also can't tell what the control flow is. So to understand what a single piece of code is doing, I find myself chasing for hints that are scattered throughout the entire codebase. Contrast this to Haskell's use of DSLs – although they really can be quite dense sometimes, I feel like, when I get stuck, I can always just dig into the documentation on Hackage, and figure out things from the type definitions (even when explanations in docs are lacking). Though it does require being comfortable with the abstractions being used (monads and such). Rust is similar in this manner but to a lesser extent. But again, maybe the macro critique stems from my inexperience with them.
- Xmd5a 5mo agoI wrote a couple macros that record data transiting through code at runtime (it's in Clojure, so basically almost every function is pure, returning what they produce as if it was water flowing out of a faucet), stores these intermediary results in a file, and finally display these values in the code itself, as comments, just below the call-site that produced them. You can then, for a given call-site, choose to "load" these recorded computations, which will change the displayed comments, both below this call site and all the other instrumented call-sites that are downstream to it, even for code sitting in other source files. It's a bit fragile and needs more polishing but it's a lot more convenient than any type system that will always get in the way, be not powerful enough, and it allows me to see what kind of data flows in my program without running it. Because I record everything and display the result not at compile-time but at coding time, in the same window, alongside the rest of the code. I don't understand why this was never done (to the best of my knowledge). Biggest limit I encounter is that Clojure doesn't provide any mean to identify areas in my code that are not pure.
- deleted 5mo ago[deleted]
- ux266478 5mo ago> Actually, in my opinion, Scheme (and Lisp) allows you to express complex systems and problem domains in more simple terms than any other language can. I disagree with this. Lisps are procedural languages like most other programming languages. They can describe procedures in a relatively simple manner (big asterisk on that one, the simplicity of Lisp as a language is greatly overstated and conflated with the simplicity of s-expressions), but procedures aren't really good at expressing complex systems or problem domains. In my experience, relational languages like Prolog are vastly better at this. Lisps are much easier for the average programmer to pick up and write reasonably performant code with, though.
- _glass 5mo agoI think it all depends on the shape of the problem. I love Prolog for its expressive power, sometimes. Complex simulation problems are really nice to model in OCaml or CLOS, and then again, maybe remodelling in Prolog brings some insights. And often writing a recursive function in Lisp is all you need to understand a complex system. It's all layers. An outer shell to prolog would be a theorem solver for example, because Prolog is a very rudimentary one.
- ux266478 5mo ago> An outer shell to prolog would be a theorem solver for example, because Prolog is a very rudimentary one. I think it's the wrong way to look at it. It's not that Prolog is a rudimentary theorem solver, it's that theorem provers are a specialized use-case of deductive proofs, so a computational foundation of FOL makes them trivial to write as programs. A pile of bricks and a jar of mortar isn't a rudimentary house, so to speak.
- jhbadger 5mo agoProlog is great for the cases where logic programming makes sense but you can easily create a logic engine in Lisp (or other languages - that's what kanren/minikanren which has been ported to many languages)
- 5mo ago