Haskell-style sections in Common Lisp
Asked Answered
H

7

9

In Haskell, if I have a lambda that looks like the following

(\x -> doStuff x y)

where y is from the surrounding scope, I could section it and turn it into

(`doStuff` y)

which is shorter and more concise (and one of the things I love the most about Haskell).

Now, in Common Lisp I would write the equivalent code as

(lambda (x) (do-stuff x y))

And this is actually a very common thing for me to be writing, but I feel even that tiny bit of boilerplate bothers me somewhat, so I wonder if there is a way to get something like the Haskell-style sections in Common Lisp?

Howell answered 22/3, 2013 at 10:57 Comment(3)
Well, I meant concise as in brief... I'm still fairly new to CL though and would have no idea where to begin, I've only touched the very basics of macros. @downvoter - care to comment?Howell
@wvxvw I do not understand the point of the first part of your comment. Can it not be assumed that if OP uses a subjective descriptor such as 'concise' that he or she is stating an opinion?Temuco
Regarding things which "...can be routinely showed...", I would be interested in a citation if you had one. Moreover it occurs to me that which one of your examples is more concise likely depends on the background of the audience. If a person has no math background whatsoever, I suspect the first example is more quickly and easily understood. Apart from all that, it is my opinion that this level of pedantry is, on balance, harmful to the function and purpose of this forum.Temuco
J
11

Unless you are more experienced, I would propose that you learn to write Lisp in Lisp, not how to write Haskell in Lisp. The latter is not a good idea. Haskell works very different.

Lisp does not do any 'currying' (or schönfinkeling ;-) ).

You can write it as:

CL-USER 5 > (defun curry (fn arg) (lambda (&rest args) (apply fn arg args))) 
CURRY

CL-USER 6 > (mapcar (curry #'expt 2) '(2 3 4 5 6))
(4 8 16 32 64)

It costs a bit efficiency that way, though.

CL-USER 7 > (mapcar (lambda (base) (expt base 2)) '(2 3 4 5 6))
(4 8 16 32 64)

I personally prefer the latter, because I have a real readable name for the variable. This helps in a debugger, where I see then a backtrace. Tools like these are probably more important in Lisp, than in Haskell.

CL-USER 12 > (mapcar (lambda (base) (expt base 2)) '(2 3 "four" 5 6))

error. Let's look at the backtrace:

CL-USER 12 : 1 > :bb
...

Condition: In EXPT of ("four" 2) arguments should be of type NUMBER.

Call to SYSTEM::ARGS-TO-BINARY-ARITHMETIC-FN-NOT-OF-TYPE {offset 189}
  SYSTEM::FN-NAME : EXPT
  SYSTEM::ARG1    : "four"
  SYSTEM::ARG2    : 2
  TYPE  {Closing} : NUMBER

Interpreted call to (SUBFUNCTION :ANONYMOUS SYSTEM::ANONYMOUS-LAMBDA):
  BASE : "four"

Now I can see that the thing has a name. I was passing the string "four" to the function with a variable named base.

Interactive development with REPL and debugging tools is common. Best prepare the code to be useful for this development style. Common Lisp is not optimized to provide full program compilers with extensive type checking - like in Haskell.

One of the main problems of Lisp is that it can be very hard to find out what a piece of code really does. The default (strict functional programs with prefix syntax) is relatively easy to understand. But there are many possibilities to change the meaning of code in Lisp (macros, read macros, symbol macros, the Meta Object protocol, advising, ...).

First rule: if you are writing basic Lisp code, stick with the basic syntactic and semantic possibilities. Write defensively. Expect that someone else needs to understand the code. For that the code should be readable, easy to understand, use common idioms and it should be debuggable.

In Haskell many people with math background want to write code in a very compact way with a high level of abstraction. You can do that in Lisp, too. But for ordinary code I would not go that route and for larger pieces of code, Lisp often uses other mechanisms (code transformations via macros, ...).

Janise answered 22/3, 2013 at 11:41 Comment(2)
Thanks - though I'm most definitely not trying to learn how to write Haskell in Lisp, I just don't want to be one of those who give up on a language or end up writing bad/convention-defying code thinking that "it doesn't even do x" while it actually does do x and I just wasn't aware of it.Howell
Three things (two of which are nitpicks). First, your curry doesn't solve the problem; what's described is not just currying, but a syntactic feature (applying a function to the second argument). Second, most of my Haskell development takes place at a REPL, and I think that's typical; on the other hand, while GHCi (the standard compiler's REPL) has a built-in debugger, I've barely used it, and I think that's typical too (but less so). Thirdly, I don't think a "math background" has anything to do with whether you find \x -> doStuff x y, flip doStuff y, or (`doStuff` y) more readable.Kampala
M
9

You can develop arbitrary special syntax for such forms. There're multiple variants. I, for instance, use a Clojure-inspired sharp-backquote syntax. Using it, your form will look like this:

#`(do-stuff % y)
Monarchism answered 22/3, 2013 at 11:38 Comment(0)
D
5

I don't think you can do it directly, but...

If you know that you always want to do something that is equivalent to (lambda (x) (fun x lexical)) and simply want a shorter way of expressing that, you could, in theory, use a macro.

I would, personally, advise against doing so, (lambda (x) (fun x lex)) doesn't take much typing and removes one layer of obscurity from your code. But if it is a pattern that is sufficiently common that it warrants special handling, something like the following might do:

(defmacro section (function lexical)
   (let ((sym (gensym))
     `(lambda (,sym) (,function ,sym ,lexical))))

That makes the Haskell section:

(`doStuff` y)

become the Common Lisp section:

(section dostuff y)

I don't, as such, find it more readable, at least in the short, but if it was something that I did see again and again, I would indeed consider (and have done, more for experimental purposes than anything else) a macro to make it quicker (I have a half-baked macro, somewhere, that allows you to do things like (_ func _2 lexical _1) -> *(lambda (a b) (func b lexical a)) and that is sometimes handy, but doesn't really improve readability).

Diagnosis answered 22/3, 2013 at 11:10 Comment(0)
M
3

There's a sharp backquote read macro in Let Over Lambda that could work for this case:

CL-USER>
(print
  '#`,(+ a1 y))

(LAMBDA (A1) (+ A1 Y))
(LAMBDA (A1) (+ A1 Y))

CL-USER>
(let ((y 2))
  (mapcar #`,(+ a1 y)
          (list 1 2 3 4)))
(3 4 5 6)
CL-USER>

This approach is very similar to the technique mentioned by @Vsevolod Dyomkin. Hoyte's version does have a few extra features, like building a lambda with any number of arguments. On the other hand, it's a bit harder to parse, because it's being expressed at one level higher notation, where in order to eval a form you have to unquote the backquote (using ',' in this example).

Myrtice answered 24/3, 2013 at 7:41 Comment(0)
C
3

Scheme has the cut macro (in SRFI-26), which allows you to specify holes in a procedure call with <>. For example:

(cut doStuff <> y)  ;; same as (lambda (x) (doStuff x y))
(cut - 5 <> 6 <> 7) ;; same as (lambda (x y) (- 5 x 6 y 7))

You could probably define something similar in CL.

Cleft answered 25/3, 2013 at 13:5 Comment(0)
P
2

I also missed the ease of Haskell-style function currying and composition when in common lisp. As a result I wrote the following package which defines reader macros for concise curry and composition in lisp (it uses the alexandria functions).

http://eschulte.github.io/curry-compose-reader-macros/

With this package (mapcar (compose (curry #'* 2) (curry #'+ 1)) (list 1 2 3 4)) becomes (mapcar [{* 2} {+ 1}] (list 1 2 3 4)). I now use this in nearly all of my CL projects and find it greatly reduces code size and increases readability.

Pulverable answered 20/8, 2013 at 23:13 Comment(0)
T
1

The alexandria package exports the symbols curry and rcurry. So in your case you just do (alexandria:rcurry function arg) e.g., (rcurry #'do-staff y).

Rcurry and curry return functions so you need to funcall the result as usual.

Terrence answered 1/4, 2013 at 10:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.