Arc Forumnew | comments | leaders | submitlogin
1 point by akkartik 5115 days ago | link | parent

Perhaps you just want guards?

http://en.wikipedia.org/wiki/Guarded_Command_Language#Guarde... http://en.wikibooks.org/wiki/Haskell/Control_structures#Guar...

Guards work particularly well with case expressions.

http://en.wikibooks.org/wiki/Haskell/Control_structures#Equa... http://arclanguage.org/item?id=13790



1 point by Pauan 5115 days ago | link

That's a potential way to make methods easier to define/use, but doesn't replace the concept. Let me put it this way... where do you define a function?

The traditional answer is "in the global scope" or maybe in a let block or something. That's all well and good, in most circumstances. In the case of a data-type, though, you want the possibility to create custom data types, like a custom table, or a custom stream.

The problem is... in this case, because things like peekc and readc are global functions, you end up needing to do some hacky stuff, extending them in a very verbose way to add in your custom stream type.

So instead, let's think of a different place to define the functions. In this case, the functionality is local to the stream: each individual stream decides how it is read. Thus, rather than defining a global function that operates on streams... we define a 'peek and 'char function on the stream itself. That, is the fundamental concept of methods.

Of course, this is only necessary for the primitive functions like readc and peekc. Functions that build on top of them (like readline) can be written in the normal way. So, I'm proposing that Arc remain essentially the same, with the key difference that data-types (tables, input, output, etc.) are implemented as methods, which makes it way easier to define custom data types.

-----

1 point by akkartik 5115 days ago | link

I'm totally with you, not trying to replace any concept, just suggesting alternative terminology, something that isn't so overloaded.

And pointing out that a language with these 'places to put functions' doesn't need functions anymore. Your 'methods' are just a form of dispatch, and you want programmable dispatch policies. In the simplest case, no dispatch policy. In a language with your methods you need no concept called functions.

Wart already does all this. You can already overload peek or readc. I'd love for you to take a look and see if it does what you're talking about. Don't panic :) it's super brief and readable by all accounts. (or if you've taken a look already just say so and I'll stop with all the PR.)

-----

1 point by Pauan 5115 days ago | link

Alright, alright, I'll take a look. :P

And yes, I'm aware that the term "method" is overloaded, but I still feel it's appropriate. Consider earlier where I showed functions that are placed into a table. I don't think that could reasonably be described as guards, yet they can be reasonably described as methods.

Also, you're right that methods (in OOP languages) can simulate functions, and vice versa, but I feel like functions are "lighter weight". For starters, a method must always be attached to something, it can't be global. I like functions, so I'd like to keep 'em, I just feel like methods are more appropriate in certain (probably rare?) circumstances.

Assuming that we use the function approach rather than the object approach, then "guard" would indeed be a very reasonable name for them. So thanks for that.

-----

1 point by akkartik 5115 days ago | link

Yeah, we can agree to disagree on terminology :) I think case expressions with guards are more general than C++-style vtables because you can dispatch on arbitrary expressions rather than just lookup a symbol in a table. It seems a pity to come so close and give up power you can get for free.

-----

1 point by Pauan 5115 days ago | link

Yeah, except that C style languages (including JavaScript) make `switch` so awful that you end up using if/else chains. A missed opportunity, to be sure. Arc does a better job, which is good.

By the way, I'm curious, how would you go about creating a custom table type in wart? In my hypothetical ideal Arc, it would be like this:

  (annotate 'table (def my-table (x n v)
                     (case x
                       'get ...
                       'set ...)))
Pretend that it's been made pretty with macros. The ... after 'get is called when doing (my-table 'foo) and the ... after 'set is called when doing (= (my-table 'foo) 5)

Incidentally, my hypothetical ideal Arc wouldn't even be particularly difficult to do, but I'm curious if there's a better (shorter or more extensible) approach than what I gave above.

-----

1 point by akkartik 5115 days ago | link

"By the way, I'm curious, how _would_ you go about creating a custom table type in wart?"

Hmm, I didn't really follow your example. Arc already has a table type. Do you mean you want a literal syntax for tables, so that you can say {a 1 b 2} or something?

-----

1 point by Pauan 5114 days ago | link

No... I'm talking about writing something in Arc that behaves like a table, and fits seamlessly into Arc. Consider my earlier prototype post (http://arclanguage.org/item?id=13838). A prototype behaves exactly like an ordinary table, with the only difference being that if it can't find a key, it checks it's parent.

As I explained in that post, it's impossible to do this seamlessly in Arc:

  (= my-proto (clone nil x "foo"))

  (my-proto 'x)           -> "foo"
  (= (my-proto 'x) "bar") -> "bar"
  (keys my-proto)         -> (x)
  (vals my-proto)         -> ("bar")
The above is impossible to do in Arc. What I am describing is a simple interface that makes the above not only possible, but easy. It lets you create new data structures that Arc treats as if they were tables. I will write a post about this ina sec.

-----

1 point by akkartik 5114 days ago | link

Ah, I see. Here's how you do prototypes in wart:

  $ git clone git@github.com:akkartik/wart.git
  $ cd wart
  $ git checkout a5d25c805a97347d1207 #Current HEAD for those trying this in future

  $ cat > 099heir.wart # Just kidding; copy and paste the following lines
                       # until the '^D' into 099heir.wart

  ; A new type: heir (because it inherits from a parent)
  (def clone-prototype(x)
    (annotate 'heir (obj val (table) parent x)))

  ; Extend ssyntax
  (defcall heir h
    [or rep.h!val._
        rep.h!parent._])

  ; Extend assignment
  (defset heir(h key value)
    (= rep.h!val.key value))

  ; Extend keys
  (def keys(x) :case (isa x 'heir)
    (join (keys rep.x!val)
          (keys rep.x!parent)))
  ^D

  # Now start your engines
  $ wart

  wart> ; And try the following expressions at the prompt
  (= a (table))
  (= b (clone-prototype a))

  ; inheriting from parent works
  (= a!x 23)
  (prn b!x) ; => 23

  ; assignment works
  (= b!y 34)
  b!y ; => 34
  a!y ; => nil

  ; keys works
  keys.a ; => (x)
  keys.b ; => (x y)

  ; vals is left as an exercise for the reader :)

  wart> (quit) ; or hit ctrl-d a few times

-----

1 point by Pauan 5114 days ago | link

Yeah, that's more-or-less the way to do it in pgArc as well (but using extend rather than :case).

It's nice that wart makes it so easy to extend stuff (with :case and defcall), but that doesn't help with the situation of having to extend multiple built-in functions (like keys, vals... anything that relies on tables, even user functions!) just to define one new type.

A further benefit of my system is that your custom tables can have a type of 'table, so existing code that expects tables can use them too.

-----

1 point by akkartik 5114 days ago | link

"that doesn't help with the situation of having to extend multiple built-in functions.. even user functions.. just to define one new type."

I can't think of a situation where I'll need to override user functions once I've overridden the basic operations. Can you? I think you just need to override these ops: https://github.com/akkartik/wart/blob/a5d25c805a97347d1207e7...

If user functions are untouched I think one approach is as verbose as the other, whether you say: (= x (fn args (case car.args ...))) or def :case over and over. The latter seems more declarative to me.

-----

1 point by Pauan 5114 days ago | link

The verbosity isn't in the actual declaration. It's in the need to extend built-in functions. My system avoids that completely: no extending.

As for extending user functions... consider this: what about code that expects something of type 'table? Your prototype is of type 'heir... so that won't work. My system does work in that situation. That can be mitigated by having functions coerce to 'table, and then define a defcoerce for heir, but why jump through so many hoops when you can get all that for free?

To put it another way, my system uses duck typing: if it supports these arguments, use it. This means that all you need to do is define something that looks like a table, and Arc automatically treats it as if it were a built-in table. No need to extend Arc's built-ins to convince it that, "yes I really really really want this to be a table."

-----

2 points by rocketnia 5114 days ago | link

"what about code that expects something of type 'table?"

I blame that code for its lack of extensibility. It should just go ahead and use the value as a table, trusting the extensions to be there (and using failcall to recover, of course ^_^ ).

If it must do a boolean check to determine the value's features (perhaps because failcall isn't available, or because it needs to fail fast), it shouldn't do a direct type check. Instead, it should call another global function dedicated to the inquiry of tableness (which is probably too specific an inquiry anyway, most of the time). That way, people can extend the tableness function too.

Extensibility isn't the only benefit of avoiding [isa _ 'table]. It also becomes possible to have a single value that supports table operations and (say) input stream operations at the same time. It's probably a silly combination, but a custom type based on an association list could warrant it.

-----

1 point by Pauan 5113 days ago | link

You're missing the point... the problem is not when the function expects something of type table... it's when the function needs to change it's behavior based on the interface. Consider `each`. It needs to change it's behavior depending on whether it's a table or a cons or a string. How is using (table? foo) better than (isa foo 'table)? Both are the same as far as extensibility goes. And in fact, you can do that right now, in Arc:

  (def table? (x) (isa x 'table))
And you're also missing the whole point of message passing... message passing enables duck typing, which means it's possible to write a function that doesn't care what it's type is, as long as it supports the required message.

You're trying to solve the symptom... I'm trying to solve the problem. What I'm saying is that re-extending all the relevant built-ins is ridiculously inefficient and verbose, and that there's a far better way. Message passing not only drastically reduces verbosity, but it also solves the problem you mentioned.

I suggest you go and read http://arclanguage.org/item?id=14237 which already explains this in more depth.

I think the current way that people think about types is fundamentally wrong, and I'll write a post about that in a moment.

Edit: here's the post http://arclanguage.org/item?id=14261

-----

1 point by rocketnia 5113 days ago | link

"Consider `each`. It needs to change it's behavior depending on whether it's a table or a cons or a string."

Yes. In a world with failcall, it can try treating the value as a table in one rule, as a cons as another rule, and as a string in a third rule. In a world without failcall, it can do the same thing, but with the help of extensible global functions 'atable, 'acons, and 'astring. That's merely a specific example of what I just said.

---

"How is using (table? foo) better than (isa foo 'table)? Both are the same as far as extensibility goes."

Sure, there's not a whole lot of difference between extending 'table? and extending 'isa. However, in my mind 'isa has a clear role as a way to check the most specific possible feature of a value: the kind of internal data it has.

One could go all the way down, making a (deftype wrap-foo unwrap-foo isa-foo) form that eliminates the need for 'annotate, 'rep, 'isa, and 'type altogether. In this example, 'isa-foo would be a function that indicates support for 'unwrap-foo, and 'wrap-foo would be a simple data constructor that makes an encapsulated value with nothing but innate support for 'unwrap-foo. (In a system with failcall, 'isa-foo is pretty much redundant.)

This is basically how the Kernel draft deals with custom types (except without an eye toward extensibility), but I haven't been inspired to go quite as far as this in practice. I think programmers should be free, if discouraged, to break encapsulation of values they don't know the nature of, like Arc programmers can when they use 'type and 'rep.

"And in fact, you can do that right now, in Arc[...]"

Cool, huh? :-p

Obviously pg-Arc hasn't been built from the ground up this way, but Arc Forumgoers have had an awful lot of extensibility ideas since Arc 3.1 was released, and pursuing any one of them in Anarki would change the core so much as to turn it into a separate language. That's why I don't call Penknife a version of Arc.

---

"And you're also missing the whole point of message passing... message passing enables duck typing, which means it's possible to write a function that doesn't care what it's type is, as long as it supports the required message."

That's the whole point of extending global functions too. I don't care what something's type is, as long as all the utilities I use have been extended to deal with it.

As I mentioned above with 'deftype, the 'extend approach can go all the way down to the level of making the 'type function itself merely a reflection tool. Obviously pg-Arc isn't built this way, but we've had an awful lot of extensibility ideas here since Arc 3.1 was released, and pursuing any one of them in Anarki would practically turn it into a separate language. (That's why I don't call Penknife a version of Arc.)

---

"I suggest you go and read http://arclanguage.org/item?id=14237 which already explains this in more depth."

I've already read that, thanks. I just wasn't convinced, and I found more interesting arguments to make over here. ^^

"Edit: here's the post http://arclanguage.org/item?id=14261 "

My next order of business is to read that.

-----

1 point by Pauan 5113 days ago | link

"Yes. In a world with failcall, it can try treating the value as a table in one rule, as a cons as another rule, and as a string in a third rule."

How? I've provided concrete examples where my solution is significantly shorter at creating new data types than the current solution, while still retaining `isa`, while avoiding problems. If you have a solution that can do all that, then please present it.

---

"In this example, 'isa-foo would be a function that indicates support for 'unwrap-foo, and 'wrap-foo would be a simple data constructor that makes an encapsulated value with nothing but innate support for 'unwrap-foo. (In a system with failcall, 'isa-foo is pretty much redundant.)"

Why? My system already does things similar to that, but in a very simple way, that's compatible with pgArc.

---

"However, in my mind 'isa has a clear role as a way to check the most specific possible feature of a value: the kind of internal data it has."

This is precisely what I meant when I said that I think the current view of types is fundamentally wrong. A table is not a hash. It's an interface that could potentially be implemented as a hash... or something else. Arc leaves it unspecified, which I consider to be a Good Thing.

---

"That's the whole point of extending global functions too. I don't care what something's type is, as long as all the utilities I use have been extended to deal with it."

...but as I've already mentioned repeatedly (and demonstrated in a post), that way is significantly more verbose than it has to be. My solution solves that, in a way that is more hackable/extendable/succinct. That is the goal of Arc, yes? Hackability and succinctness? I still have not seen a solution that matches mine for succinctness, let alone extensibility. Please provide one, if you're not convinced that my way is superior. Then I will back down.

-----

1 point by Pauan 5113 days ago | link

By the way, I just noticed something. Check out this link: http://wiki.call-cc.org/Python-like%20generators

Note that they're using the message-passing idea: after an iterator has been created, you can call it with 'status?, 'dead?, 'alive?, or 'kill! to get/set it's private data.

What I'm suggesting is to treat all compound data types like that. You can then create function wrappers around them, which is in fact what they did too: note the definition of `iterator-empty?`

Every individual iterator has it's own private state, so it doesn't make sense to require extending built-in functions every single time you create a new iterator. Instead, you define a function wrapper (like `iterator-empty?`) that accesses the internal data for you. No need to extend anything! That conciseness is what message passing gives you.

-----

2 points by akkartik 5113 days ago | link

"define something that looks like a table, and Arc automatically treats it as if it were a built-in table"

Bringing http://arclanguage.org/item?id=14268 back here, how would you convert code like (isa x 'table) into a method framework? Wouldn't it require getting rid of types altogether?

-----

1 point by Pauan 5113 days ago | link

"how would you convert code like (isa x 'table) into a method framework?"

Haha... that's what I've been explaining all along! That's the whole point of message passing: defining a low-level framework that makes it easy to both create new types, and also extend existing types.

That is, in fact, what this post is describing: http://arclanguage.org/item?id=14237

-----

1 point by akkartik 5109 days ago | link

Go seems to do this really well. Functions can ask for a specific interface, which is defined as a set of operations. But types don't have to declare that they implement the interface. http://golang.org/doc/go_tutorial.html

I still don't see what this has to do with methods, though. (I still can't follow that humongous comment[1]). Both C++ and Java have methods. No language but Go tests for an interface by seeing the functions it supports.

And this isn't the same as duck typing at all. Arc does duck typing just fine. In arc or python there's no way to convey tableness or any other ness. You just use the operations you care about, and hopefully any new types remembered to implement them.

Duck typing has no contract, Java has too rigid and inextensible a contract. Go is just right. And you're hosed in any of them if you write code of the form if (isa x table) ..

[1] I think I've missed half your comments in recent weeks. Either because they're so long and hard to follow, or because there's just too many of them and I forgot to go to the second page of new comments. The latter is a weakness of this forum structure. For the former, well, I think you spend too much time describing methods. Most of us have seen them in other languages. What you suggest is subtly different, and the subtleties are lost in the verbiage.

Which is why runnable examples are useful. You're right that wart differs from arc in subtle ways. That's why I have unit tests, to enumerate all the subtleties. I think you're calling it out (fairly) for lots of gritty details, and then doing the same thing even more egregiously.

Or maybe I just suck at reading comprehension compared to the rest of the group here :)

-----

1 point by Pauan 5109 days ago | link

"Go seems to do this really well. Functions can ask for a specific interface, which is defined as a set of operations. But types don't have to declare that they implement the interface. http://golang.org/doc/go_tutorial.html "

So... Go's interfaces actually look an awful lot like message passing. Here's a comparison:

  type reader interface {  // Go
      Read(b []byte) (ret int, err os.Error)
      String() string
  }


  (annotate 'reader        ; py-arc
    (fn (m b)
      (case m
        'read   ...
        'string ...)))


  (object reader           ; py-arc, with sugar
    read   (b) ...
    string ()  ...)
The idea is, every object is a collection of methods, and also a type. You can just call the methods directly, bypassing the type (this is duck typing), or you can check the type to make sure it implements the interface you want... or you can use coerce to change it's interface. Here is an example of the three ways, assuming foo implements the reader interface:

  (attr foo 'read)                   ; duck typing
  
  (if (isa foo 'reader)              ; interface checking
    (attr foo 'read))
     
  (attr (coerce foo 'reader) 'read)  ; coerce to reader
Message passing is simply the low-level way to create objects + methods... or prototypes. Or rulebooks. Or other things. To put it an other way, objects are just macros that expand to message passing.

Think of it like continuations and iterators. Continuations are lower-level than iterators, and it's possible to create iterators by using continuations. Message passing is the low-level idea, which can be used to create higher-level things, like prototypes, objects, or interfaces.

---

"Duck typing has no contract, Java has too rigid and inextensible a contract. Go is just right. And you're hosed in any of them if you write code of the form if (isa x table) .."

But what if `isa` tested for an interface? In other words, (isa x 'table) would be like saying, "does x support the table interface?"

---

"I think I've missed half your comments in recent weeks. [...] For the former, well, I think you spend too much time describing methods."

Yeah, I've probably been a bit too excitable about this... ^^;

---

"I think you're calling it out (fairly) for lots of gritty details, and then doing the same thing even more egregiously."

I'm not saying it's a bad thing that wart differs from pgArc, I was just explaining why I haven't immediately jumped in and started using wart. I think it's good to try out different approaches. Nor am I expecting people to immediately jump in and start using message passing... especially since message passing probably isn't useful for everybody.

I was presenting my ideas in the hope of getting useful feedback... so that my ideas can be improved, or rejected. I know that you guys are smart, and by presenting my ideas here, you all have already helped me improve them. I was just looking for some feedback (which I'm finally getting, which is good).

Also, I think it's perfectly possible to discuss and improve on the idea without needing a runnable example. You're right that I could have explained the idea better, though.

-----

2 points by akkartik 5109 days ago | link

"I think it's perfectly possible to discuss and improve on the idea without needing a runnable example. You're right that I could have explained the idea better, though."

Agreed. I'm giving you feedback on what would help me understand the idea. Sometimes if an idea has too many subtle nooks and crannies it helps me to see running code. That doesn't compel you to provide it, of course.

"I was just looking for some feedback."

You're not just looking for feedback, you're aggressively/excitably demanding it :) But you'll be more likely to get it from me (as opposed to super boring meta-feedback, and agonized grunts about your writing quality) if you provide runnable code. Pseudocode is no different than english prose.

"Nor am I expecting people to immediately jump in and start using message passing.."

I'm not sure what you're expecting when you say things like "what more do I have to do?". What more do you have to do for what?

(Btw, the answer to the question 'what more do I have to do' is.. give me runnable code :) I usually start out talking about an idea; if it is neither shot down nor embraced I go build it. If it's still neither shot down nor embraced I try to build stuff atop it. Keep doing that until you run into limitations or (slim chance) you build something so impressive people have to pay attention. Platforms need killer apps.)

---

"Go's interfaces actually look an awful lot like message passing."

Argh, here we go again..

  (annotate 'reader        ; py-arc
    (fn (m b)
      (case m
        'read   ...
        'string ...)))
Since you can write this in arc today, it's utterly confusing and distracting to me that you keep harping on it.

"what if `isa` tested for an interface?"

This seems to be the absolute crux of your idea. This needs changes to ac.scm. But it has absolutely nothing to do with message passing. Messages are not types, and your suggestion is to augment the type system with a keyword called interface or object that isa can accept. If you made that change, arc would support 'message passing' according to you. Am I right?

I'll summarize my position again. I buy that interfaces are foundational, but lots of things are foundational. Unification like in prolog, pattern matching like in haskell, lazy evaluation like in haskell, messages like in smalltalk, function calls like in lambda calculus. That something is foundational doesn't necessarily make it a good fit for a specific language. To show that something foundational is a good fit in arc and also backwards-compatible, provide runnable examples that look good/short and behave intuitively. Show us that existing mechanisms in arc can coexist with the new feature, that it occupies a niche that they don't already provide. No hand-wavy ellipses.

---

"I'm not saying it's a bad thing that wart differs from pgArc, I was just explaining why I haven't immediately jumped in and started using wart."

No explanation necessary. I really don't think wart needs to be part of this conversation. Just compare your approach with extend and we don't have to keep on going on about "backwards compatible? at least it runs!" and "I don't care about running it, it's not backwards compatible." :)

-----

1 point by Pauan 5109 days ago | link

"Since you can write this in arc today, it's utterly confusing and distracting to me that you keep harping on it."

Having built-in types like table/cons/etc. written in the same way, rather than as opaque blobs in Racket.

---

"I'm not sure what you're expecting when you say things like "what more do I have to do?". What more do you have to do for what?"

"What more do I have to do to demonstrate that message passing has advantages over extend?"

I asked that because it seemed that people weren't convinced that message passing was actually better than extend. This was unfortunate because:

1) It appeared to ignore the evidence that I was presenting

2) It seemed to basically dismiss message passing, but without explaining why. How am I supposed to know how message passing is flawed, unless people explain?

If the dismissal was caused by not understanding my idea, then it's my fault for not explaining well enough.

---

"[...] This needs changes to ac.scm."

Yup!

---

"But it has absolutely nothing to do with message passing. Messages are not types, and your suggestion is to augment the type system with a keyword called interface or object that isa can accept."

You're right: message passing is the low-level idea. Interfaces are built on top of message passing, but aren't necessary to support message passing.

---

"If you made that change, arc would support 'message passing' according to you. Am I right?"

No, in order for Arc to support message passing (at least in my mind), it would be necessary for the built-in types to also use message passing. As you pointed out, user-created Arc code can already use message passing, but that's not useful if you want to create something that behaves like a built-in type.

---

"[...] provide runnable examples that look good/short and behave intuitively."

Kay. I'll need to patch up py-arc first, though, because right now it doesn't support basic stuff (I'm looking at you, `apply`). Once I get py-arc into a decent enough shape, it should take less than a day to get message passing working.

-----

1 point by akkartik 5109 days ago | link

"Having built-in types like table/cons/etc. written in the same way, rather than as opaque blobs in Racket."

"in order for Arc to support message passing, it would be necessary for the built-in types to also use message passing"

Are you planning to replace (f x) everywhere with (x f)? That hardly seems backwards-compatible. (Forgive me if this is a stupid question. I have zero 'expertise' since I haven't read 75% of what you have written in this thread.)

If you provide a way to handle (f x) as an 'f message to x, then you shouldn't need to implement primitives.

"It seemed to basically dismiss message passing, but without explaining why. How am I supposed to know how message passing is flawed, unless people explain?"

You have to try it :) It's pretty clear that nobody here has tried quite what you're proposing, so what you thought of as dismissal was just people thinking (like me for the past few weeks) that they had nothing to add since they haven't tried it, and wanting to reserve judgement until they had an opportunity to play with an implementation. But maybe that's just me :)

Or did I miss a dismissive comment?

-----

1 point by Pauan 5109 days ago | link

"Are you planning to replace (f x) everywhere with (x f)?"

Nooope. Let's assume my-table is a table. On the left side is the current pgArc interpretation. On the right side is message passing:

  (my-table 'something)       -> (my-table 'get 'something)
  (= (my-table 'something) 5) -> (my-table 'set 'something 5)
  (keys my-table)             -> (my-table 'keys)
...but that's all hidden behind functions, so ordinary Arc code doesn't need to know that. In other words, `apply` would automagically convert (my-table 'something) into (my-table 'get 'something), so Arc code can't tell the difference. That's why it's backwards compatible.

---

"Or are you seeing dismissal in statements rather than silence?"

Mostly rocketnia, but that's fine since they now understand what I'm talking about, so we can actually discuss the idea. :P Their dismissal seemed to be because of a conflict in motivations.

-----

1 point by rocketnia 5109 days ago | link

"So... Go's interfaces actually look an awful lot like message passing."

That's a bit of a tautology. ^_^ If I'm not mistaken, you took the term "message-passing" from its object-oriented "foo.bar( baz )" context to begin with.

---

"Yeah, I've probably been a bit too excitable about this... ^^;"

Well, I (rocketnia) can't blame you for that, and I can't blame you for not having runnable examples either. I tend to be guilty of both those things a lot, and this discussion is a great example. >.>

-----

1 point by akkartik 5114 days ago | link

my system uses duck typing: if it supports these arguments, use it. all you need to do is define something that looks like a table, and Arc automatically treats it as if it were a built-in table.

I see, yeah you're right. The alternative to duck typing would be subtypes. Ugh.

No need to extend Arc's built-ins to convince it that, "yes I really really really want this to be a table."

You're saying more than that, you're saying how. That logic is common to both approaches. Your approach is superior anytime user code has language like if (isa x 'table) ... Otherwise the two are equivalent in power.

I always treated branching on types as a code smell. wart basically grew out of trying to remove every case of that pattern in the arc codebase. You've given me something to think about, thanks.

-----

1 point by Pauan 5114 days ago | link

By the way... I know using (case) is ugly. On the bright side, my system doesn't care how you write the actual function, just so long as you follow the interface. So you could use wart's :case for it as well:

  (def my-table ()
    ...)
  
  (def my-table (m) :case (is m 'keys)
    ...)
  
  (def my-table (m k) :case (is m 'get)
    ...)
    
  (def my-table (m k v) :case (is m 'set)
    ...)
...at least, I think that's how :case works? I actually like the wart style. That's supposed to be equivalent to this:

  (= my-table (fn (m k v)
                (case m
                  'keys ...
                  'get ...
                  'set ...)))
So yeah, it doesn't matter how you define your function (case or :case or `if` or extend or whatever), just so long as it follows the interface.

P.S. My goal here isn't necessarily to increase raw power, it's to increase extensibility and conciseness. Being able to do the same thing, but in a very hacky and verbose way doesn't sound appealing to me. So if wart has equivalent power, that's great! But I want to do better. I want to have equivalent power and make it shorter and make it more extensible.

-----

1 point by akkartik 5114 days ago | link

Yeah that was the half of your point I understood when I was peddling wart :) But yes wart won't handle user code that checks the type of an object if that is important to you.

-----

1 point by aw 5114 days ago | link

My http://awwx.ws/table-vivifier patch sounds like it may be close to what you want, though it goes too far: it extends tables so that if a key isn't found in the table, it calls a specified function to generate the value -- and then inserts the new value in the table.

If it would be useful, I could simplify it to leave off the last part, and then you could specify the action to take if the key isn't found (such as looking it up in a parent, for example).

-----

1 point by Pauan 5114 days ago | link

Pretty cool. But with my proposal, you could write that in Arc itself.

My solution is general... it works on all compound data types: tables, input, lists, etc.

Imagine being able to write cons, car, cdr, instring, peekc, readc, and more, all in Arc! It drastically cuts down on the number of primitives Arc needs.

-----

1 point by akkartik 5115 days ago | link

"C style languages (including JavaScript) make `switch` so awful that you end up using if/else chains. A missed opportunity, to be sure. Arc does a better job, which is good."

I don't follow that at all.

-----

1 point by Pauan 5114 days ago | link

In languages derived from C, including JavaScript, they use `switch` rather than `case`, like so:

  ; Arc

  (case x
    "foo" ...
    "bar" ...)


  // JavaScript

  switch (x) {
  case "foo":
      ...
      break;
  case "bar":
      ...
      break;
  }
As you can see, switch blocks are atrocious, especially because they require a break; statement or they'll fallthrough. So instead of using switch, some developers prefer if/else chains:

  if (x === "foo") {
      ...
  } else if (x === "bar") {
      ...
  }
Which, as you can see, are less verbose. Arc gets it right, JavaScript doesn't.

-----

2 points by thaddeus 5114 days ago | link

I'm going to argue that you're comparing apples to oranges to pears.

Arc and Javascript case functions are not expected to do the same thing. JS does not allow expressions for input arguments while Arc can. And that break; statement is a feature that some would say Arc lacks. ie, what if you want your case statement to fall through? - now Javascript is golden and one could say Arc is lacking.

I also don't believe developers, generally speaking, prefer if/else chains over switch/case statements as they are different tools intended for different purposes.

They both are not perfect, both are missing features that the other could benefit from.

I think Clojure got it right having it all: case + cond + condp.

-----

1 point by Pauan 5114 days ago | link

Not true... JavaScript allows arbitrary expressions in switch statements, so it does behave like Arc's `case`, only it's much more verbose:

  switch (true) {
  case 5 > 1:
      console.log("5 > 1");
      break;
  }
---

On the contrary, switch should not have a break statement! It should have a fallthru; or continue; statement. Why make the common case difficult? This should be uncontroversial... it's been well established that switch's design is poor, and they should have made it explicit fallthru (like with a continue statement) rather than implicit.

As for Arc "lacking" fallthru... can't you implement that with continuations? That would be the equivalent of explicit fallthru.

---

My point was that switch statements are so atrocious that even in the situations that they were intended for, some developers still prefer if/else chains because they're shorter and more readable.

-----

1 point by waterhouse 5114 days ago | link

> Why make the common case difficult?

I think that's because the above semantics correspond more directly to assembly language, which I imagine was done because "switch" was defined back when that was either important or just not seen to be bad. (Perhaps I'm just making that up, though.) Here's how a switch statement would look in x64 assembly:

  switch:
        ;put desired thing in rax, let's say
  case_1:
        cmp rax, val_1
        jne case_2      ;jump if not equal
        <case 1 code>
  case_2:
        cmp rax, val_2
        jne case_3
        <case 2 code>
  case_3:
        cmp rax, val_2
        jne done
        <case 3 code>
  done:
        <whatever>
By default, the machine will just plow through the remaining cases. If you want it to break out after one case, you have to tell it to do so:

  switch:
        ;put desired thing in rax, let's say
  case_1:
        cmp rax, val_1
        jne case_2      ;jump if not equal
        <case 1 code>
        jmp done        ;jump.
  case_2:
        cmp rax, val_2
        jne case_3
        <case 2 code>
        jmp done
  case_3:
        cmp rax, val_2
        jne done
        <case 3 code>
  done:
        <whatever>
Assembly language is kind of awesome, by the way. And so is the Miller-Rabin primality test. Some disorganized code: http://pastebin.com/raw.php?i=wRyQ2NAx

-----

1 point by Pauan 5114 days ago | link

Fine. That's great and all for C, but I dislike how Java copied C word-for-word, and then JavaScript copied Java. It would have been nice if they had said, "hm... using continue rather than break would make a lot more sense."

It's not a huge deal, and developers can simply avoid switch if they don't like it. My point was merely that "JavaScript's switch sucks, Arc's (case) is awesome."

-----

1 point by thaddeus 5114 days ago | link

a reply for one layer down:

> JavaScript allows arbitrary expressions in switch statements

That's right... looks like it was the test data I was thinking of, which isn't entirely relevant, given you can accomplish the same thing in the end.

> Why make the common case difficult?

A fall-through could be nicer than many breaks. Trying to think of all the scenarios required to make this usable, i.e., do you need a break to get out of a fall-through? Maybe that's why they just went with break. either way, it's a good idea.

-----

1 point by Pauan 5114 days ago | link

The current behavior for switch is simple: it will continue until it finds a break statement. Thus:

  switch (true) {
  case true:
      console.log(1);
  case true:
      console.log(2);
  case true:
      console.log(3);
      break;
  case true:
      console.log(4);
  }
...which outputs 1, 2, and 3. If you wanted to have the same behavior using the continue statement, you would use this:

  switch (true) {
  case true:
      console.log(1);
      continue;
  case true:
      console.log(2);
      continue;
  case true:
      console.log(3);
  case true:
      console.log(4);
  }
As far as I know, this would have exactly the same power as using break; but would be much better suited for the very common case of not wanting fallthru. I think it's much more readable, too: makes it very obvious where it falls through. And it's less error prone... no more strange bugs if you accidentally forget to add in a break; statement.

In fact, in my years and years of programming in JavaScript, I have used if/else blocks many many times (some of which could have been switch statements), and only wanted fallthru a handful of times. I think allowing fallthru is fine, but it should be made explicit, rather than implicit.

The only possible argument I can think of in favor of implicit fallthru is using a switch inside a `for` loop:

  for (var i = 0; i < array.length; i += 1) {
      switch (array[i]) {
      case "foo":
          continue;
      }
  }
...but that's actually ridiculous because I'm fairly sure that breaking out of a loop is far more common than continuing. In which case explicit fallthru would be better with loops.

-----

1 point by akkartik 5114 days ago | link

Argh, we're totally talking about different things. And now this thread's gotten hijacked by switch/case/break C whining :)

Part of the problem was that I too was using the wrong terminology. So fuck the names and terms, when I said 'case expressions' I was referring to wart's (def .. :case ..) syntactic sugar to extend any function. This is why I linked to http://en.wikibooks.org/wiki/Haskell/Control_structures#Equa... earlier in the thread. See how I extend keys in http://arclanguage.org/item?id=14244.

-----

1 point by Pauan 5114 days ago | link

Oh, that makes a bit more sense. Technically speaking, Arc's case expression is sorta like guards, though, in the sense of dispatching to different code depending on a condition. You're right that wart's :case is essentially a super nice version of `case`.

Also, what's wrong with `switch` whining? :P This is a JavaScript forum, right? Oh wait...

-----