Arc Forumnew | comments | leaders | submitlogin
(map [string ".*" _] "test")
4 points by d0m 4997 days ago | 18 comments
Again a beginner question from me.. but I don't get why

  (map [string ".*" _] "test")
isn't working. I get

  Error: "string-set!: expects type <character> as 3rd argument,
  given: \".*t\"; other arguments were: \"\\u0000\\u0000\\u0000\\u0000\" 0"
Is it because I shouldn't use "map" on a string?

And, what would be a simple way to do what I'm trying ?

Thank you



2 points by aw 4997 days ago | link

If you use map on a string, map expects the function in its first argument to return a character. It builds a new string from the characters returned by the function.

  arc> (map (fn (x) #\a) "hello")
  "aaaaa"
And, what would be a simple way to do what I'm trying ?

I don't know, what are you trying to do? If you'd like to have the result be a concatenated string, this may not qualify as "simple"... but...

  arc> (apply + (map [string "." _] (coerce "test" 'cons)))
  ".t.e.s.t"
Now, whenever something of the wrong type causes an error in Arc, that's an opportunity to have Arc do something useful instead, if there's something we'd like and would make sense. Thus we could say, if we wanted to, that if we pass a string to map and the function returns a string, paste that returned string into the result. That could be useful. Suppose, for example, we want to escape HTML:

  (map [case _ #\< "&lt;" #\> "&gt;" #\" "&quot;" #\& "&amp;" _] str)
I don't actually know of a way to do that as nicely otherwise.

-----

1 point by fallintothis 4997 days ago | link

And, what would be a simple way to do what I'm trying?

Alternatively, there's

  arc> (string (map [+ ".*" _] (coerce "test" 'cons)))
  ".*t.*e.*s.*t"
which does (almost exactly) what (apply + ...) does, but is shorter. You could also avoid the lengthy coercion and go with

  arc> (tostring (each c "test" (pr ".*" c)))
  ".*t.*e.*s.*t"
Not that either of those is optimal.

-----

1 point by d0m 4997 days ago | link

Interesting solutions :D

There's also:

  (string (intersperse ".*" (coerce "..." 'cons)))
edit:

  (string ".*" (intersperse ".*" (coerce "..." 'cons)))

-----

1 point by fallintothis 4997 days ago | link

Except intersperse does it in the wrong order:

  arc> (string (intersperse ".*" (coerce "test" 'cons)))
  "t.*e.*s.*t"
By the way, for code that won't screw up asterisks, just prefix the line by two spaces. :)

Edit: Ah. Well, I suppose appending an extra onto the front works. Still not optimal, but oh well.

-----

1 point by d0m 4995 days ago | link

Finally, I've chosen to make a little macro:

  (let s "hello"
    (->s s 
      (map [+ ".*" _] s)))

-----

1 point by akkartik 4995 days ago | link

Can you elaborate?

-----

2 points by d0m 4995 days ago | link

This macro hide fallintothis' suggestion to my string-are-not-list problem. So basically it does:

  (def ->s (var . body) 
   `(let ,var (coerce ,var 'cons)
      (string ,@body)))
And now I can use:

  (let s "hello"
    (->s s
      (intersperse ", " s)))
  
  > "h, e, l, l, o"

-----

2 points by fallintothis 4994 days ago | link

Yet another option:

  (def map-as (type f seq)
    (coerce (map1 f (coerce seq 'cons)) type))

  arc> (map-as 'cons [+ ".*" _] "test")
  (".*t" ".*e" ".*s" ".*t")
  arc> (map-as 'string [+ ".*" _] "test")
  ".*t.*e.*s.*t"
  arc> (map-as 'cons [+ _ 1] '(1 2 3))
  (2 3 4)
  arc> (map-as 'string [+ _ 1] '(1 2 3))
  "234"
This works because of how coerce works.

  arc> (coerce '("a" "b" "c") 'string)
  "abc"
It deals with lists and strings well, which is decent: Arc's only other sequence-like type is the table (I don't think you'd ever want to treat symbols as a sequence of 1-character symbols; you'd just use a string). Tables would work better if they were properly coerced, cf. the comment above tablist and listtab in arc.arc.

The more I think about it, the more I like this model. Conceptually, it seems that map should behave like

  (def map (f seq)
    (map-as (type seq) f seq))
even if it's not implemented like that -- all the coercions would surely be slow. (Tangential: map would also need to handle multiple sequences.) But it makes more sense for map and coerce to at least have compatible behavior. Plus, map's current behavior is a degenerate case of the coerce-compatible map:

  arc> (map inc "abc")
  "bcd"
  arc> (map-as 'string inc "abc")
  "bcd"
so it's not like you lose functionality, and then this post's example would've worked to begin with.

-----

1 point by akkartik 4991 days ago | link

When you phrase it as map-as, it becomes easier to fit into my defgeneric/defmethod framework (http://www.arclanguage.org/item?id=11865). Thanks!

I've spent some time thinking about how to extend it for multiple-dispatch, and I didn't want to also think about setting the arg index to dispatch on.

-----

2 points by ainar-g 4970 days ago | link

You can actually do the last one using 'multisubst': (multisubs­t '(("<" "&lt;") (">" "&gt;") ("\"" "&quot;) ("&" "&amp;")­) str)

(sorry, I am a new here and I don't know how to mark code)

-----

2 points by fallintothis 4970 days ago | link

FYI: http://arclanguage.org/formatdoc

(Welcome to the forum!)

-----

1 point by ainar-g 4969 days ago | link

Thanks!

Also, I wonder, if there is a way to replace pairs of characters? I mean, if we need a code, that would replace 'number two number' with 'one two three,' should we use (multi)subst for it or is it done the other way (regexps etc)?

-----

1 point by d0m 4997 days ago | link

Thank you, I get it.. And yeah, I think it could be cool if we could concat string instead of only characters in map.

Maybe a way to do that would be to coerce the string to a list (as you did), to use a normal map on this, and then, to join the new list with (string)

(apply string (map .. (coerce "test" 'cons)))

(And by the way, I didn't know about '(coerce string 'cons))

-----

2 points by aw 4997 days ago | link

Here's a mapstr which does this:

  (def mapstr (f . seqs)
    (string (accum a (for i 0 (- (apply min (map1 len seqs)) 1)
                       (a (string (apply f (map1 [_ i] seqs))))))))

  arc> (mapstr [string "." _] "test")
  ".t.e.s.t"
And we can plug that into the definition of map:

  (def map (f . seqs)
    (if (some [isa _ 'string] seqs) 
         (apply mapstr f seqs)
        ;; the rest is the same as the arc3.1 def
        (no (cdr seqs)) 
         (map1 f (car seqs))
        ((afn (seqs)
          (if (some no seqs)  
              nil
              (cons (apply f (map1 car seqs))
                    (self (map1 cdr seqs)))))
         seqs)))

  arc> (map [string "." _] "test")
  ".t.e.s.t"

-----

3 points by aw 4997 days ago | link

Though you know, it's funny, my brain doesn't actually like it that map returns different things depending in the types of its arguments. In the same way that a long run-on sentence is annoying if I can't tell what's it's saying until the end, if I see a map I don't know if it's returning a list or a string or whatever until I've looked at its arguments and seen what they are.

So I might like map to always return a list, but still have it treat a string as if it were a list of characters. The example then becomes:

  (string:map [string "." _]  "test")

-----

1 point by d0m 4997 days ago | link

Cool, I've learn a couple of things by reading this. (Mainly accum). Do you think modifying map this way could break things?

-----

1 point by aw 4997 days ago | link

Assuming that I didn't introduce some gratuitous bugs, it's safe because it works the same for programs which don't throw an error in Arc 3.1.

-----

1 point by fallintothis 4997 days ago | link

When you map across a string, it coerces the whole result into a new string character-by-character. It's like if you (hypothetically) weren't allowed to have nested lists but tried to do

  (map [list 1 2 3 _] '(8 6 7 5 3 0 9))
Each string element is a character, not a whole string. Thus

  arc> (= s (newstring 3))
  "\u0000\u0000\u0000" ; these are null characters, by the way
  arc> (= (s 0) #\a)
  #\a
  arc> s
  "a\u0000\u0000"
  arc> (= (s 1) #\b)
  #\b
  arc> s
  "ab\u0000"
  arc> (= (s 2) "foo")
  Error: "string-set!: expects type <character> as 3rd argument, given: \"foo\"; other arguments were: \"ab\\u0000\" 2"
But you can use map on a string for things like

  arc> (inc #\a)
  #\b
  arc> (inc #\b)
  #\c
  arc> (inc #\c)
  #\d
  arc> (map inc "abc")
  "bcd"

-----