Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider implementing loops #5

Open
vks opened this issue Feb 10, 2016 · 12 comments
Open

Consider implementing loops #5

vks opened this issue Feb 10, 2016 · 12 comments

Comments

@vks
Copy link
Contributor

vks commented Feb 10, 2016

As far as I know, it is currently not possible to implement loops without resorting to recursion, which is limited by the stack size. It would be useful to have efficient loops in the language.

@murarth
Copy link
Owner

murarth commented Feb 10, 2016

Loops are based on mutable state. While mutable state technically exists in the interpreter, it's not available to Ketos code. I don't see how loops could work.

However, the interpreter does implement tail recursion optimization, allowing infinite recursion without filling up the stack. This definitely should have been mentioned in the docs and I'll write something up about it straight away.

@hauleth
Copy link

hauleth commented Feb 10, 2016

👍 for TCO instead of loops. After that it would be nice to have macros like loop that would simplify that, but with TCO.

@vks
Copy link
Contributor Author

vks commented Feb 10, 2016

Under which conditions is TCO guaranteed? The factorial example blows up the stack rather quickly...

@murarth
Copy link
Owner

murarth commented Feb 10, 2016

The code in examples/calling.rs was intended to be simple; not necessarily optimal. I didn't want to complicate it with a taill-call-optimizable implementation. Perhaps it would be better replaced with a naive implementation of something else that doesn't involve recursion at all.

In low-level terms, tail call optimization is guaranteed any place where a recursive call occurs immediately before a Return instruction. That's the way the bytecode compiler generates a TailCall instruction.

In high-level terms, any recursive function call in a tail position should become a tail call. If it doesn't, it's a bug. Note that this only applies to recursive calls.

@qthree
Copy link

qthree commented Mar 16, 2016

I don't see how loops could work

Easy!

extern crate ketos;
use ketos::Interpreter;

fn main() {
    let interp = Interpreter::new();

    let _out = interp.run_code(r#"

(define (for from to body)
  (if
    (< from to) (do (body from) (for (+ from 1) to body))
  )
)
(define (hello x) (println (format "Hello ~d" x)))
(for 0 5 hello)
(for 0 5 (lambda (x) (println (format "World ~d" x)) ))

        "#, None);
}

@murarth
Copy link
Owner

murarth commented Mar 16, 2016

@qthree: It looks like a loop (kind of), but there's no mutable state being modified in the body. That's what I was saying wouldn't work. Local bindings can't be modified. Maybe you could do something crazy with define, but... please don't.

@hauleth
Copy link

hauleth commented Mar 17, 2016

But that is how loops In Lisps works, almost. In Scheme there are loop macros which translates almost to that (they use let to not pollute global namespace).

Łukasz Jan Niemier

Dnia 16 mar 2016 o godz. 05:27 Murarth [email protected] napisał(a):

@qthree: It looks like a loop (kind of), but there's no mutable state being modified in the body. That's what I was saying wouldn't work. Local bindings can't be modified. Maybe you could do something crazy with define, but... please don't.


You are receiving this because you commented.
Reply to this email directly or view it on GitHub

@ghost
Copy link

ghost commented Dec 30, 2016

I really don't think loops are necessary. However, I think a lot of imperative programmers find comfort in having some kind of list comprehension syntax, sort of like Elixir's for.

@murarth
Copy link
Owner

murarth commented Dec 30, 2016

List comprehension and perhaps other styles of loop could be implemented via macro.

@hauleth
Copy link

hauleth commented Dec 30, 2016

@murarth alternatively you could provide only general loop with break and implement rest using macros.

@murarth
Copy link
Owner

murarth commented Dec 30, 2016

@hauleth: It's conceivable, yes, but I don't believe it would be of much use without the ability to mutate values and/or bindings.

In Common Lisp implementations, values can be mutated. Ketos, however, uses a model of shared ownership and copy-on-write. (Some Value variants contain an Rc.) It would be possible to implement an operator that assigns a new value to a local let binding, but I think that a half-hearted mutation operator of this kind would only cause confusion because of the general absence of mutation facilities.

Without general mutation, imperative programming styles are less effective and a loop construct with break is, in my opinion, not significantly more useful than a tail-recursive function. If I'm wrong, please provide some hypothetical code examples for things that can't easily be done without looping and break.

@vklquevs
Copy link

For what it's worth, I think a construct that implicitly repeats a block of code (unless you tell it not to) is less intuitive than a construct that executes a block of code once (unless you tell it to execute again). Really, loop-with-break is functionally identical to run-with-rerun, just with the logic inverted.

Fixed-point combinators let you write loops without explicitly using recursion, which should keep everyone happy (?):

ketos=> (define (fix f v) (f (lambda (w) (fix f w)) v))
fix
ketos=> (fix (lambda (fact n) (if (< (first n) 1) (second n) (fact (list (- (first n) 1) (* (second n) (first n)))))) (list 50 1))
30414093201713378043612608166064768844377641568960512000000000000

(This example blows up the call stack at around 350 or so for me, but you get the idea.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants