Giter Site home page Giter Site logo

suggest.el's Introduction

suggest.el

Build Status Coverage Status MELPA

suggest.el is an Emacs package for discovering elisp functions based on examples. You supply an example input and output, and it makes suggestions.

Interested readers may enjoy my blog posts:

suggest

Examples

suggest.el knows many string functions:

;; Inputs (one per line):
"foo bar"

;; Desired output:
"Foo Bar"

;; Suggestions:
(capitalize "foo bar") ;=> "Foo Bar"

suggest.el can also help you find tricky dash.el functions:

;; Inputs (one per line):
(list 'a 'b 'c 'd)
'c

;; Desired output:
2

;; Suggestions:
(-elem-index 'c (list 'a 'b 'c 'd)) ;=> 2

suggest.el is particularly handy for path manipulation, using both built-in functions as well as f.el:

;; Inputs (one per line):
"/foo/bar/baz.txt"

;; Desired output:
"baz.txt"

;; Suggestions:
(file-name-nondirectory "/foo/bar/baz.txt") ;=> "baz.txt"
(f-filename "/foo/bar/baz.txt") ;=> "baz.txt"

It can even suggest calling functions with apply:

;; Inputs (one per line):
'(1 2 3 4 5)

;; Desired output:
15

;; Suggestions:
(-sum '(1 2 3 4 5)) ;=> 15
(apply #'+ '(1 2 3 4 5)) ;=> 15

It can also suggest composing functions:

;; Inputs (one per line):
'(a b c)

;; Desired output:
'c

;; Suggestions:
(cadr (cdr '(a b c))) ;=> 'c
(car (last '(a b c))) ;=> 'c
(cl-third '(a b c)) ;=> 'c

It will also suggest additional arguments of basic values (0 in this example):

;; Inputs (one per line):
'(a b c d)

;; Desired output:
'a

;; Suggestions:
(elt '(a b c d) 0) ;=> 'a
(nth 0 '(a b c d)) ;=> 'a
(car '(a b c d)) ;=> 'a
(cl-first '(a b c d)) ;=> 'a
(-first-item '(a b c d)) ;=> 'a

How it works

suggest.el uses enumerative program synthesis. It tries your inputs (in any order) against every function in suggest-functions.

suggest-functions is a carefully chosen function list: they're all pure functions with a small number of arguments using only simple data types. We only include functions that users could 'stumble upon' with the right set of inputs.

Contributing

To work on the code, clone the repo, then you can eval suggest.el in your current Emacs instance.

To run the tests, you will need Cask. Once you've installed Cask, you can install the suggest.el dependencies and run the tests as follows:

$ cask
$ cask exec ert-runner

License

GPLv3.

Related projects

Program synthesis or inductive programming is a computer science research topic, with a range of tools taking very different approaches to generate code.

Smalltalk: Finder

This project was inspired by the Finder in Smalltalk, which does something similar. There's a great demo video here.

You give a list of values (inputs followed by an output):

3. 4. 7.

The Finder iterates over all possible calls (with arguments in any order) to a list of known safe messages, and returns suggestions:

3 + 4 --> 7
3 bitOr: 4 --> 7
3 bitXor: 4 --> 7
3 | 4 --> 7

This only returns single messages that meet the requirements, no nesting (e.g. it won't find '(data1 reversed asUppercase)' from 'foo'. 'OOF').

You can also supply multiple examples, using expressions as inputs:

MethodFinder methodFor: { {'29 Apr 1999' asDate}. 'Thursday'.
		{'30 Apr 1999' asDate}. 'Friday' }.

This returns '(data1 weekday)'.

Amusingly, it will sometimes find '(data1 shuffled)' from 'fo'. 'of'., which is a random sort.

Python: cant

There are some other niche tools that take other approaches. For example, cant for Python tries every function in scope (without a safety whitelist) to find functionality.

Scheme: barliman

barliman takes this idea of synthesis much, much further for Scheme. There's an incredible demo video here.

C-like: sketch

sketch allows the user to specify value placeholders in code (examples).

harness void doubleSketch(int x){
  int t = x * ??;
  assert t == x + x;
}

Generally you provide the structure of the code.

bit[W] firstLeadingZeroSketch (bit[W] x) implements firstLeadingZero {	
	return !(x + ??) & (x + ??); 
}

Or you can specify a set of operators for sketch to explore. Each line here is an assignment to x or tmp of an expression that may contain ! & + with x, tmp or a constant as arguments.

bit[W] firstLeadingZeroSketch (bit[W] x) implements firstLeadingZero {	
	bit[W] tmp=0;
       {| x | tmp |} = {| (!)?((x | tmp) (& | +) (x | tmp | ??)) |};
       {| x | tmp |} = {| (!)?((x | tmp) (& | +) (x | tmp | ??)) |};
       {| x | tmp |} = {| (!)?((x | tmp) (& | +) (x | tmp | ??)) |};
       return tmp;
}

Constraints are fed to a SAT solver, then sketch finds a solution. The output can be converted to C.

Liquid Haskell: Synquid

Synquid (the first half of this Microsoft Research talk gives a good overview) is a program synthesis tool leveraging refinement types.

The user provides the type of the function they want to generate, and a collection of 'components', the building blocks that Synquid tries to combine.

Synquid then lazily generates ASTs and type checks them. This allows it to prune the search tree by ignoring program structures that are never valid types.

(This is the extent of my understanding: I have probably oversimplified.)

Impressively, Synquid can generate recursive functions.

suggest.el's People

Contributors

dunn avatar hhnr avatar nickdrozd avatar syohex avatar tarsius avatar wilfred avatar xiongtx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

suggest.el's Issues

Try passing the function to mapcar

This would help us with the following:

;; Inputs (one per line):
"abc"

;; Desired output:
'(?a ?b ?c)

(mapcar #'identity my-input) would work here.

I think you know what the problem is just as well as I do.

;; Inputs (one per line):
"hal"

;; Desired output:
"ibm"

;; Suggestions:
(??? 1 "hal")

Wouldn't it be neat if suggest could come up with a function like ????

https://www.youtube.com/watch?v=qDrDUmuUBTo


Actually, Emacs does have a rot13 function, so I tried adding it to suggest-functions and running this:

;; Inputs (one per line):
"suggest"

;; Desired output:
"fhttrfg"

;; Suggestions:

I didn't get an answer. Instead, Emacs froze until I hit C-g. I checked *Messages* to see what had happened, and it was completely filled up with these:

Showing all blocks ... done
Showing all blocks ... done
Showing all blocks ... done
Showing all blocks ... done
Showing all blocks ... done
Showing all blocks ... done
Showing all blocks ... done
Showing all blocks ... done

Oh, and the suggest buffer had been wiped clean.

Now where, you ask, did that message come from? Hey, it's hs-show-all:

(defun hs-show-all ()
  "Show everything then run `hs-show-hook'.  See `run-hooks'."
  (interactive)
  (hs-life-goes-on
   (message "Showing all blocks ...")
   (let ((hs-allow-nesting nil))
     (hs-discard-overlays (point-min) (point-max)))
   (message "Showing all blocks ... done")
   (run-hooks 'hs-show-hook)))

Wait, why would rot13 trigger hs-show-all? Isn't it just a string manipulation function? No, it turns out that things are a little more complicated:

(defvar rot13-translate-table
   (let ((str (make-string 127 0))
         (i 0))
    (while (< i 127)n
      (aset str i i)
      (setq i (1+ i)))
    (setq i 0)
    (while (< i 26)
      (aset str (+ i ?a) (+ (% (+ i 13) 26) ?a))
      (aset str (+ i ?A) (+ (% (+ i 13) 26) ?A))
      (setq i (1+ i)))
    str)
  "String table for ROT13 translation.")

;;;###autoload
(defun rot13 (object &optional start end)
  "Return ROT13 encryption of OBJECT, a buffer or string."
  (if (bufferp object)
      (with-current-buffer object
	(rot13-region start end))
    (rot13-string object)))

;;;###autoload
(defun rot13-string (string)
  "Return ROT13 encryption of STRING."
  (with-temp-buffer
    (insert string)
    (rot13-region (point-min) (point-max))
    (buffer-string)))

;;;###autoload
(defun rot13-region (start end)
  "ROT13 encrypt the region between START and END in current buffer."
  (interactive "r")
  (translate-region start end rot13-translate-table))

rot13 calls rot13-string, which creates a temporary buffer with just the string inserted into it. rot13-string then passes the entire contents of the buffer (as min and max points) to rot13-region, which mutates the string in the buffer according to ROT13, and then returns the whole contents of the buffer as a string.

If you're anything like me, you looked at that code and thought: What the shit is this? Why does the simple string function call the elaborate buffer-mutating function as a subroutine rather than the other way around? What kind of idiot would design a package like this?

In fact, nobody designed the package like that. Way back in 1992 (and presumably as early as 1988), rot13.el didn't even have the rot13 function. The only function it had was rot13-other-window, which causes another window to display the ROT13 contents of the current window's buffer. (Note that this ROT13-display is a condition of the window rather than the buffer, and it apparently cannot be undone except by deleting the window or calling toggle-rot13-mode.) Indeed, the package is a self-described "hack" designed "mainly to show off the char table stuff".

Is there a moral to this story? Maybe we shouldn't judge the quality of code without knowing its etiology.


By the way, the manual and the source code commentary employ strikingly different examples to illustrate a common use of ROT13. According to the manual,

Mailing list messages that might offend or annoy some readers are
sometimes encoded in a simple code called “rot13”—so named because it
rotates the alphabet by 13 letters.  This code is not for secrecy, as it
provides none; rather, it enables those who wish to to avoid seeing the
real text of the message.  For example, a review of a film might use
rot13 to hide important plot points.

whereas the source code says that

;; ROT13 encryption is sometimes used on USENET as a read-at-your-
;; own-risk wrapper for material some might consider offensive, such as
;; ethnic humor.

number-sequence is no longer suggested

We should see this:

;; Inputs (one per line):
4

;; Desired output:
'(1 2 3 4)

;; Suggestions:
(number-sequence 1 4) ;=> '(1 2 3 4)

but we are not getting that suggestion. I suspect number-sequence is so far down in the list that we hit the intermediate value cap too early.

Prefer function calls to apply

;; Inputs (one per line):
2
3

;; Desired output:
8

;; Suggestions:
(expt 2 3) ;=> 8
(apply #'expt (list 2 3)) ;=> 8
(apply #'* (-repeat 3 2)) ;=> 8
(apply #'* (make-list 3 2)) ;=> 8
(-product (-repeat 3 2)) ;=> 8

Currently we suggest (apply f (list arg1 arg2)) too frequently when we already have (f arg1 arg2).

Undefined reference to it.

I downloaded the mode today from melpa and tried running it, but unfortunately I get undefined reference to the "it" variable. The version of suggest is 20160807.1830 and my emacs version is 24.4.1.

Add builtins-only flag

Sometimes you can't or don't want to use functions from f, s, etc. In those cases it would be nice to be able to restrict suggest output to just builtin Emacs functions.

Consider passing common values to functions

Given this:

;; Inputs (one per line):
'(a b c d)

;; Desired output:
'b

it would be nice to suggest (nth 1 '(a b c d)), but this requires us passing extra values to functions. 0, 1 and 2 are the obvious candidates.

This will also help with discovering (-partition 2 some-list).

suggest--possibilities: Symbol’s value as variable is void: it

Just installed suggest.el from MALPA. Ran M-x suggest. The suggest buffer comes up with the default inputs but no suggestions and the error message:

suggest--possibilities: Symbol’s value as variable is void: it

Could there be an undeclared dependency?

Don't call f-entries with tramp paths

If a user specifies e.g. "/scp:foo:bar" then f-entries will try to connect to a remote host.

Update suggest--safe to exclude this. See tramp-dissect-file-name for an example showing how to detect tramp paths.

Related: #32.

Example from readme segfaults Emacs

When I try this example

;; Inputs (one per line):
"foo bar"

;; Desired output:
"Foo Bar"

;; Suggestions:
(capitalize "foo bar") ;=> "Foo Bar"

it crashes my emacs. Also many other examples involving strings seem to do so. Is there something like unbound recursion or maybe I overflow the stack? I don't know if it is possible to control that somehow.

My max-lisp-eval-depth was set to 1000, version GNU Emacs 24.5.1 (x86_64-unknown-linux-gnu)

Rising it to 10000 or even 100000 makes no difference.

Why can't programmers tell the difference between Christmas and Halloween?

Because DEC 25 is OCT 31.

suggest doesn't suffer from this problem though:

  ;; Inputs (one per line):
  25

  ;; Desired output:
  "31"

  ;; Suggestions:
  ;; No matches found.

What I'd like to get back is something like

  (format "%o" 25)

Stringifying a number is no problem, so I take it the issue is just the radix conversion.

  ;; Inputs (one per line):
  25

  ;; Desired output:
  "25"

  ;; Suggestions:
  (number-to-string 25) ;=> "25"
  (apply #'number-to-string (list 25)) ;=> "25"
  (apply #'number-to-string (cons 25 nil)) ;=> "25"
  (apply #'number-to-string (make-list 1 25)) ;=> "25"
  (number-to-string (car (-cons* 25 2))) ;=> "25"

It turns out that suggest doesn't "know about" format; that is, format isn't included in suggest-functions. I add it.

  (push #'format suggest-functions)

I still don't get the result I want, so I try adding a more specialized function:

  (defun octalize (n)
    (format "%o" n))

  (push #'octalize suggest-functions)

This time I get what I want:

  ;; Inputs (one per line):
  25

  ;; Desired output:
  "31"

  ;; Suggestions:
  (octalize 25) ;=> "31"
  (apply #'octalize (list 25)) ;=> "31"
  (octalize (float 25)) ;=> "31"
  (octalize (ffloor 25)) ;=> "31"
  (octalize (fceiling 25)) ;=> "31"

If I run it again, however, I get slightly worse results:

  ;; Inputs (one per line):
  25

  ;; Desired output:
  "31"

  ;; Suggestions:
  (apply #'octalize (list 25)) ;=> "31"
  (octalize (float 25)) ;=> "31"
  (octalize (ffloor 25)) ;=> "31"
  (octalize (fceiling 25)) ;=> "31"

In fact, if I run some other query, then run [25] => "31" again (is there a better notation for this?), then run it again, I get the same results: (octalize 25) is included the first run, but not in subsequent runs.

I guess this issue really has two issues.

  1. I think radix conversion is common, simple, and "discoverable" enough to warrant inclusion in suggest-functions. I don't know exactly what the right function to use is; I've tried using functions from calc, but to no avail. There must be an appropriate library somewhere.

  2. Different results from one run to the next. I've seen this behavior with other functions (e.g. [100] => 10), so I don't think it has anything to do with octalize being a "user-defined" function.

Incorrect formatting of characters

;; Inputs (one per line):
"ab"

;; Desired output:
'(?a ?b)

;; Suggestions:
(string-to-list "ab") ;=> '(97 98)

We shouldn't show char codes here.

tries to fetch url with tramp(?)

Try putting an url in the input/output (as string) and pressing C-c C-c:

suggest-url

This seems like a bug :)

;; Version: 0.4
;; Package-Version: 0.4

Fix warnings on Emacs 30 related to case 'match and 'different

I am on the latest Emacs master and see those warnings. Can they be fixed?

../suggest-20231003.404/suggest.el: Warning: Case 'match will match ‘quote’.  If that’s intended, write (match quote) instead.  Otherwise, don’t quote ‘match’.
../suggest-20231003.404/suggest.el: Warning: Case 'different will match ‘quote’.  If that’s intended, write (different quote) instead.  Otherwise, don’t quote ‘different’.

tag for MELPA stable?

If you don't yet consider this package stable, then close and ignore 🙈

But if it is, it'd be great to have a tag so that MELPA stable picks up on it and offers it through that channel.

Suggest mapping function too

E.g. it would be nice to do:

;; Inputs
'((a . 1) (b . 2))

;; Output
'(a b)

;; Figure out
(mapcar #'cons input-here)

accept multiple sets of inputs/output

It would be neat if I could specify multiple sets of inputs and outputs and get suggestions that satisfy all of them. For example if I am looking for an expression to check if a string starts with a substring I could specify:

"f" "foo" => t
"f" "barf" => nil
"b" "bar" => t

nil and t

;; Inputs (one per line):
nil

;; Desired output:
t

When I run that query, Emacs hangs without notice. Nothing happens, and even the cursor stops blinking, until I C-g quit. After that, things return to normal after about a second.

Similar queries don't have this problem (though the results aren't the most insightful):

;; Inputs (one per line):
t

;; Desired output:
nil

;; Suggestions:
(-zip t nil) ;=> nil
(remq t nil) ;=> nil
(last t -1) ;=> nil
(assoc t nil) ;=> nil
(-take -1 t) ;=> nil
;; Inputs (one per line):
t

;; Desired output:
t

;; Suggestions:
(last t) ;=> t
(-cons* t) ;=> t
(append t) ;=> t
(-concat t) ;=> t
(identity t) ;=> t
;; Inputs (one per line):
nil

;; Desired output:
nil

;; Suggestions:
(cdr nil) ;=> nil
(car nil) ;=> nil
(last nil) ;=> nil
(cdar nil) ;=> nil
(cadr nil) ;=> nil

The weird hang aside, support for booleans isn't great. I'm going to open a PR to add some boolean functions.

Don't pass all arguments at once

Given this:

;; Inputs (one per line):
(list 'abc :foo 'def)
:foo

;; Desired output:
'def

It would be nice to suggest (plist-get (cdr (list 'abc :foo 'def)) :foo). This requires passing :foo later.

Please add a prefix to test-helper.el to avoid conflicts with 68 other packages

There exist at least 69 packages that contain a file named test-helper.el that also provides the feature test-helper.

This leads to issues for users who have at least two of these packages installed. It is unlikely that such a user would be able to run the tests of all of those packages. If the primary test file of one of those packages does (require 'test-helper), then it is undefined which of the various test-helper.el files gets loaded. Which it is, depends on the order of the load-path.

To avoid this conflicts, you should rename your test-helper.el to <your-package>-test-helper.el and adjust the feature and symbol prefixes accordingly.

Also don't forget to update the require form in your primary test file and/or update references to the library/feature elsewhere. Also, if your primary test file is named something like test.el, then please consider renaming that too (same for any other utility elisp files your repositoroy may contain).

Thanks!

PS: This issue is a bit generic because I had to open 69 issues.

Add support for seq.el

Hello, what an intriguing package.

Personally I want to see recommendations from the seq.el library as well.

To start this off I am looking over each function and comparing the results to the default suggest.el configuration. The following is an in-progress skeleton.

(use-package suggest
  :config
  (setq suggest-functions-addl
        (list
         #'seq-doseq
         ;; #'seq
         ;; #'seq-let
         #'seq-elt
         #'seq-length
         ;; #'seq-do 
         #'seqp
         ;; #'seq-copy
         #'seq-subseq
         ;;;below #'seq-map
         ;;;below #'seq-mapn
         #'seq-drop
         #'seq-take
         #'seq-drop-while
         #'seq-take-while
         #'seq-empty-p
         #'seq-sort
         #'seq-reverse
         #'seq-concatenate
         #'seq-into-sequence
         #'seq-into
         #'seq-filter
         #'seq-remove
         #'seq-reduce
         #'seq-every-p
         #'seq-some
         #'seq-find
         #'seq-count
         #'seq-contains
         #'seq-set-equal-p
         #'seq-position
         #'seq-uniq
         #'seq-mapcat
         #'seq-partition
         #'seq-intersection
         #'seq-difference
         #'seq-group-by
         #'seq-min
         #'seq-max
         ;; seq-random-elt
         #'seq-min
         #'seq-max
         #'seq-position
         ))
  (setq suggest-functions-orig
        (or (bound-and-true-p suggest-functions-orig)
            (copy-list suggest-functions)))
  (setq suggest-functions (copy-list suggest-functions-orig))
  ;;
  (setq suggest-funcall-functions-addl
        (list
         #'seq-map
         #'seq-mapn
         ))
  (setq suggest-funcall-functions-orig
        (or (bound-and-true-p suggest-funcall-functions-orig)
            (copy-list suggest-funcall-functions)))
  (setq suggest-funcall-functions (copy-list suggest-funcall-functions-orig))
  ;;
  (suggest-activate-func-adds)
  )

(defun suggest-reset-func-adds ()
  ""
  (interactive)
  (setq
   suggest-functions suggest-functions-orig
   suggest-funcall-functions suggest-funcall-functions-orig))

(defun suggest-activate-func-adds ()
  ""
  (interactive)
  (setq suggest-functions (append suggest-functions-orig
                                  suggest-functions-addl)
        suggest-funcall-functions (append suggest-funcall-functions-orig
                                          suggest-funcall-functions-addl)))

Before (without seq.el):

;; Inputs (one per line):
(vector 1 2 3 6 5)
2
4


;; Desired output:
(vector 3 6)

;; Suggestions:

(substring (vector 1 2 3 6 5) 2 4) ;=> [3 6]
(apply #'substring (list (vector 1 2 3 6 5) 2 4)) ;=> [3 6]
(apply #'substring (-cons* (vector 1 2 3 6 5) 2 4 nil)) ;=> [3 6]
(apply #'substring (cl-list* (vector 1 2 3 6 5) 2 4 nil)) ;=> [3 6] 3 6 5) 2 4 nil)) ;=> [3 6]

After (with seq.el):

;; Inputs (one per line):
(vector 1 2 3 6 5)
2
4


;; Desired output:
(vector 3 6)

;; Suggestions:

(seq-subseq (vector 1 2 3 6 5) 2 4) ;=> [3 6]
(apply #'substring (list (vector 1 2 3 6 5) 2 4)) ;=> [3 6]
(apply #'seq-subseq (list (vector 1 2 3 6 5) 2 4)) ;=> [3 6]
(apply #'substring (-cons* (vector 1 2 3 6 5) 2 4 nil)) ;=> [3 6]

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.