bakpakin / Fennel

Lua Lisp Language
https://fennel-lang.org
MIT License
2.43k stars 125 forks source link

Calling return from inside a loop #272

Closed dandevs closed 4 years ago

dandevs commented 4 years ago

All return are culled inside loops, but it would be great if it were possible to use it somehow. Something like (values ...) could be used to override the behavior and allow a return? (Or maybe I missed this functionality in the documentations?)

(fn first-index-of [elem list]
  (each [i v (ipairs list)]
    (when (= elem v) (values i))))
local function first_index_of(elem, list)
  for i, v in ipairs(list) do
    if (elem == v) then
      -- return here please
    end
  end
  return nil
end
thatismatt commented 4 years ago

Here's an alternative (more lispy) way to do this with a recursive function, removing the need for early return from the each loop:

(fn first-index-of
  [elem list index]
  (let [(i v) (next list index)]
    (if (= nil i)  nil
        (= elem v) i
        :otherwise (first-index-of elem list i))))

(first-index-of :b [:a :b :c]) ;; => 2

(first-index-of :b {:x :a :y :b :z :c}) ;; => :y

(first-index-of :d [:a :b :c]) ;; => nil
dandevs commented 4 years ago

Unless Fennel wants to enforce a more functional approach, I think the flexibility of being able to return early would be great. In the case of first-index-of the for loop is just much more concise than a recursive approach.

technomancy commented 4 years ago

It doesn't really have as much to do with functional programming as being a lisp; this is just how lisps work. One of the benefits of lisp is regularity; you can read a function and know that it's only going to return from a tail position; and being able to make assumptions like that without reading every single line of the function is very beneficial.

However, there is an escape hatch for those cases where you need it, either for very pressing performance reasons (LuaJIT can optimize the recursive version into the equivalent of how it would run with early returns but PUC Lua cannot) or because you're porting a piece of Lua code line-by-line and just want to get it working first before making it idiomatic:

(fn first-index-of [elem list]
  (each [i v (ipairs list)]
    (when (= elem v)
      (lua "return i"))))
technomancy commented 4 years ago

Also here's a more concise version of the recursive approach; it's the same line count as the early-return approach if it hadn't omitted the newline after the when.

(fn first-index-of [elem list index]
  (match (next list index)
    (i elem) i
    i (first-index-of elem list i)))