News for hackers. Stuff that matters.
Re:Racket is 25 by billfruit on Sunday May 10, 2020 @11:27AM (#23133357)
I thought similarly too, until I spent some time with Prolog. Lisp seems wonderful and clever, but Prolog feels even more magical.

Even a simple thing like concatenation of two lists, that in Prolog it could be done purely declaratively, without expressing how it needs to be performed, seems levels more magical than how it is done in lisps.

Re:Racket is 25 by JadeNB on Sunday May 10, 2020 @11:34AM (#23133411)
> Even a simple thing like concatenation of two lists, that in Prolog it could be done purely declaratively, without expressing how it needs to be performed, seems levels more magical than how it is done in lisps.

Not to argue with the claim—Prolog does feel magic sometimes—and maybe I'm not sufficiently idiomatic in Prolog, but I would express them similarly: something like

    concat([], Bs, Bs).
    concat([A|As], Bs, [A|Cs]) :- concat(As, Bs, Cs).
I don't have Lisp code at my fingertips, but that's quite like how I'd do it in Haskell:

    concat [] bs = bs
    concat (a:as) bs = a:(concat as bs)
(I just looked up a Scheme primer to refresh myself, and I think there I'd do

    (define (concat as bs)
        (if (null? as)
            bs
            (cons (car as) (concat (cdr as) bs))))
… but don't hold me to it. Certainly not using pattern matching makes the Scheme more verbose, but I think the algorithms are the same.)

Is one of these an un-idiomatic way to do it? (If you downvote, of course that's your right, but I'm not being sarcastic or dismissive; I really would like to know, as not a professional programmer in or fluent speaker of any of these languages, if I'm not writing idiomatically, so I'd be grateful if the downvote came with a comment!)

Re:Racket is 25 by ookdatnog on Sunday May 10, 2020 @01:03PM (#23134150)
As far as I (not a Prolog expert) know, your Prolog is idiomatic. But it is more declarative than your Haskell or Scheme implementations: you didn't just define a concat function, but a concat relation, which means you can use it in many more ways. Here are just a few examples from the top of my head.

- The head function (with X unknown and L known): concat([X],_,L).

- Produce all splits of a list (with X and Y unknown): concat(X,Y,L).

- Does this list start with the prefix [a,b]? If yes, let Y be the tail (Y unknown): concat([a,b],Y,L).

... and many more.

Re:Racket is 25 by mturmon on Sunday May 10, 2020 @04:36PM (#23135944)
This is one of the most magical things about Prolog.

From my functional/imperative programming experience, when originally picking up Prolog, I conceptualized the kind of operations you describe (e.g., “all splits”) as “running the function backwards” — going from output L to inputs X, Y.

But as you write, the truth is that Prolog is modeling the relation of concatenation.

Re:Racket is 25 by junke on Sunday May 10, 2020 @12:55PM (#23134086)
What is magical apart from pattern matching is how you can invocate it with mixed ground terms and variables, and it computes values that makes the relationship holds. For example:

    [eclipse 2]: concat(A,[_,_],[0,1,2,3,4]).

    A = [0, 1, 2]
    Yes (0.00s cpu, solution 1, maybe more) ? ;

    No (0.00s cpu)
Or iterate over all lists that concatenate as [0,1,2]

    [eclipse 3]: concat(A,B,[0,1,2]).
    
    A = []
    B = [0, 1, 2]
    Yes (0.00s cpu, solution 1, maybe more) ? ;
    
    A = [0]
    B = [1, 2]
    Yes (0.00s cpu, solution 2, maybe more) ? ;
    
    A = [0, 1]
    B = [2]
    Yes (0.00s cpu, solution 3, maybe more) ? ;
    
    A = [0, 1, 2]
    B = []
    Yes (0.00s cpu, solution 4)
Re:Racket is 25 by soegaard on Sunday May 10, 2020 @04:37PM (#23135947)
FWIW there is of course pattern matching in Racket. The program is practically identical to the Haskell version.

    (define (concast as bs)
        (match as
           ['()         bs]
           [(cons a as) (cons a (concat as bs))]))
Re:Racket is 25 by masklinn on Sunday May 10, 2020 @02:05PM (#23134636)
> Is one of these an un-idiomatic way to do it?

I think the Scheme version might be considered unidiomatic due to the inefficiency: because it's not tail recursive, you're building a huge stack of cons calls (whose depth is the length of `as`) which only resolve once you reach the end of `as`.

On the other hand, the version with accumulator would need to traverse as and bs in full, twice (once for the accumulation and once for the terminal reverse).

Re:Racket is 25 by soegaard on Sunday May 10, 2020 @05:18PM (#23136287)
There is a difference between Racket and Scheme. In Racket you will never get a stack overflow. You can of course exhaust memory, if you allocate too much, but the stack is guaranteed not to overflow.

    > At the same time, recursion does not lead to particularly 
    > bad performance in Racket, and there is no such thing as 
    > stack overflow; you can run out of memory if a computation 
    > involves too much context, but exhausting memory typically 
    > requires orders of magnitude deeper recursion than would 
    > trigger a stack overflow in other languages. These 
    > considerations, combined with the fact that tail-recursive 
    > programs automatically run the same as a loop, lead Racket 
    > programmers to embrace recursive forms rather than avoid
    > them.
https://docs.racket-lang.org/guide/Lists__Iteration__and_Rec...
Re:Racket is 25 by pflanze on Monday May 11, 2020 @08:22AM (#23141304)
> There is a difference between Racket and Scheme

AFAIK the Scheme standard doesn't specify either behaviour. So, with regards to the standard both Racket and other Scheme implementations are free to implement this in whichever way they prefer. And the Scheme implementations that I'm using (Gambit-C, Chicken and Guile) all work the same as Racket. I only know of Bigloo (of about 10 years ago, maybe it changed?) as a Scheme implementation that uses the machine stack to implement Scheme continuation frames, and Bigloo (again back then) doesn't (didn't?) implement call/cc efficiently either. Arguably to implement the Scheme standard including call/cc efficiently the machine stack can't be used, thus any 'proper' Scheme implementation will behave like Racket.

I've written lots of non-Racket Scheme code and my code always assumes that it won't run out of stack before running out of heap. Some Scheme implementations allocate stack frames more efficiently than cons cells thus the straight-forward solution (recursion, not iteration then reversion) is also the more efficient one.

There is a tendency of claiming Racket is somehow fundamentally different from Scheme which sometimes feels a bit like a racket. I'm not opposed to Racket but think it would be better to take it as what it is, Scheme plus extensions and tooling, which most other Scheme implementations are as well. Racket may be doing that more extensively but I think it calling itself a different language should primarily be understood as a marketing ploy, or maybe as a move to justify moving ahead with own ideas without caring about the rest of the Scheme community.

Re:Racket is 25 by soegaard on Monday May 11, 2020 @10:21AM (#23142416)
I agree that some Scheme implementations have the same guarantee - it's not in the Scheme standard though (as far as I know), which makes it difficult to rely on, if you are programming to the spec.

It is difficult to use the machine stack and have a fast call/cc. But I think Chez Scheme proves it's possible.

Re:Racket is 25 by pflanze on Monday May 11, 2020 @04:43PM (#23146812)
> it's not in the Scheme standard though (as far as I know), which makes it difficult to rely on, if you are programming to the spec.

If you want to have code work everywhere and thus code defensively, yes. Of course you're not coding defensively when coding for Racket, either. But I guess I may have misunderstood you--Racket simply gives you a guarantee; as do some (other) Scheme implementations, so you get the same benefit with some/many other Scheme implementations as well--but even those "differ from Scheme (the standard)", by giving such a guarantee outside of the standard. It's not "Racket vs. Scheme implementations", it's "the (Racket|Scheme) implementations vs. Scheme (the standard)". Apologies then for my inappropriate rant about false claims.

> It is difficult to use the machine stack and have a fast call/cc. But I think Chez Scheme proves it's possible.

Chez doesn't seem to be using the machine stack either. I've tested with this code and "ulimit -s 1000" on Linux and it runs fine:

    (define (conc a b)
      (if (null? a)
          b
          (cons (car a)
                (conc (cdr a) b))))

    (define (iot n)
      (if (negative? n)
          '()
          (cons n (iot (- n 1)))))

    (display (length (conc (iot 10000000) (iot 10000000))))
    (newline)
Note that it can still be using a stack built from "machine" or C style arrays as building blocks--that's what I meant with "Some Scheme implementations allocate stack frames more efficiently than cons cells". Gambit falls into this category as well. But you can't have a single uninterrupted C style array as the stack (with no copying during GC like Chicken does) and implement call/cc efficiently. So I'd still say that implementing Scheme including call/cc efficiently means moving away from the stack controlled by ulimit -s, and once that is done it's easy/natural for an implementation to also offer unbounded stacks like Racket does.
Re:Racket is 25 by soegaard on Monday May 11, 2020 @05:18PM (#23147200)
I think we agree: an implementation that uses a stack to make normal calls fast, needs to move/copy the stack to the heap, when the stack runs out (or when a continuation is captured).

I don't know whether Chez still uses the same strategy, but a major part of Dybvig's thesis (very readable) was dedicated to explain the details of a possible implementation strategy.

http://agl.cs.unm.edu/~williams/cs491/three-imp.pdf

Re:Racket is 25 by pankajdoharey on Sunday May 10, 2020 @04:29PM (#23135891)
Actually pattern matching is easy to implement in fact its implementation is given in SICP Section 4A Pattern Matching and Rule Based Substitution. And most modern lisps have it.
Re:Racket is 25 by dreamcompiler on Sunday May 10, 2020 @10:51PM (#23138457)
Looks right to me except the Scheme version is not tail-recursive, so there's a possibility of stack overflow.
Re:Racket is 25 by sedachv on Sunday May 10, 2020 @05:27PM (#23136370)
The good thing is you can have both: https://mitpress.mit.edu/books/reasoned-schemer
Re:Racket is 25 by efxz on Monday May 11, 2020 @03:06AM (#23139589)
Re:Racket is 25 by jrumbut on Sunday May 10, 2020 @05:29PM (#23136381)
Something I would like to see is Prolog/logic programming embedded in a general purpose programming language, rather than as a standalone system.

Racket and Perl 6 are the only small userbase languages in the last 8-10 years to amaze me with their feature sets and to make me want to learn them just for the sake of experiencing them.

Re:Racket is 25 by FPGAhacker on Sunday May 10, 2020 @06:32PM (#23136935)
Probably doesn’t fit what you mean by mainstream, but even so: https://github.com/clojure/core.logic

> A logic programming library for Clojure & ClojureScript. core.logic offers Prolog-like relational programming, constraint logic programming, and nominal logic programming for Clojure.

Re:Racket is 25 by jrumbut on Sunday May 10, 2020 @10:27PM (#23138327)
This is cool but less what I'm looking for, which is to actually write Prolog inside my Clojure source, or be able to include a Prolog file.

Minikanren seems to be closer to the mark, and I thank the commenters who pointed it out!

Re:Racket is 25 by harperlee on Monday May 11, 2020 @11:31AM (#23143195)
I think you oversaw that core.logic is basically a minikanren (plus c-kanren, plus alpha-kanren) implementation in clojure instead of scheme; it even points to the papers and to The Reasoned Schemer for documentation and reference.

That is, saying that minikanren seems to be closer to the mark than core.logic is a little bit nonsensical (please read that in a good sense!)

Re:Racket is 25 by jrumbut on Monday May 11, 2020 @12:58PM (#23144293)
You're right, although I think what I was actually wrong about was what minikanren is (probably by looking at examples in languages I was less familiar with).

I don't want to do logic programming in clojure (or whatever other language), I want to do logic programming in Prolog and drop it into my clojure project and have to do as little plumbing as possible.

The nearest analogy I can come up with is LINQ. In the examples in [1] the experience of the developer is closer to writing SQL in the middle of a C# or VB file, rather than having a good C# database query library.

[1] https://docs.microsoft.com/en-us/dotnet/csharp/programming-g...

Re:Racket is 25 by lizmat on Monday May 11, 2020 @05:20AM (#23140301)
Please note that Perl 6 has been renamed to Raku (https://raku.org using the #rakulang tag on social media). Although the emphasis has been on stability and efficiency improvements, some advances have been made in the concurrency department (react whenever) since then. And a project is now underway that will result in macros becoming first class citizens in Raku.
Re:Racket is 25 by sorawee on Monday May 11, 2020 @03:30AM (#23139708)
Re:Racket is 25 by andrewflnr on Sunday May 10, 2020 @06:33PM (#23136947)
There is probably a logic programming system for your favorite language. MiniKanren might not be exactly what you want, but even then there's probably something Prologgy too.
Re:Racket is 25 by still_grokking on Monday May 11, 2020 @07:00AM (#23140823)
Just to name two less know but interesting languages with that feature:

http://shenlanguage.org/

https://flix.dev/#/

Re:Racket is 25 by dragonwriter on Monday May 11, 2020 @09:22AM (#23141795)
> Something I would like to see is Prolog/logic programming embedded in a general purpose programming language, rather than as a standalone system.

That's super common, both as bridges to Prolog systems from other host languages and as implementations of Prolog semantics (or a subset like Datalog) as libraries in other host languages; also MiniKanren is available for many host languages.

Re:Racket is 25 by madhadron on Sunday May 10, 2020 @07:21PM (#23137240)
That's been a regular thing to do for many years. There are systems in Python, many systems for various Lisps, even a few in C++.
Re:Racket is 25 by zelphirkalt on Sunday May 10, 2020 @08:25PM (#23137623)
Have a look at minikanren : )
Re:Racket is 25 by nemoniac on Monday May 11, 2020 @11:44AM (#23143340)
Lisp has several. Screamer comes to mind.