Arc Forumnew | comments | leaders | submitlogin
Wart update: swapped are args to 'isa'
1 point by akkartik 1246 days ago | 6 comments
https://github.com/akkartik/wart/commit/2fa2a3b1c0

Two reasons for this:

a) Since the object is more likely to be an expression than the type we're checking for, it's often most readably placed last. Compare:

  (isa function (car (foo (bar quux))))

  (isa (car (foo (bar quux))) function)
b) Calls look like multi-word functions (http://arclanguage.org/item?id=17233), which is nice.


2 points by rocketnia 1246 days ago | link

Reminds me of this old thread you and I participated in: http://arclanguage.org/item?id=10752

aw: "Here's an "as" macro, which swaps the arguments to "coerce", and quotes the type:"

me: "You could do the same thing with isa. [...] This particular example probably isn't quite as useful; if I have a complex expression, I usually want to use the result itself at some point, rather than just passing it to isa."

you: "Languages like haskell do a better job explicitly encouraging this practice - you can curry functions with frequently-used args."

-----

1 point by akkartik 1246 days ago | link

I have no idea what I was saying there, but it took me a couple of years to truly appreciate both your points :)

-----

2 points by rocketnia 1246 days ago | link

"I have no idea what I was saying there"

Oh, well I got a lot from it. :) I may have quoted you out of context just now, but I think your post was the first time I thought about the idea that optional args and Haskell-like currying may encourage opposite argument orders. If a function argument is going to remain mostly constant, it should go last so it can be optional, but it should also go first so we can curry it away.

Since then, I've come to think the conundrum is largely internal to currying itself: Currying is useful when people have a frequent need to insert their own locally constant value. That's a tenuous balance in itself, and only one half of it conflicts with the general idea of putting stable values toward the end.

(Here's another really old exchange about argument order in Haskell: http://arclanguage.org/item?id=4705)

At the moment, I just say no to currying. I even manually eta-expand higher-order applications so it's easier to see what function signatures I expect, and so they're easier to step through in a debugger.

  // No:
  _.arrAll( arr, _.isString )
  
  // Yes:
  _.arrAll( arr, function ( x, i ) {  // or just ( x )
      return _.isString( x );
  } )

-----

2 points by akkartik 1244 days ago | link

After reading http://arclanguage.org/item?id=4703, I wanted to be able to say both:

  (map f (keep f (sort > ..)))
and:

  (map :over seqs
       (fn (f) ..))
But I can't do that. The ways that wart gets in the way are instructive:

a) First I tried adding an alias to https://github.com/akkartik/wart/blob/2fa2a3b1c0/043list.war...:

  def (map f seq|over)
    ..
But it was easy to forget that I extend map later on: https://github.com/akkartik/wart/blob/2fa2a3b1c0/050list2.wa....

Lesson: when adding param aliases we need to update all later extensions. That seems painful.

b) Even after I identified both places to modify, it's unclear how to deal with this declaration:

  def (map f ... seqs)
I could make it:

  def (map f ... seqs|over)
But then this call combines all args into seqs.

  map :over fs
      (fn (f) ..)
Lesson: rest args by keyword sometimes don't work well. Better to try to find the right names for other args.

Maybe something like this?

  map fs :do (fn (f) ..)
I still can't think of the right keyword to make this readable.

Update: I ended up going with

  map fs :with (fn (f) ..)
(https://github.com/akkartik/wart/commit/537fb6d832)

I also made a change to permit keyword args after rest keyword args:

  map :over fs :with (fn (f) ..)
(https://github.com/akkartik/wart/commit/b3667cda49)

Which of these do people prefer? Any other options?

-----

1 point by akkartik 1246 days ago | link

Looking elsewhere in that thread, one idea for really-as is explicit currying:

  (as.int.nil.0 (arg req "id"))
..or something.

Hmm, at the very least, perhaps we can curry as so that we no longer need functions like int (though I know you prefer recognizers to types ^_^)

  (as.int "34")
Now wart's uniform left-associative precedence truly comes into its own:

  (as.int+arg req 'id)

-----

2 points by rocketnia 1246 days ago | link

"Hmm, at the very least, perhaps ... (as.int "34")"

I had thought about that for Penknife (http://www.arclanguage.org/item?id=13715). I was going to call it "ify" and then use it with the reverse application syntax:

  [int'ify:arg req s.id]
(Note Penknife's uniform left-associative precedence.)

Similarly, 'isa was going to be "ish":

  int'ish.x
However, I still don't see any semantic benefit in associating a coercion function with a type tag. This would have been nothing but a way to shove a bunch of different utilities into one organizational unit, like Java's static methods. And an organization style like this can backfire: If the system is trying to be securable according to the principles of object-capability model, a programmer who passes a whole open-ended bundle of utilities into untrusted code might accidentally expose more privileges than they've bargained for.

---

"though I know you prefer recognizers to types"

I was thinking of bringing that up in response to the OP, but I think I'm mostly on the side of type tags now! I use tables with "type" fields all the time. I like the ability to dump these tables at a REPL, serialize them, use 'iso for deep comparison, or pass them between frames (in JavaScript). This could be a mess if I use more of these features than I plan to support, but "adding" support is as easy as changing my mind. :-p

My old argument for maintaining a dedicated (foo? x) procedure was so that the 'foo? symbol could be namespaced just like any other member of the global scope. But if the type needs to go outside the language runtime and into serialized data or other concurrently executing runtimes (namely, browser frames), then runtime-local tools for namespace management aren't much help.

Fully abstraction-leak-proof namespace management amounts to having a secure notion of which programs have privilege over which namespaces. Cryptography makes it possible to achieve a pragmatic degree of security at the level of serialized data. I've been keeping this in mind as a guideline during my recent module system pursuit.

-----