Top
Best
New

Posted by jjba23 19 hours ago

Why I still reach for Lisp and Scheme instead of Haskell(jointhefreeworld.org)
177 points | 80 comments
galaxyLogic 17 minutes 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 1 minute ago|
There is always `trace`.

https://wiki.haskell.org/Debugging#Printf_and_friends

evdubs 8 hours 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...) 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#.

aidenn0 5 hours ago||
How do Lisp developers deal with XML and JSON? Convert it to s-expressions.

As a common lisp developer, that is only very vaguely true for me.

The mapping I prefer for json<->Lisp is:

  true:  t
  false: nil
  null:  :null
  []     #()
  {}     (make-hash-table :test #'equal)
This falls out of my desire for the mapping to be bijective:

- The only built-in type that is unambiguously a mapping type is hash-tabe.

- nil is the only value that is falsy in CL

- () is the same as nil, so we can't use it as an empty list; vectors are the obvious alternative

- Not really any obvious values left to use for "null" so punt to a keyword.

sparkie 43 minutes ago||
In Kernel I would use something like this:

    true        #t
    false       #f
    null        ()
    [...]       (& ...)
    "k" : v     (: k v)
    {...}       (@ ...)  
Where &, :, @ are defined as:

    ($define! &
        ($lambda args (cons list args)))

    ($define! : 
        ($vau (key value) env
            (list key (eval value env))))
            
    ($define! @ 
        (wrap 
            ($vau kvpairs env 
                (eval (list* $bindings->environment kvpairs) env))))
Using the "person" example from the JSON/syntax section on Wikipedia:

    ($define! person
        (@
            (: first_name "John")
            (: last_name "Smith")
            (: is_alive #t)
            (: age 27)
            (: address 
                (@
                    (: street_address "21 2nd Street")
                    (: city "New York")
                    (: state "NY")
                    (: postal_code "10021-3100")))
            (: phone_numbers
                (& (@ (: type "home") (: number "212 555-1234"))
                   (@ (: type "office") (: number "646 555-4567"))))
            (: children
                (& "Catherine" "Thomas" "Trevor"))
            (: spouse ())))
I would then define `?`

    ($define! ? $remote-eval)
Now we can query the object.

    > (? age person)
    27

    > (? postal_code (? address person))
    "10021-3100"

    > (car (? children person))
    "Catherine"

    > (cdr (? children person))
    ("Thomas" "Trevor")

    > (? type (cadr (? phone_numbers person)))
    "office"

    > (? number (car (? phone_numbers person)))
    "212 555-1234"

    > ($define! full_name ($lambda (p) (string-append (? first_name p) " " (? last_name p))))
    > (full_name person)
    "John Smith"
anonzzzies 11 minutes ago||
[0] https://web.cs.wpi.edu/~jshutt/kernel.html
0x3444ac53 5 hours ago|||
For what it's worth, anytime I have written a macro it's usually not because it's needed, but just because I think it'll be fun :)
sparkie 2 hours ago||
When I learned Scheme, I liked the language but strongly disliked macros and quotation. I'd only been using it a short while and when I searched for solutions to a few problems these "fexpr" things kept appearing up, which i didn't understand, and this "Kernel" language. I decided to learn it since "fexprs" were apparently the solution to several of my problems. This wasn't easy at first - I had to read the Kernel Report several times, but I ended up finding it way more intuitive than using macros and quotes.

I've not written a Scheme macro since. I've written hundreds of Kernel operatives though.

I was also a typoholic previously, but am in remission now thanks to Kernel.

https://web.cs.wpi.edu/~jshutt/kernel.html

moron4hire 6 hours ago||
Sometime 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.

privong 4 hours 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?

anonzzzies 4 minutes ago||
I use it a lot for my one man projects; it is really fantastic in that setting. I use SBCL exclusively; it is very fast and robust and has image based development. I have my own versioning toolkit so I don't go insane.

It is obvious why it is not really used or recommended as it really falls flat in a team setting, mostly even when 2 people are involved. But fixing bugs live as they happen and then spitting out a new .exe for clients is still a lot faster than modern alternatives. Far more dangerous too.

shawn_w 37 minutes ago|||
That sort of hotfix workflow isn't really a thing in Racket or Scheme in general. Changing the definition of a function doesn't update everything else that calls that function like it does in CL.

Maybe emacs lisp works that way?

rtpg 26 minutes ago||
Clojure allows for that, giving you neat hot reload capabilities when working in Clojurescript. I believe Emacs Lisp works the same way, and allows for fairly fluid debugging sessions.

Universal hot reload is really a messy beast though. For every "yeah we can just reload this without re-init'ing the structure" there's another "actually reloading causes weird state issues and you have to restart everything anyways" thing.

I've found that hot reloading _specific targetting things_ tends to get you closer to where you want. But even then... sometimes using browser dev tools to experiment on the output will get you where you want faster than trying to hot reload clojurescript but having to "reset" state over and over again or otherwise work around weirdness.

I think this flow works well in Emacs though because you're operating on an editor. So you can change things, press buttons, change things, and have a good mental model. Emacs Lisp methods tend to have very little state to them as well (instead the editor is holding a bunch of exposed state).

Meanwhile React (for example) has _loads_ of hard-to-munge state that means that swapping one component for another inline might be totally fine or might just crash things in a weird way or might not have anything happen. Sometimes just a full page refresh will save you thinking about this

b00ty4breakfast 3 hours ago|||
it's been my experience that when most people say "Lisp does this that or the other", what they usually mean is "Common Lisp does this that or the other". Often there's an implicit "with SLIME" in there as well
drob518 1 hour ago|||
This is doable in Common Lisp, Scheme/Racket, and Clojure. Yes, it might require some tooling.
privong 3 hours ago|||
That could very well be it. I guess I had gotten my hopes up, seeing the statement in a piece that purported to be specifically about Scheme .
drob518 1 hour ago|||
It’s common in Clojure as well as other Lisps. I was just doing that exact thing, modifying a running program in production, earlier this week, adding in print calls to gather debugging information and then modifying the code to fix the bug and it immediately going live and the correct behavior verified.
rtpg 4 hours ago|||
Python is not Lisp, but jumping into a Python REPL in a halfway-run program and poking at the internals easily is _very_ useful as a debugging tool, quickly getting you answers on some messier programs.

It's a shame that other scripting languages that theoretically have the capabilities to do this don't do this (looking at you, node! Chrome dev tools are fine but way too futzy compared to `import pdb; pdb.set_trace()` and "just" using stdin)

I do also use Emacs, and with Emacs Lisp `trace-function` means you can very quickly get call traces in your running instance without having to pull out a debugger and the like. Not like you can't trace functions with `gdb` of course. But the lowered barrier to entry and the ability to do in-process debugging dynamically means you just have access to richer debugging tools from the outset.

ch4s3 2 hours ago|||
In ruby it used to be common to ssh into a box, attach to the console and edit files from the REPL and rerun the code to see if your patch worked. I haven’t touched it in years and I doubt many people do that anymore.
fragmede 1 hour ago|||
Yeah not having an equivalent to pdb.set_trace() is what turned me off compiled languages, but with AI I'm not even sure anymore.
wonger_ 3 hours ago|||
Not Lisp, but for those interested in editing programs that are running in production:

I read some Erlang article saying that hot swapping is not actually very useful in production because of some reasons, and instead a blue-green deployment is preferred. Can't find the link atm. This was close: https://learnyousomeerlang.com/relups

Compare to this comment: https://news.ycombinator.com/item?id=42405168 Hot swaps for small patches and bugfixes, and hard restarts for changing data structures and supervisor tree.

ch4s3 2 hours ago||
It not that hot swapping isn’t useful, it’s just difficult to do well and you need to write your code in a way that supports it. If you need 0 downtime on a device that can do a blue green deployment then the BEAM has you covered. Most people just don’t need that, so the extra hassle isn’t worth constantly considering how to migrate data in flight.
dmux 4 hours ago|||
I 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?
arikrahman 3 hours ago||
The nREPL is present even in newer dialects. It is as easy as installing Calva vscode extension for Clojure, or jacking in with Cider. This makes it perfect for LLM interaction as well.
kolme 8 hours 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 8 hours ago||
Kawa is a Scheme which runs on the JVM and is pretty great.

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`.

packetlost 8 hours ago||
as a part time schemer, I also love Clojure and reach for it more often than Scheme these days.
ggm 19 hours 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 8 hours ago|
For all practical purposes, the syntax of Lisp isn't just a cosmetic choice, though.
rauli_ 8 hours ago||
Lisp was meant to be written with M-expressions instead of S-expressions anyway.
reikonomusha 7 hours ago|||
For a brief period of time over 60 years ago, yes. :)
drob518 1 hour ago||||
If 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.
SideQuark 6 hours ago|||
M-expressions were never implemented and never used.
vincent-manis 1 hour ago|||
Actually, 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.
ux266478 6 hours ago||||
Haskell's syntax comes from ISWIM, which was motivated quite a lot by m-expressions.
Grosvenor 6 hours ago|||
Except in mathematica - which isn’t formally a lisp, but practically it’s used like one a lot of the time.
coldtea 5 hours ago||
Because they're elegant. Haskell is a conceptual and syntax mess.
ngruhn 5 hours ago||
Compared 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.
jes5199 3 hours ago|||
I 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)

coldtea 4 hours ago|||
I 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.

isatty 1 hour ago|||
Haskell 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 39 minutes ago||
For me one of the best things about Haskell is syntax and how clean it is.
tmtvl 5 hours ago||
Seems a bit similar to 'Why I prefer Scheme to Haskell' (<https://news.ycombinator.com/item?id=3816385>, 2012). Seems a bit plagiarized, but that may just be a coincidence.
wild_egg 8 hours ago||
If you know lisp, just reach for Coalton instead of Haskell
anonzzzies 8 hours ago|
Coalton has some evolution to go before that, but it is good and flexible enough.
reikonomusha 8 hours ago||
What 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

anonzzzies 7 hours ago||
I 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.

SoftTalker 46 minutes 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.

dunham 34 minutes ago||
For 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.

qsera 40 minutes ago||
Debug.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..

busterarm 9 hours ago|
I 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 8 hours ago|
I have written a very large codebase in Scheme (gambit) and in the end I really, really, wanted a type system to catch bugs.
Boxxed 2 hours ago|||
Can you say more about the system? A lifetime ago I was really excited about gambit (and bigloo) but I never had the chance to work with them beyond messing around here and there after work.
DASD 4 hours ago||||
There's also Crunch Scheme(from creator of Chicken): https://wiki.call-cc.org/eggref/6/crunch
nicoty 4 hours ago||||
https://github.com/carp-lang/Carp might be of interest. It's a statically typed lisp.
tmtvl 5 hours ago||||
That'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).
rahen 7 hours ago||||
Jank looks promising if you want a typed Lisp. It’s essentially native Clojure without the JVM: 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 6 hours ago|||
I 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.
More comments...