Arc Forumnew | comments | leaders | submit | rocketnia's commentslogin

I don't remember if I've brought this up before, but in Jarc, (catch:list:point ignored throw.nil) returns (nil) instead of nil. This means we can't really use escape continuations in a library function if the (catch ...) form surrounds a call to a parameter, 'cause doing that changes the effective behavior of the function when the library user is also using escape continuations.

That said, please don't fix this unless you also change 'on-err so that it doesn't catch escape continuations. I'm currently using this bug to work around that one, by re-throwing caught escape continuations with a (catch.throw value.the-error) idiom. :-p

-----

4 points by rocketnia 5627 days ago | link | parent | on: Local recursive functions

You could do a couple of things.

If it's just one self-recursive function, use 'afn or 'rfn:

  (let factorial (afn (x)
                   (if (is x 0)  0
                       (< 0 x)   (self:- x 1)))
    factorial.12)
  
  (let factorial (rfn custom-name (x)
                   (if (is x 0)  0
                       (< 0 x)   (custom-name:- x 1)))
    factorial.12)
(Note that 'afn is just 'rfn with the name 'self already chosen for you.)

If one local function just needs to refer to another, you can use 'withs:

  (withs (get-street [...]
          format-address [... get-street._ ...])
    ...)
I doubt that's what you're looking for though, 'cause it's just a convenience for doing this:

  (let get-street [...]
    (let format-address [... get-street._ ...]
      ...))
If it's two or more actually corecursive functions you need, you can use a manual letrec pattern. (Is CL's 'labels basically the same as Scheme's 'letrec?)

  (let (odd even) nil     ; destructuring, so both start as nil
    (= odd [case _ 0 nil (even:- _ 1)])
    (= even [case _ 0 t (odd:- _ 1)])
    odd.12)
In fact, if you're using Anarki (http://github.com/nex3/arc), this kind of letrec is already defined as 'withr:

  (withr (odd [case _ 0 nil (even:- _ 1)]
          even [case _ 0 t (odd:- _ 1)])
    odd.12)
...I really like reducing parentheses though, so just now I added my own 'letrec to Lathe (http://github.com/rocketnia/lathe). :-p

  (use-rels-as ut (+ lathe-dir* "utils.arc"))
  
  ; The bindings all have to be everyday variable names, but this
  ; detects them automatically. You can use with-style parentheses too,
  ; in case you need to be extra sure.
  (ut:letrec odd [case _ 0 nil (even:- _ 1)]
             even [case _ 0 t (odd:- _ 1)]
    odd.12)

-----

2 points by rocketnia 5632 days ago | link | parent | on: Ask: PG and Anti-OO

I'm sure this doesn't surprise you, but here's a quick version of 'defclass that uses a syntax similar to your first example and an implementation similar to your second example:

  (mac defclass (name constructed-fields derived-fields . defs)
    (let mt (sym:string name '-mt)
      `(do (= ,mt (obj ,@(mappend
                           [do (case car._
                                 def  (let (name parms . body) cdr._
                                        `(,name (fn ,(cons 'self
                                                       (cons 'o parms))
                                                  ,@body)))
                                  (err:+ "An invalid 'defclass "
                                         "declaration was "
                                         "encountered."))]
                           defs)))
           (def ,name ,constructed-fields
             (let o (withs ,derived-fields
                      (obj ,@(mappend [list _ _]
                               (join constructed-fields
                                     (map car pair.derived-fields)))))
               (afn (method-name)
                 (fn args
                   (apply (,mt method-name)
                          (cons self (cons o args))))))))))
  
  (defclass Bank-Account (password)
    (money 0)
    (def check-pass (pw)
      (unless (is pw o!password)
        (err "Wrong password!")))
    (def deposit (x pw)
      self!check-pass.pw
      (++ money x))
    (def withdraw (x pw)
      self!check-pass.pw
      (when (< o!money x)
        (err "Not enough money."))
      (-- o!money x))
    (def check (pw)
      self!check-pass.pw
      o!money)
    (def change-pw (new-pw pw)
      self!check-pass.pw
      (= o!password new-pw)))

-----

1 point by bogomipz 5631 days ago | link

Nice, and you even changed it so x!deposit returns a function again! This does of course add some overhead since a closure is constructed every time you call a method, but still.

One thing I'm not quite happy with is that one has to write o!money. Would it somehow be possible to hide the o? Would it be possible to use !money or .money, or does the parser not allow that? And how to pass the hash table from the afn to the methods without polluting their namespaces? It could be done using a gensym, but then it is not possible to add methods to the method table outside defclass.

Perhaps doing something like this:

  (= bank-account-mt
    (obj check-pass (fn (self pw)
                      (unless (is self!ivars!pw pw)
                        (err "Wrong password!")))
         deposit (fn (self x pw)
                   self!check-pass.pw
                   (++ self!ivars!money x))
         withdraw (fn (self x pw)
                    self!check-pass.pw
                    (if (< self!ivars!money x)
                        (err "Not enough money.")
                        (-- self!ivars!money x)))
         check (fn (self pw)
                 self!check-pass.pw
                 self!ivars!money)
         change-pw (fn (self new-pw pw)
                     self!check-pass.pw
                     (= self!ivars!pw new-pw))))

  (def bank-account (password)
    (let ivars (obj money 0 pw password)
      (afn (selector)
        (if (is selector 'ivars)
            ivars
            (fn args
              (apply (bank-account-mt selector)
                     (cons self args)))))))
Then make defclass turn .foo into self!ivars!foo. Another macro could exist for (re)defining methods after the fact:

  (defmethod bank-account steal-money (x)
    (-- .money x))
Or even redefine Arc's def so you could do:

  (def bank-account!steal-money (x)
    (-- .money x))
since (bank-account 'steal-money) is not an atom and 'def could thus recognize it as different from an ordinary function definition.

-----

4 points by rocketnia 5635 days ago | link | parent | on: Beginner Question: The arc installation

I use Arc on Windows XP. My Arc 3.1 setup process is pretty straightforward:

1) Get Racket. http://racket-lang.org/

2) Get Arc. http://arclanguage.org/item?id=10254

3) Make a batch file that'll run an Arc REPL for me when I double-click it. Here's my batch file, with a boatload of comments to help you figure out what I'm doing: http://gist.github.com/576688 There's probably some room for improvement, but it's what I'm happily using at the moment. (Note that it crashes a bit if you close the terminal window while Racket 5.0.1 is running, instead of exiting via a Racket (exit) call or an Arc (quit) call. I think this is a Racket issue, since previous versions don't crash that way.)

4) Make some necessary changes to Arc so that it stops giving errors on Windows. There are two main issues here. For Arc to start at all, you'll need to fix 'setuid (http://arclanguage.org/item?id=10625). Then, if you also want to run web applications, you'll probably need to fix the system call to mkdir (http://arclanguage.org/item?id=4652), and I wouldn't be surprised if one of the other system calls breaks too.

If you've already installed Cygwin, the system call issues may not exist, but I'm not totally sure of that. I know I only encountered the mkdir issue on a Cygwin-free Windows computer after having used Arc on a Cygwin-ful Windows computer for quite a while... but I'm not sure I've ever actually tried to run Arc web applications on the Cygwin-ful computer.

-----

3 points by waterhouse 5635 days ago | link

Tip: At least for me (on a Mac), running Arc with "[racket|mzscheme] -f as.scm" and then hitting ctrl-C at some point will kill the whole process, instead of dropping to a [Racket|Scheme] prompt from which I can use (tl) to return to the arc> prompt. Replacing the command with "[racket|mzscheme] -i -f as.scm", -i for interactive, fixes this. Much better.

Also, I highly recommend rlwrap. See this thread: http://arclanguage.org/item?id=10

I have added the following to my .bash_profile (probably the Mac equivalent of .bashrc):

  export PATH=/Applications/Racket\ v5.0/bin:$PATH
  alias arc='rlwrap -c -r -l ~/Documents/ARC_LOG -q "\"" -C arc racket -i -f ~/Dropbox/arc3.1/as.scm'
I used to have 'cd ~/Dropbox/arc3.1;' before the 'rlwrap ...' part; the aload function and all the Arc files that load other Arc files (libs.arc) assume that you're in the Arc directory. I worked around this by wrapping all the calls to 'aload in as.scm like this:

  (parameterize ((current-directory (current-load-relative-directory)))
    (aload "arc.arc")
    (aload "libs.arc") 
    
    (aload "sh.arc")) ;my stuff
Now I can call "arc" from any directory; it will load fine and I'll still end up in the directory I started in.

-----

1 point by evanrmurphy 5634 days ago | link

rlwrap really is great. I like it almost as well as having my REPL in Emacs.

Also, that's a neat idea to house your Arc installation in Dropbox.

-----


I've found a few more bugs.

- Jarc doesn't support coercion from 'sym to 'int.

- Jarc's 'readc returns a spurious -1 character rather than the given end-of-file value. This has further consequences, like causing (readline:instring "") and (prf "anything") to go into infinite loops.

- Rainbow doesn't support 'macex1.

- Rainbow doesn't accept the syntax [].

- Rainbow chokes on (after (err "ignored") errsafe!ignored). In general, it's frustrating to do anything nontrivial with 'after (or 'protect), because an 'on-err anywhere in the "finally" call tree will mess things up.

- Rainbow raises an error when 'rep is called with an untagged argument.

- Both Rainbow and Jarc 17 handle (annotate 'a (annotate 'b 'c)) so that it's distinct from (annotate 'a 'c). Actually, the official Arc behavior is what caught me by surprise here, and I've ended up using the workaround (annotate 'a (list:annotate 'b 'c)) with my own tagged types, but maybe this tag-collapsing behavior is something you'd like to emulate anyway.

-----

1 point by conanite 5606 days ago | link

(stdin) and (string '("")) are fixed now in rainbow, as well as 'writec returning nil. Other bugs ... are noted ...

What is the issue with [] in rainbow? This works:

  arc> ([* _ 2] 3)
  6
Do you have an example?

I added macex1 as an arc fn (it's a builtin in arc3) -

  (def macex1 (expr)
    (let macro car.expr
      (if (and (isa macro 'sym)
               (bound macro)
               (isa (eval macro) 'mac))
        (let mac-impl (rep:eval macro)
          (apply mac-impl cdr.expr))
        expr)))

-----

2 points by rocketnia 5606 days ago | link

What is the issue with [] in rainbow? ... Do you have an example?

I think you'll find it's a very simple example. :-p

  arc> []
  rainbow.parser.ParseException: Encountered "]" at line 1, column 2.
As for 'macex1, there's definitely one inconsistency, which is that it doesn't work unless car.expr works. I also briefly worried about ssyntax, but I'm not sure it's a problem; official Arc lets (assign a.b nil) cause (bound 'a.b) to be true, but I don't blame Rainbow for not emulating that.

-----

1 point by conanite 5605 days ago | link

aha ... fixed []. Changed a "+" to a "*" to allow empty bodies ...

-----

1 point by rocketnia 5610 days ago | link

- Jarc doesn't support coercion from 'sym to 'int.

I don't remember what I meant by this. Official Arc doesn't support this either. XD

I must have done some test that behaved one way on official Arc 3.1, Anarki, and Rainbow and a different way on Jarc (17), but I guess I discarded too many necessary details here.

-----

1 point by rocketnia 5637 days ago | link

- Both Rainbow and Jarc 17 handle (annotate 'a (annotate 'b 'c)) so that it's distinct from (annotate 'a 'c).

Whoops, official Arc does that too. I should have given the examples (annotate 'a (annotate 'a 'b)) and (annotate 'sym 'b); official Arc's 'annotate doesn't wrap the value if it's already of the given type.

-----

1 point by conanite 5605 days ago | link

fixed

-----

2 points by rocketnia 5646 days ago | link | parent | on: Atpos bug?

What do you expect it to do?

In case you didn't know, the point of 'atpos is to implement atstrings, an optional string interpolation feature that lets "@qty @(plural qty \"taco\") @@ $@price" compile so that it evaluates to things like "2 tacos @ $11". This option isn't enabled by default, so any Arc program that uses atstrings, such as news.arc, has to (declare 'atstrings t) first.

IMO, an @ at the end of an atstring is a syntax error, since it isn't escaping anything. It might be convenient to give "...@" a meaning like (sym "..."), (err "..."), or even just "...@@", but that's adding functionality rather than fixing bugs.

-----

3 points by rocketnia 5647 days ago | link | parent | on: The use of + for non-numbers

For what it's worth, I practically never add numbers. I actually use subtraction much more than addition! So I don't mind whatever these operations are named--and I mind the speed even less, actually--but I can testify that this change would sorta hinder my own code at this point. :-p

You've almost certainly encountered this comment above the definition of 'join, but I'll paste it here just in case it elucidates anything:

  ; Rtm prefers to overload + to do this

-----

3 points by garply 5647 days ago | link

I make heavy use of + for concatenation throughout my code. I prefer it for a few reasons:

1. I find myself concatenating lists frequently and I prefer that frequently used functions be short. join has 4 chars to +'s 1

2. I also find ++ convenient for modifying a list variable. What would you use for the equivalent for join? In Racket's style, it would be join!, but I don't see a good analogue in arc for your proposal.

3. I'm constantly doing string concatenation. + is good for that because it's a short function name and also because I expect it to work because it works like that in many popular high-level languages (python, ruby, javascript). I also don't like to use arc's "@" for string-escaping because I find thinking about whether or not I have to escape the "@" character is distracting.

-----

3 points by waterhouse 5645 days ago | link

With regard to (2), to destructively append the list '(1 2 3) to xs, you can:

  (zap join xs '(1 2 3))
"zap join" is several characters longer than ++, but zap has general utility.

I use the string function to concatenate strings. It seems to work identically to +, as long as the first argument is a string. I do give heterogeneous arguments to string usually, and I like seeing that they will clearly be coerced into a string.

I have a couple of ideas.

1. It would be little problem for me if Arc provided + as is and provided a "plus" function that worked only on numbers and allowed me to simply go (= + plus) and proceed normally. Unfortunately, that would break all the functions in arc.arc and so forth that use + to concatenate lists (which includes, among other things, the setforms function, which is used in expand=). It would be really nice if one could "freeze" the variable references in those functions, so that changing the global value of + wouldn't change what "+" in the bodies of those functions referred to.

2. If you use concatenation so much, perhaps we could allocate an ssyntax character for concatenation. & is currently used for andf (experiment with ssexpand to see). We could boot out that usage, or perhaps have "&&" become andf. Good/bad idea? (Precedent: my memory tells me the TI-89 uses & for string concatenation.)

-----

1 point by garply 5645 days ago | link

Regarding your second suggestion, we could also use . instead of &, as that's what Perl and PHP do - feels a little more natural to me. But . might cause me a little mental friction in differentiating between the different uses of . in (. "a" "b") and (do (= a '(1 2)) a.0).

To be honest, I'm still not crazy about the idea simply because I don't need the speed boost and + doesn't seem to cause me to use extra mental cycles when reading my code. I'd be open to it though if the community really wanted it that way.

We could vote and also ask PG what he thinks and then make a decision.

-----

1 point by prestonbriggs 5644 days ago | link

I wouldn't do anything for a speed boost at this stage. Premature optimization and all that.

Preston

-----

2 points by fallintothis 5647 days ago | link

String concatenation is particularly convenient after the + change for arc3.tar: http://arclanguage.org/item?id=9937. That's probably what I use + for most of the time.

I find thinking about whether or not I have to escape the "@" character is distracting

I find this is easier with proper syntax highlighting. My arc.vim ftplugin can detect if you have (declare 'atstrings t) and, if so, highlights the escaped parts of strings. That way, you know if @ is escaped just by glancing. But I don't mean to shamelessly plug, haha. I don't use atstrings either, but my reason is far lazier: in the middle of writing code, it's less effort to just use + than it is to declare then go back and start using @s.

-----

2 points by garply 5647 days ago | link

What other goodies does your arc.vim plugin have? Is your editor at all integrated with the arc repl? Lack of a repl that I could easily send arc code to was the reason I switched to emacs after years of using vim. These days, using emacs with viper (vim emulation mode), I don't miss vim at all.

-----

2 points by fallintothis 5638 days ago | link

Sorry, I'm not going to be the one to write SLIME for Vim. :P I'm afraid the only "goodies" I have are highlighting-related. Off the top of my head:

- It can detect macros that take rest parameters named body, then highlight and indent them correctly.

- It uses R5RS's grammar & mz-specific extensions to highlight numbers correctly -- even weird ones, like

  #e1.5
  -nan.0+2.5i
  1/2@+inf.0
  #x8a
  #o1E+2
- It notices paren errors involving [], like

  [(+ a b])
- It highlights escape sequences in strings, like

  "\x61b \t c\u0064\n"
- It does its best to highlight ssyntax characters so they're easy to read.

You can check out more at http://arclanguage.org/item?id=10147 or http://bitbucket.org/fallintothis/arc-vim. It hasn't changed much since I first submitted it, though I've noticed it fails to highlight 0-argument function names like queue and new-fnid. Been meaning to fix that for awhile.

-----

2 points by rocketnia 5648 days ago | link | parent | on: Macros without 'mac'?

Am i wrong if I think that, in the end, the main difference between 'def'+'eval' and 'mac' is that 'mac' allows its code to be evaluated and expanded once, whereas 'def' needs to expand its code every time it is called ?

It's 'eval that expands its code once every time it's called. If you don't use 'eval at all, then all your arc code will be expanded exactly once, at the time you enter it at the REPL or load it from a file (since those are the points where 'eval is called internally).

When you enter the expression (def foo (x) (obj key x)) is at the REPL, it's evaluated as a two-step process: First it's compiled (which expands the (def ...) and (obj ...) macro forms), and then the compiled code is run. When it's run, the body of the function isn't run yet; instead, it's packed up into a procedure and stored as foo. When you call foo later, no expansion takes place, only running of already compiled code.

A better (but not perfect) way to use 'eval equivalently to 'mac is something like this:

  (def mywhen (test . body)
    `(if ,test (do ,@ body))
  
  (eval `(time:for i 1 1000 ,(mywhen '(is i 666) '(prn "Oh no!"))))
If we wanted to make 'do a non-macro, we could do that too:

  (def mydo body
    `((fn () ,@body)))
  
  (def mywhen (test . body)
    `(if ,test ,(apply mydo body)))
  
  (eval `(time:for i 1 1000 ,(mywhen '(is i 666) '(prn "Oh no!"))))
Finally, if we wanted to avoid the use of macros altogether, the example would turn into something like this:

  ; The procedure corresponding to an Arc macro can already be obtained
  ; using 'rep.
  (assign mywhen rep.when)
  (assign mytime rep.time)
  (assign myfor rep.for)
  
  (eval:mytime:myfor 'i '1 '1000 (mywhen '(is i 666) '(prn "Oh no!")))
I could make a few guesses as to why this kind of programming isn't popular in lisps, but my personal reason is that I tend to consider all operators to provide their own syntax, with procedure call syntax just being the default choice. For me, it's inconsistent to have the '(prn ...) syntax be quoted when the (mywhen ...) syntax isn't.

-----

1 point by ulix 5647 days ago | link

Very interesting, thanks!

Do you know where I can find an Arc language reference?

-----

1 point by aw 5647 days ago | link

http://arcfn.com/doc/index.html and http://arcfn.com/2009/06/whats-new-in-arc3.html

-----

1 point by rocketnia 5656 days ago | link | parent | on: File I/O Challenge

How about 'tolog? Are files opened for appending for other reasons, in practice? This would also keep with the to-means-output, from-means-input pattern.

-----

2 points by fallintothis 5656 days ago | link

I'd try to err on the side of generality. And I'm not quite as concerned about to:output / from:input, if the names are still "clear enough".

As to waterhouse's suggestions, I had considered those names. I suppose if you read appendfile as a noun instead of a verb-and-noun, it's confusing (though infile and outfile don't really have the same problem, so it's not the train of thought my brain follows). It's hard modifying a name like tofile with a long word like append. We already have two words in tofile, so adding a third without hyphenation is stretching it, and adding hyphens breaks the flow with the other names (fromfile, tostring, etc.). We could go for something shorter, like addtofile, which delineates itself well without hyphens because each word is one syllable. If we can't avoid hyphens, using / instead (e.g., tofile/a or tofile/append) flows better, but isn't that great.

Another name that occurred to me -- and is probably my favorite so far -- is ontofile, which is still simple enough to not need hyphens, communicates intent (appending something onto a file), and worms the word to in there, painting it with the to:output / from:input correlation. Thoughts?

-----

2 points by evanrmurphy 5656 days ago | link

Another name that occurred to me -- and is probably my favorite so far -- is ontofile, which is still simple enough to not need hyphens, communicates intent (appending something onto a file), and worms the word to in there, painting it with the to:output / from:input correlation. Thoughts?

+1! ontofile is a great name, in my opinion, for all the reasons you listed.

I searched for a good portmanteau in the vein of mappend, but I don't think there is one. fappend? Sounds like frappuchino. filepend is decent, but I think I prefer ontofile.

-----

2 points by fallintothis 5656 days ago | link

fappend? Sounds like a frappuchino.

Hahaha! Or worse: http://www.urbandictionary.com/define.php?term=fap :P

filepend is decent, but I think I prefer ontofile.

Agreed on both counts. But that's a clever one; I hadn't thought to try out portmanteaus yet.

-----

1 point by evanrmurphy 5656 days ago | link

Oh wow, didn't know that one. Who knew you could get street smarter hanging out on Arc Forum??

-----

1 point by garply 5655 days ago | link

Regarding logging, I use a log function such that (log "my log message here") appends to a globally identified file log*.

-----

3 points by rocketnia 5656 days ago | link | parent | on: File I/O Challenge

The "append" requirement is pretty specific. The file has to be opened for appending, if that's possible. The Python 2 and Groovy examples have complaints along these lines in the comments.

Furthermore, even though I think it was vague in the original description of the problem, the complaints indicate that the "world" line should be read into a variable.

So the first thing I tried was this, riffing off of evanrmurphy's version:

  (w/stdout (outfile "fileio.txt") prn!hello)
  (w/stdout (outfile "fileio.txt" 'append) prn!world)
  (pr:= line (string:cadr:readfile "fileio.txt"))
No luck. The file contains "helloworld" on one line. It doesn't even have a trailing newline. This is thanks to a lack of flushing, which is fixed on Anarki.

Speaking of bugs, do we even close these file handles?

And what if someone's mysteriously edited the file in between our commands, and we end up reading in a million-line file or barfing on a paren mismatch?

Also, most of the responses seem to use "putStrLn", "println", etc. for displaying the line that was read, even though that sacrifices two characters' worth of brevity. :-p

Let's try that again.

  (w/outfile f "fileio.txt" (disp "hello\n" f))
  (w/appendfile f "fileio.txt" (disp "world\n" f))
  (w/infile f "fileio.txt" (repeat 2 (= line readline.f)))
  prn.line
Whoops, I have a stray #\return at the end, 'cause I'm on Windows. To account for that, I can use Anarki and thereby take advantage of aw's 'readline fix and the flushing fix I mentioned earlier. If I use Anarki and take garply's lib/util.arc suggestion, this is what I get:

  (load "lib/util.arc")
  
  ; to be consistent
  (mac w/stdappendfile (name . body)
    (w/uniq gf
      `(w/appendfile ,gf ,name (w/stdout ,gf ,@body))))
  
  (w/stdoutfile "fileio.txt" prn!hello)
  (w/stdappendfile "fileio.txt" prn!world)
  (w/stdinfile "fileio.txt" (repeat 2 (= line (readline))))
  prn.line
That's what I'd settle for in this I/O demonstration, but if it were my own code I'd end up using Lathe, just so I could continue to support official Arc on Windows:

  (= lathe-dir* "my/path/to/lathe/arc/")
  (load:+ lathe-dir* "loadfirst.arc")
  (use-fromwds-as ut (+ lathe-dir* "utils.arc"))
  
  (w/outfile f "fileio.txt" (disp "hello\n" f))
  (w/appendfile f "fileio.txt" (disp "world\n" f))
  (w/infile f "fileio.txt" (repeat 2 (= line ut.readwine.f)))
  prn.line
Unfortunately, this code will break on Jarc 17 and Rainbow, and it's really their fault. :-p Mainly, both of them seem to overwrite the file rather than appending to it, and no workaround for that is coming to mind--well, except for ignoring that part of the problem statement. I'll start a new bug report thread.

For what it's worth, here's how I'd write it in Groovy:

  def file = "fileio.txt" as File
  file.withWriter { it.writeLine "hello" }
  file.withWriterAppend { it.writeLine "world" }
  def line = file.withReader { it.readLine(); it.readLine() }
  println line
My favorite posted answer is this PowerShell one. The comments lead me to believe it's cheating somehow, but the brevity is really impressive. This is pretty much what I'd expect a file I/O DSL to look like.

  sc fileio.txt 'hello'
  ac fileio.txt 'world'
  $line = (gc fileio.txt)[1]
  $line

-----

More