Search code examples
common-lispseriessequenceslazy-sequences

using Generic sequences system in common lisp with an alist from jonathan


I'm working with the messenger api from facebook, using ningle. there is a moment in my program that I need to work with this alist coming from jonathan:

CL-USER> (defparameter *params*
           '(("entry"
              (("messaging"
                (("message" ("text" . "hola") ("seq" . 3227)
                  ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u"))
                 ("timestamp" . 1496071517968)
                 ("recipient" ("id" . "474086316315717"))
                 ("sender" ("id" . "1695095647186162"))))
               ("time" . 1496071518212) ("id" . "474086316315717")))
             ("object" . "page")))

*PARAMS*
CL-USER> (length *params*)
2

Then I have to work with the entry part:

CL-USER> (defparameter entries (cdr (assoc "entry" *params* :test #'string=)))
ENTRIES
CL-USER> entries
((("messaging"
   (("message" ("text" . "hola") ("seq" . 3227)
     ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u"))
    ("timestamp" . 1496071517968) ("recipient" ("id" . "474086316315717"))
    ("sender" ("id" . "1695095647186162"))))
  ("time" . 1496071518212) ("id" . "474086316315717")))
CL-USER> (length entries)
1

Then I define two functions for working with this:

(defun extract-entry (entry)
  (let ((id (cdr (assoc "id" entry :test #'string=)))
        (time (cdr (assoc "time" entry :test #'string=)))
        (messaging (cdr (assoc "messaging" entry :test #'string=))))
    messaging))


(defun extract-messaging (event)
  (let ((message (cdr (assoc "message" event :test #'string=))))
    message))

Then I operate:

CL-USER> (defparameter messaging (extract-entry (first entries)))
MESSAGING
CL-USER> messaging
((("message" ("text" . "hola") ("seq" . 3227)
   ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u"))
  ("timestamp" . 1496071517968) ("recipient" ("id" . "474086316315717"))
  ("sender" ("id" . "1695095647186162"))))
CL-USER> (length messaging)
1


CL-USER> (defparameter message (extract-messaging (first messaging)))
MESSAGE
CL-USER> message
(("text" . "hola") ("seq" . 3227)
 ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u"))
CL-USER> (length message)
3

And it works well, then I want to apply generic-sequences to this parts:

CL-USER> (gen-seq:seq->list (gen-seq:seq-map #'extract-messaging messaging))
((("text" . "hola") ("seq" . 3227)
  ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u")))

CL-USER> (gen-seq:seq->list (gen-seq:seq-map #'extract-entry entries))
(((("message" ("text" . "hola") ("seq" . 3227)
    ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u"))
   ("timestamp" . 1496071517968) ("recipient" ("id" . "474086316315717"))
   ("sender" ("id" . "1695095647186162")))))

and it work but when I concatenate the functions I get this problem:

CL-USER> (trace extract-entry extract-messaging)
(EXTRACT-ENTRY EXTRACT-MESSAGING)


CL-USER> (gen-seq:seq->list (gen-seq:seq-map #'extract-messaging (gen-seq:seq-map #'extract-entry entries)))
  0: (EXTRACT-ENTRY
      (("messaging"
        (("message" ("text" . "hola") ("seq" . 3227)
          ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u"))
         ("timestamp" . 1496071517968) ("recipient" ("id" . "474086316315717"))
         ("sender" ("id" . "1695095647186162"))))
       ("time" . 1496071518212) ("id" . "474086316315717")))
  0: EXTRACT-ENTRY returned
       ((("message" ("text" . "hola") ("seq" . 3227)
          ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u"))
         ("timestamp" . 1496071517968) ("recipient" ("id" . "474086316315717"))
         ("sender" ("id" . "1695095647186162"))))
  0: (EXTRACT-MESSAGING
      ((("message" ("text" . "hola") ("seq" . 3227)
         ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u"))
        ("timestamp" . 1496071517968) ("recipient" ("id" . "474086316315717"))
        ("sender" ("id" . "1695095647186162")))))
; Evaluation aborted on #<TYPE-ERROR expected-type:
             "(OR (VECTOR CHARACTER) (VECTOR NIL) BASE-STRING SYMBOL CHARACTER)"
             datum: ("message" ("text" . "hola") ("seq" . 3227) ..)>.

with this output in the debugger:

The value
  ("message" ("text" . "hola") ("seq" . 3227)
   ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u"))

is not of type
  (OR (VECTOR CHARACTER) (VECTOR NIL) BASE-STRING SYMBOL
      CHARACTER)

when binding SB-IMPL::STRING2
   [Condition of type TYPE-ERROR]

Restarts:
 0: [*ABORT] Return to SLIME's top level.
 1: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING {1003180003}>)

Backtrace:
  0: (STRING= "message" ("message" ("text" . "hola") ("seq" . 3227) ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u"))) [more]
  1: (SB-KERNEL:%ASSOC-TEST "message" #<unavailable argument> #<FUNCTION STRING=>)
  2: (EXTRACT-MESSAGING ((("message" # # #) ("timestamp" . 1496071517968) ("recipient" #) ("sender" #))))
  3: (SB-DEBUG::TRACE-CALL #<SB-DEBUG::TRACE-INFO EXTRACT-MESSAGING> #<FUNCTION EXTRACT-MESSAGING> ((("message" # # #) ("timestamp" . 1496071517968) ("recipient" #) ("sender" #))))
  4: ((LABELS GENERIC-SEQ::TRAVERSE :IN GENERIC-SEQ::SEQ-MAP-1) (((# # # #)) . #<CLOSURE (LAMBDA NIL :IN GENERIC-SEQ::SEQ-MAP-1) {1004CF1F5B}>))
  5: (GENERIC-SEQ:SEQ->LIST #S(GENERIC-SEQ::BASIC-SEQ :DELAYED-ENUM #<CLOSURE (LAMBDA NIL :IN GENERIC-SEQ::SEQ-MAP-1) {1004C9A46B}>))
  6: (SB-INT:SIMPLE-EVAL-IN-LEXENV (GENERIC-SEQ:SEQ->LIST (GENERIC-SEQ:SEQ-MAP (FUNCTION EXTRACT-MESSAGING) (GENERIC-SEQ:SEQ-MAP # ENTRIES))) #<NULL-LEXENV>)
  7: (EVAL (GENERIC-SEQ:SEQ->LIST (GENERIC-SEQ:SEQ-MAP (FUNCTION EXTRACT-MESSAGING) (GENERIC-SEQ:SEQ-MAP # ENTRIES))))
  8: (SWANK::%EVAL-REGION "(gen-seq:seq->list (gen-seq:seq-map #'extract-messaging (gen-seq:seq-map #'extract-entry entries))) ..)
  9: ((LAMBDA NIL :IN SWANK::%LISTENER-EVAL))
 10: (SWANK-REPL::TRACK-PACKAGE #<CLOSURE (LAMBDA NIL :IN SWANK::%LISTENER-EVAL) {1004C9A1CB}>)
 11: (SWANK::CALL-WITH-BUFFER-SYNTAX NIL #<CLOSURE (LAMBDA NIL :IN SWANK::%LISTENER-EVAL) {1004C9A1AB}>)
 12: (SWANK::%LISTENER-EVAL "(gen-seq:seq->list (gen-seq:seq-map #'extract-messaging (gen-seq:seq-map #'extract-entry entries))) ..)
 13: (SB-INT:SIMPLE-EVAL-IN-LEXENV (SWANK-REPL:LISTENER-EVAL "(gen-seq:seq->list (gen-seq:seq-map #'extract-messaging (gen-seq:seq-map #'extract-entry entries))) ..)
 14: (EVAL (SWANK-REPL:LISTENER-EVAL "(gen-seq:seq->list (gen-seq:seq-map #'extract-messaging (gen-seq:seq-map #'extract-entry entries))) ..)
 15: (SWANK:EVAL-FOR-EMACS (SWANK-REPL:LISTENER-EVAL "(gen-seq:seq->list (gen-seq:seq-map #'extract-messaging (gen-seq:seq-map #'extract-entry entries))) ..)
 16: (SWANK::PROCESS-REQUESTS NIL)
 17: ((LAMBDA NIL :IN SWANK::HANDLE-REQUESTS))
 18: ((LAMBDA NIL :IN SWANK::HANDLE-REQUESTS))
 19: (SWANK/SBCL::CALL-WITH-BREAK-HOOK #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK> #<CLOSURE (LAMBDA NIL :IN SWANK::HANDLE-REQUESTS) {100317FFEB}>)
 20: ((FLET SWANK/BACKEND:CALL-WITH-DEBUGGER-HOOK :IN "/Users/toni/.roswell/lisp/slime/2017.02.27/swank/sbcl.lisp") #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK> #<CLOSURE (LAMBDA NIL :IN SWANK::HANDLE-REQUESTS) {..
 21: (SWANK::CALL-WITH-BINDINGS ((*STANDARD-INPUT* . #1=#<SWANK/GRAY::SLIME-INPUT-STREAM {100309EAE3}>) (*STANDARD-OUTPUT* . #2=#<SWANK/GRAY::SLIME-OUTPUT-STREAM {1003167973}>) (*TRACE-OUTPUT* . #2#) (*ERR..
 22: (SWANK::HANDLE-REQUESTS #<SWANK::MULTITHREADED-CONNECTION {10030004A3}> NIL)
 23: ((FLET #:WITHOUT-INTERRUPTS-BODY-1159 :IN SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE))
 24: ((FLET SB-THREAD::WITH-MUTEX-THUNK :IN SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE))
 25: ((FLET #:WITHOUT-INTERRUPTS-BODY-358 :IN SB-THREAD::CALL-WITH-MUTEX))
 26: (SB-THREAD::CALL-WITH-MUTEX #<CLOSURE (FLET SB-THREAD::WITH-MUTEX-THUNK :IN SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE) {8A4DCFB}> #<SB-THREAD:MUTEX "thread result lock" owner: #<SB-THREAD:THREAD "..
 27: (SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE #<SB-THREAD:THREAD "repl-thread" RUNNING {1003180003}> NIL #<CLOSURE (LAMBDA NIL :IN SWANK-REPL::SPAWN-REPL-THREAD) {100317FF6B}> (#<SB-THREAD:THREAD "re..
 28: ("foreign function: call_into_lisp")
 29: ("foreign function: new_thread_trampoline")
 30: ("foreign function: _pthread_body")
 31: ("foreign function: _pthread_body")
 32: ("foreign function: thread_start")

At this point the problem is that extract-entry returns a list so really I should work like this:

CL-USER> (apply #'mapcar #'extract-messaging (mapcar #'extract-entry entries))
  0: (EXTRACT-ENTRY
      (("messaging"
        (("message" ("text" . "hola") ("seq" . 3227)
          ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u"))
         ("timestamp" . 1496071517968) ("recipient" ("id" . "474086316315717"))
         ("sender" ("id" . "1695095647186162"))))
       ("time" . 1496071518212) ("id" . "474086316315717")))
  0: EXTRACT-ENTRY returned
       ((("message" ("text" . "hola") ("seq" . 3227)
          ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u"))
         ("timestamp" . 1496071517968) ("recipient" ("id" . "474086316315717"))
         ("sender" ("id" . "1695095647186162"))))
  0: (EXTRACT-MESSAGING
      (("message" ("text" . "hola") ("seq" . 3227)
        ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u"))
       ("timestamp" . 1496071517968) ("recipient" ("id" . "474086316315717"))
       ("sender" ("id" . "1695095647186162"))))
  0: EXTRACT-MESSAGING returned
       (("text" . "hola") ("seq" . 3227)
        ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u"))
((("text" . "hola") ("seq" . 3227)
  ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u")))

but then it fails working with the library:

CL-USER> (apply #'gen-seq:seq-map #'extract-messaging (gen-seq:seq-map #'extract-entry entries))

attempt to use VALUES-LIST on a dotted list:
  #S(GENERIC-SEQ::BASIC-SEQ
     :DELAYED-ENUM #<CLOSURE (LAMBDA ()
                               :IN
                               GENERIC-SEQ::SEQ-MAP-1) {100499978B}>)
   [Condition of type SIMPLE-TYPE-ERROR]

Restarts:
 0: [*ABORT] Return to SLIME's top level.
 1: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING {1003160003}>)

Backtrace:
  0: (APPLY #<FUNCTION GENERIC-SEQ:SEQ-MAP> #<CLOSURE SB-IMPL::ENCAPSULATION {1002E5970B}> #S(GENERIC-SEQ::BASIC-SEQ :DELAYED-ENUM #<CLOSURE (LAMBDA NIL :IN GENERIC-SEQ::SEQ-MAP-1) {100499978B}>))
  1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (APPLY (FUNCTION GENERIC-SEQ:SEQ-MAP) (FUNCTION EXTRACT-MESSAGING) (GENERIC-SEQ:SEQ-MAP (FUNCTION EXTRACT-ENTRY) ENTRIES)) #<NULL-LEXENV>)
  2: (EVAL (APPLY (FUNCTION GENERIC-SEQ:SEQ-MAP) (FUNCTION EXTRACT-MESSAGING) (GENERIC-SEQ:SEQ-MAP (FUNCTION EXTRACT-ENTRY) ENTRIES)))
  3: (SWANK::%EVAL-REGION "(apply #'gen-seq:seq-map #'extract-messaging (gen-seq:seq-map #'extract-entry entries)) ..)
  4: ((LAMBDA NIL :IN SWANK::%LISTENER-EVAL))
  5: (SWANK-REPL::TRACK-PACKAGE #<CLOSURE (LAMBDA NIL :IN SWANK::%LISTENER-EVAL) {100499951B}>)
  6: (SWANK::CALL-WITH-BUFFER-SYNTAX NIL #<CLOSURE (LAMBDA NIL :IN SWANK::%LISTENER-EVAL) {10049994FB}>)
  7: (SWANK::%LISTENER-EVAL "(apply #'gen-seq:seq-map #'extract-messaging (gen-seq:seq-map #'extract-entry entries)) ..)
  8: (SB-INT:SIMPLE-EVAL-IN-LEXENV (SWANK-REPL:LISTENER-EVAL "(apply #'gen-seq:seq-map #'extract-messaging (gen-seq:seq-map #'extract-entry entries)) ..)
  9: (EVAL (SWANK-REPL:LISTENER-EVAL "(apply #'gen-seq:seq-map #'extract-messaging (gen-seq:seq-map #'extract-entry entries)) ..)
 10: (SWANK:EVAL-FOR-EMACS (SWANK-REPL:LISTENER-EVAL "(apply #'gen-seq:seq-map #'extract-messaging (gen-seq:seq-map #'extract-entry entries)) ..)
 11: (SWANK::PROCESS-REQUESTS NIL)
 12: ((LAMBDA NIL :IN SWANK::HANDLE-REQUESTS))
 13: ((LAMBDA NIL :IN SWANK::HANDLE-REQUESTS))
 14: (SWANK/SBCL::CALL-WITH-BREAK-HOOK #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK> #<CLOSURE (LAMBDA NIL :IN SWANK::HANDLE-REQUESTS) {100315FFEB}>)
 15: ((FLET SWANK/BACKEND:CALL-WITH-DEBUGGER-HOOK :IN "/Users/toni/.roswell/lisp/slime/2017.02.27/swank/sbcl.lisp") #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK> #<CLOSURE (LAMBDA NIL :IN SWANK::HANDLE-REQUESTS) {..
 16: (SWANK::CALL-WITH-BINDINGS ((*STANDARD-INPUT* . #1=#<SWANK/GRAY::SLIME-INPUT-STREAM {100307F5A3}>) (*STANDARD-OUTPUT* . #2=#<SWANK/GRAY::SLIME-OUTPUT-STREAM {1003147DD3}>) (*TRACE-OUTPUT* . #2#) (*ERR..
 17: (SWANK::HANDLE-REQUESTS #<SWANK::MULTITHREADED-CONNECTION {10030004A3}> NIL)
 18: ((FLET #:WITHOUT-INTERRUPTS-BODY-1159 :IN SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE))
 19: ((FLET SB-THREAD::WITH-MUTEX-THUNK :IN SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE))
 20: ((FLET #:WITHOUT-INTERRUPTS-BODY-358 :IN SB-THREAD::CALL-WITH-MUTEX))
 21: (SB-THREAD::CALL-WITH-MUTEX #<CLOSURE (FLET SB-THREAD::WITH-MUTEX-THUNK :IN SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE) {8A57CFB}> #<SB-THREAD:MUTEX "thread result lock" owner: #<SB-THREAD:THREAD "..
 22: (SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE #<SB-THREAD:THREAD "repl-thread" RUNNING {1003160003}> NIL #<CLOSURE (LAMBDA NIL :IN SWANK-REPL::SPAWN-REPL-THREAD) {100315FF6B}> (#<SB-THREAD:THREAD "re..
 23: ("foreign function: call_into_lisp")
 24: ("foreign function: new_thread_trampoline")
 25: ("foreign function: _pthread_body")
 26: ("foreign function: _pthread_body")
 27: ("foreign function: thread_start")

The problem is the same but then I can solve it :

CL-USER> (gen-seq:seq->list (apply #'gen-seq:seq-map #'extract-messaging (gen-seq:seq->list (gen-seq:seq-map #'extract-entry entries))))
  0: (EXTRACT-ENTRY
      (("messaging"
        (("message" ("text" . "hola") ("seq" . 3227)
          ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u"))
         ("timestamp" . 1496071517968) ("recipient" ("id" . "474086316315717"))
         ("sender" ("id" . "1695095647186162"))))
       ("time" . 1496071518212) ("id" . "474086316315717")))
  0: EXTRACT-ENTRY returned
       ((("message" ("text" . "hola") ("seq" . 3227)
          ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u"))
         ("timestamp" . 1496071517968) ("recipient" ("id" . "474086316315717"))
         ("sender" ("id" . "1695095647186162"))))
  0: (EXTRACT-MESSAGING
      (("message" ("text" . "hola") ("seq" . 3227)
        ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u"))
       ("timestamp" . 1496071517968) ("recipient" ("id" . "474086316315717"))
       ("sender" ("id" . "1695095647186162"))))
  0: EXTRACT-MESSAGING returned
       (("text" . "hola") ("seq" . 3227)
        ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u"))
((("text" . "hola") ("seq" . 3227)
  ("mid" . "mid.$cAAGvLYgmIaVihhT_EFcVM-1tP17u")))

But when I do that I lost lazy evaluation of the term so, how can I achieve it without losing lazy evaluation in generic-sequences.


Solution

  • If your function deals with assoc lists, you might check that they actually get valid assoc lists with a function like this:

    (defun assert-valid-alist (alist)
      (assert (and (not (null alist))     ; not the empty list
                   (listp alist)          ; it is a list
                   (every #'consp alist)  ; every item is a cons
                   (every (lambda (item)     
                            (symbolp (car item)))  ; every key should be a symbol
                          alist))
          (alist)
        "Not a valid assoc list: ~a" alist))
    
    (defun get-age (person)
      (assert-valid-alist person)
      (cdr (assoc 'age person)))
    

    Example:

    CL-USER 106 > (get-age '((name . ute) (age . 34)))
    34
    
    CL-USER 107 > (get-age '(((name . ute) (age . 34))))
    
    Error: Not a valid assoc list: (((NAME . UTE) (AGE . 34)))
      1 (continue) Retry assertion with new value for ALIST.
      2 (abort) Return to level 0.
      3 Return to top loop level 0.
    

    This may cost performance and possibly you can remove it, once your code works.