emacsplist

Get the value from the plist if the key is in the variable


I'm writing code to generate the rss feed of my selfhost podcast and I need a function that would return a type depending on the file extension of podcast item.

For example, if the podcast file (item) has the extension mp4, I need to return the string video/mp4. This is needed to generate the tag <enclosure> as described in A Podcaster’s Guide to RSS.

This problem could be solved through a function to condition enumeration. For example,

(defun podcast-return-type (ext)
  "return type string for item file extension"
  (cond
   ((string= ext "m4a") (identity "audio/x-m4a"))
   ((string= ext "mp4") (identity "video/mp4"))
   ((string= ext "mp3") (identity "audio/mpeg"))
   ((string= ext "mov") (identity "video/quicktime"))
   ((string= ext "m4v") (identity "video/x-m4v"))
   ((string= ext "pdf") (identity "application/pdf"))
   (t (identity "not applyed"))))

But I wanted to solve it through a property list. I ran into a problem: I can't get the value if the key is stored in a variable.

(setq podcast-list '(mp4 "video/mp4" mp3 "audio/mpeg" pdf "application/pdf")) ;; key value
(setq ext (file-name-extension "~/podcast/podcast-item.mp4")) ---> "mp4"

(plist-get podcast-list ext) ----> nil
(plist-get podcast-list 'mp4) ----> "video/x-m4v"

How can I get the value of a plist if the key is in a variable? Or is there a more elegant way to solve this problem? I feel like the solution is somewhere on the surface, but I'm stuck.


Solution

  • That's because your keys are symbols and the value of ext is a string. Those are completely different types, so they never match. You can convert your string to a symbol by interning it:

    (plist-get podcast-list (intern ext))
    "video/mp4"
    

    You can also convert the keys to string, but then you need to specify a different comparison function in plist-get - it uses eq by default which will not work for strings:

    (setq podcast-list '("mp4" "video/mp4" "mp3" "audio/mpeg" "pdf" "application/pdf"))
    (setq ext (file-name-extension "~/podcast/podcast-item.mp4"))
    (plist-get podcast-list ext #'string=)
    "video/mp4"
    

    eq is much cheaper than string=, so most people would probably prefer the first version.


    EDIT: I was intrigued by the performance figures the OP posted in a comment, so I tried to reproduce them with this file:

    (defun podcast-return-type (ext)
      "return type string for item file extension"
      (cond
       ((string= ext "m4a") (identity "audio/x-m4a"))
       ((string= ext "mp4") (identity "video/mp4"))
       ((string= ext "mp3") (identity "audio/mpeg"))
       ((string= ext "mov") (identity "video/quicktime"))
       ((string= ext "m4v") (identity "video/x-m4v"))
       ((string= ext "pdf") (identity "application/pdf"))
       (t (identity "not applied"))))
    
    
    (setq podcast-list '(m4a "audio/x-m4a" mp4 "video/mp4" mp3 "audio/mpeg" mov "/video/quicktime" m4v "video/x-m4v" pdf "application/pdf"))
    
    
    (print (benchmark-run 100000 (podcast-return-type "mp4")))
    
    (print (benchmark-run 100000 (plist-get podcast-list (intern "mp4"))))
    
    (print (benchmark-run 100000 (podcast-return-type "pdf")))
    
    (print (benchmark-run 100000 (plist-get podcast-list (intern "pdf"))))
    
    

    Running it with emacs --batch --load test.el I got the following results:

    
    (0.026426384 0 0.0)              ;; function mp4
    
    (0.015171091000000001 0 0.0)    ;; plist-get mp4
    
    (0.043410812 0 0.0)               ;; function pdf
    
    (0.016731469 0 0.0)               ;; plist-get pdf
    

    I tried two keys: one near the beginning of the list and one near the end. The function shows a substantial slowdown between the two cases (almost a factor of 2), while the plist-get is about 7% slower in the second case.

    But in neither case, did the function beat plist-get: the function is slower by a factor of 1.5 in the first case and by 2.5 in the second case. Note I did 100000 repetitions of each.

    BTW, you can speed up your function a bit by getting rid of the unnecessary identity function calls:

    (defun podcast-return-type (ext)
      "return type string for item file extension"
      (cond
       ((string= ext "m4a") "audio/x-m4a")
       ((string= ext "mp4")  "video/mp4")
       ((string= ext "mp3")  "audio/mpeg")
       ((string= ext "mov")  "video/quicktime")
       ((string= ext "m4v")  "video/x-m4v")
       ((string= ext "pdf")  "application/pdf")
       (t "not applied")))
    

    but even so, the plist-get implementation is still faster:

    
    (0.022579204000000002 0 0.0)   ; function mp4
    
    (0.012907366 0 0.0)              ; plist-get mp4
    
    (0.038039411 0 0.0)              ; function pdf
    
    (0.014557089 0 0.0)              ; plist-get pdf