Arc Forumnew | comments | leaders | submitlogin
You want brevity? You can't handle the brevity :)
11 points by kennytilton 6066 days ago | 24 comments
Over on comp.lang.lisp Mark Tarver the creator of Qi challenged the world to come up with a version of Eliza shorter than the Qi version, I forget, fifty lines? Anyway, Stevan Apter lowered the bar to 3 with K:

  e:{$[(#b)=i:(|/'b:(w@:&~(w:(`$" "\:x@&~x in".?"))in`)in\:/:A)?1b;E@*1?#E
    " "~n:c@*,/(c:{x@*1?#x}C i)ss/:N;c
    .q.ssr[c;n;" "/:$(),P'(w[k],p:1_'(0,1+k:b[i]?1b)_`,w)"I"$n]]} 
Over to you, pg! :)

ps. Ruby done good, came in at 55:

  Script =
  [[:x, ["father",'mother','brother','sister'], :y],
    ["Tell me about your ", :_, "."]],
  [[:x, ["am", "i'm"], :y], [["Why are you", :y, "?"],
                             ["Have you always been", :y, "?"]]],
  [[:x, "I", :y], ["Why do you", :y, "?"]],
  [[:x, 'you', :y], [["We're talking about you, not me."],
             ["Please don't be so personal."]]],
  [[:x], [["That's very interesting. Do go on."],
        ["Tell me more."],
        ["I'm not sure that I understand you fully."],
        ["Can you elaborate on that?"] ]]

  def change_person s
  h = { 'I','you',  'my','your', 'myself','yourself',
    'you are','I am', "you're", 'I am' }
  tmp = s.scan(/you are|you're|\w+|\W+/).map{|s|
    h[s] or h.invert[s] or s }
  tmp[-1] = "me"  if "I" == tmp[-1]
  tmp.join
end

patterns, symbols, replies = [], [], []

  Script.each{|ary|
  syms = []
  patterns <<  Regexp.new( "^" +
    ary[0].map{|x|
      case x
        when Symbol
          syms << x
          "(.*?)"
        when String
          "\\b#{ x }\\b"
        when Array
          syms << :_
          "\\b(#{ x.join('|') })\\b"
        else
          ".*?"
      end
    }.join + "$", true ) # Case-insensitive.
  symbols << syms
  ary[1] = ary[1].sort_by{ rand }  if ary[1][0].is_a? Array
  replies << ary[1]
}

  while true
  print "? "
  resp = gets.strip.sub(/[.!?,;]+$/, "")
  break if 'quit' == resp
  patterns.each_with_index{|pat,i|
    if match_data = resp.match( pat )
      reply = replies[i]
      if reply[0].is_a? Array
        # Rotate replies for variety.
        reply.push( reply.shift )
        reply = reply[0]
      end
      captures = match_data.captures.map{|s| change_person s}
      # Create associative array mapping symbols to values.
      t = Hash[ *symbols[i].zip( captures ).flatten ]
      puts reply.map{|x| x.is_a?(Symbol) ? t[x] : x }.join
      break
    end
  }
end

pps. Norvig's from PAIP was reported at 150 LOC.



5 points by kennytilton 6066 days ago | link

I was remiss, I should have posted everything Steve offered on that snippet:

---------- BEGIN QUOTE ------------------------

   e:{$[(#b)=i:(|/'b:(w@:&~(w:(`$" "\:x@&~x in".?"))in`)in\:/:A)?1b;E@*1?#E
    " "~n:c@*,/(c:{x@*1?#x}C i)ss/:N;c
    .q.ssr[c;n;" "/:$(),P'(w[k],p:1_'(0,1+k:b[i]?1b)_`,w)"I"$n]]}
it's just a single eye-watering case statement:

   if no keyword match in A, then return a default response from E
   else if no substitution in the response c picked from C, return c
   else substitute and return
it's easier to break the q code into smaller chunks. a revised version here: http://www.nsl.com/k/eliza.q

----------- END QUOTE ---------------------------

-----

4 points by cchooper 6066 days ago | link

K is actually a very good language for Arc to steal ideas from. It's extremely concise (although it doesn't have to be line noise as above) and it does everything with lists.

-----

4 points by sa 6065 days ago | link

yes, it doesn't have to be line-noise - see eliza.q for an example. q is k without ambivalence: symbols denote only binary functions, unaries have keywords. e.g. in k x+y is addition, +x is transpose; in q, + is addition, flip is transpose. the q programmer can go further down this road, e.g. define x#y as 'take', then use x take y. as i mentioned in a follow-up on comp.lang.functional, part of how k achives concision is to keep the primitive-set small and orthogonal on a small set of datatypes. the programmer seeks representations which are tuned to the primitives. i think of this as coding to the grain of the language.

a small but important correction: k doesn't do everything with lists (althought it does do a lot with them.) k has atoms and lists. it also has dictionaries (maps of names to values), tables, which are transposed dictionaries of lists, and keytables, which are maps from utables to tables, where a utable is a table of unique records. as pg knows, it has first-class functions, but not closures.

people outside the k world may have the impression that k is just APL with an ascii character set. at this point in its development, i think it is more useful to think of k as a functional query language. in APL, the primitives understand atoms and lists (scalars and arrays). in k, they also understand maps, since a table is a transposed map of lists, a table is also a list of atomic maps.

i hope these remarks are useful to all of you arcturians.

-----

3 points by lojic 6065 days ago | link

Let's stay focused here people. Who's going to be the first to produce an Arc version shorter/nicer than the Ruby version? :)

-----

4 points by kennytilton 6065 days ago | link

Word. But I guess without built-in pattern matching or regex yer just gonna be translating Norvig's 150-line version. Fewer chars (esp. parens) but not much else.

-----

3 points by lojic 6066 days ago | link

"I will post this over on the Arc forum, I think they are headed your way. "

:)

I hope not.

-----

2 points by kennytilton 6066 days ago | link

I am starting to reconsider, had an epiphany looking at the K code, realized how much friction there is in not being able to see all my code at once, and yes I have two big flat panels going. Maybe pg is on the right track. I just wish it was a Lisp-2 to elim that name collision issue.

-----

6 points by sacado 6065 days ago | link

You know what ? I didn't like Lisp-2. Couldn't understand how that stuff could be useful.

That was before Arc. I named a table tab for once. That's also a macro's name, but I didn't know. Guess what happened when I did (tab 'foo).

-----

5 points by kens 6065 days ago | link

Am I the only person who keeps trying to use t as a variable name? It's the perfect name for a temporary, a table, a test value, ...

-----

2 points by kennytilton 6065 days ago | link

I have to be coding pretty fast and furious in CL but it has happened a few times over the years. Nice warning or error (I forget) from the compiler, tho.

-----

1 point by cooldude127 6065 days ago | link

also the perfect name for a true value :D

but yeah, i've done that in my CL code before. so annoying.

-----

3 points by nlavine 6065 days ago | link

That's an issue with variable bindings being messed up, not a Lisp-1 problem. That would work fine in Scheme, for instance.

-----

1 point by lojic 6065 days ago | link

Is that because Scheme doesn't allow you to use a data item as the first item in a form? Or does it have some other mechanism currently lacking in Arc to help with this problem?

-----

4 points by nlavine 6065 days ago | link

The reason (tab 'foo) doesn't work in arc is that ac (in ac.scm) effectively has a special list of macros, and when it compiles (tab 'foo), it looks up tab in the special list. What is really should do in a lisp-1 is look up tab in the same list that all the other variables are in, which is the list that (let tab (table) ...) will modify. (Yes, I know I'm simplifying. No, it doesn't matter in this case, unless you can think of a reason that it does.)

Also, there's only one list like this, and it has global scope, so you can't have local macros or anonymous macros.

-----

2 points by lojic 6065 days ago | link

Ok, so if it worked that way, then (tab 'foo) would work, but you would have shadowed the tab macro, and thus lost the ability to use it, which doesn't sound like a problem if you didn't know tab was a macro to begin with.

Anyone know if this is how Arc will work, or if the current behavior is by intent?

-----

3 points by cooldude127 6065 days ago | link

the problem that other people have stated is that because arc's macros are unhygienic, allowing local variables to shadow macros causes some pretty serious problems.

-----

4 points by em 6065 days ago | link

Why doesn't the K version include strings such as "That's very interesting. Do go on." ? Or the keywords?

-----

1 point by kennytilton 6065 days ago | link

The K version was offered when prompted, after Stevan first showed a Q version twice as long, so I guess the K version shared something mush like this from the Q version:

   D:(("1 father|mother|brother|sister 2";"tell me about your 0.");
  ("1 am|i'm 2";"why are you 2|have you always been 2?");
  ("1 i 2";"why do you 2?");
  ("1 you 2";"we're talking about you, not me.|please don't be so personal"))

  E:"|"vs"that's very interesting.  do go on.|
       tell me more|
       i'm not sure i understand you fully|
       can you elaborate on that?" 
I won't spam this further, see the thread winding down now on comp.lang.lisp.

-----

5 points by sacado 6065 days ago | link

Because, in K, "(w@" is a shortcut for "That's very interesting. Do go on."

-----

1 point by sacado 6065 days ago | link

and : is print. Well, I guess.

-----

1 point by map 6060 days ago | link

Later a shorter Ruby one was posted that used regular expressions:

  S =
  [ /father|mother|brother|sister/i, "Tell me about your 0."],
  [ /\b(am|i'm) (.*)/i, ["Why are you 2?","Have you always been 2?"]],
  [ /\bI was (.*)/i, ["Why were you 1?","I can't believe you were 1."]],
  [ /\bI will (.*)/i, "Do you think it's wise to 1?"],
  [ /\bI (.*)/i, "Why do you 1?" ],
  [ /\b(you|your|yours)\b/i, ["We're talking about you, not me.",
             "Please don't be so personal."]],
  [ /.*/, ["That's very interesting. Do go on.",
        "Tell me more.",
        "I'm not sure that I understand you fully.",
        "Can you elaborate on that?" ]]

  P = Hash[ *"I|you|my|your|myself|yourself|you are|I am|you're|I am".
        split('|')]
  ( gets;sub(/[.!?,; ]+$/,"");x=Array(S.find{|a|$m=$_.match(a[0])}[1])
    puts x[rand(x.size)].gsub(/\d/){$m.to_a[$&.to_i].scan(
      /you are|you're|\w+|\W+/).map{|s|P[s]||P.invert[s]||s}.join}
  ) while 9

By eliminating the step of changing the person of pronouns in the user's input (e.g., "your" to "my"), a step that the original Lisp program didn't perform, the Ruby code was reduced to 2 lines (not counting the "script"):

  S =
  [ /father|mother|brother|sister/i, "Tell me about your 0."],
  [ /\b(am|i'm) (.*)/i, ["Why are you 2?","Have you always been 2?"]],
  [ /\bI was (.*)/i, ["Why were you 1?","I can't believe you were 1."]],
  [ /\bI will (.*)/i, "Do you think it's wise to 1?"],
  [ /\bI (.*)/i, "Why do you 1?" ],
  [ /\b(you|your|yours)\b/i, ["We're talking about you, not me.",
             "Please don't be so personal."]],
  [ /.*/, ["That's very interesting. Do go on.",
        "Tell me more.",
        "I'm not sure that I understand you fully.",
        "Can you elaborate on that?" ]]

  (gets;sub(/[.!?,; ]+$/,"");x=Array(S.find{|a|$m=$_.match(a[0])}[1])
  puts x[rand(x.size)].gsub(/\d/){$m.to_a[$&.to_i]}) while 9

-----

1 point by map 6058 days ago | link

Some explanation about the shorter Ruby program. Consider this line from the "script":

  [ /\bI was (.*)/i, ["Why were you 1?","I can't believe you were 1."]],
Regular expressions are usually enclosed in /.../. The "i" at the end makes it case insensitive, so the regex could just as well have contained "i was". "\b" means that "I" will match only at the beginning of a word; we don't want to match "cheri was in the room." The parentheses in the regex denote a "capture", the text of which can be recalled later. The dot (.) matches any single character; the star matches any number of the preceding item. So if "\bI was " is found,

  (.*)
will match the rest of the string.

I've slightly tweeked the "two lines" of the program and broken them into more lines. p, the programmer's print, has been used to show the state of some variables.

  (
    gets
Read a line from the keyboard; instead of assigning it to a variable, rely upon Ruby's assigning it to $_.

    sub( /[.!?,;\s]+$/, "" )
Since we don't say which string variable the sub should act upon, it acts on $_. We're removing puctuation and whitespace at the end of the user's input.

Assume the user typed "I was thinking."

    p $_   # "I was thinking"
The tail of the string has been sanitized.

    x = Array(
In the script, sometimes we have only 1 possible response by the computer (i.e., a string); sometimes we have more than 1 response (an array (list) of strings). Array( let's us easily normalize; it converts something that's not an array to an array of 1 element and leaves arrays untouched.

      S.find{|a|
Iterate through the script list and return the first item for which our code block returns true. a is the parameter of the code block that receives the value of each item.

        $m = $_.match(a[0])
a[0] is the regular expression; we save the match-data in $m.

      }[1]
[1] yields the last part of the script item, the computer's response or responses.

    )
    p x   # ["Why were you 1?", "I can't believe you were 1."]
Since the user typed "I was ...", the computer has 2 answers from which to choose.

    p $m.class   # MatchData
    p $m.to_a   # ["I was thinking", "thinking"]
.to_a converts match-data to an array. The first item is the complete match; the rest of the items are "captures".

    puts x[rand(x.size)].
Randomly choose the computers answer. The "." at the end means a method follows...

      gsub( /\d/ ){|s| $m.to_a[s.to_i] }   # Why were you thinking?
The .gsub finds every numeral (digit) in the string and replaces it with the corresponding item in the match-data array. "0" would be replaced by the entire match; "1" is replaced by the first capture.

  ) while 9
"while 0" would work as well. The only untrue things in Ruby are false and nil. You'll have to break out of the loop with a control-key combination.

-----

2 points by absz 6060 days ago | link

Please note that this forum doesn't use BBCode; to add code, you need to surround the code by blank lines and indent each line by two spaces or more. The indented lines will be formatted like code, and your * s won't disappear. It would be nice to see the code, but it's unreadable this way.

-----

1 point by map 6060 days ago | link

Thanks for the help. I was beginning to sweat.

-----