In Lathe/arc, I filter on the first argument's type using "ontype" or multiple first arguments' types using "ontypes":
(use-fromwds-as rc (+ lathe-dir* "orc/orc.arc"))
(= my (nspace))
; These are already defined in orc.arc, but I'm redefining them here
; for the sake of example.
(rc:ontypes rc.oiso2 (a b) (cons cons) my.cons
(and (rc.oiso2 car.a car.b) (rc.oiso2 cdr.a cdr.b)))
(rc:ontype rc.otestify () fn my.fn
self)
; General pattern:
;
; (rc:ontypes <rulebook> <parameters>
; (<type of a leading parameter> ...) <rule label (optional)>
; <implementation>)
;
; (rc:ontype <rulebook> <parameters other than the first>
; <type of the first parameter> <rule label (optional)>
; <implementation, where "self" is the first parameter>)
In orc.arc, this comes with an inheritance system I don't use much, but I've found I use 'ontype itself a lot when defining new types. Porting the inheritance system would be hard without porting Lathe's rule precedence system along with it.
In lathe.js, I recently tried adding lathe.instanceofRule( ... ), which defines rules that check the type of their first argument. This was largely a failure, as it clutters up the signature line so much that I end up spreading it over three lines of code, when often I could have had a one-line signature and a one-line explicit check. I haven't given up on it completely, though, since it decreases my token count slightly. :-p
Update: I've been mulling adding 'def :type' to wart for some time, so that I can say (def prn(x) :type 'integer ..) rather than (def prn(x) :case (isa x 'integer) ..).:
In my experience, it's usually the first argument, so I think having a :type would be useful, and in the cases where it isn't, you could fall back to :case (isa ...)