schemedoc / cookbook

New Scheme Cookbook
https://cookbook.scheme.org
29 stars 3 forks source link

Improve get-type-of #69

Open mnieper opened 1 year ago

mnieper commented 1 year ago

The Cookbook should (as it says) emphasize modern solutions. I wouldn't call the solution

https://github.com/schemedoc/cookbook/blob/76456bca59da1f25fefb21d8bb11a510d7da4138/recipes/get-type-of-object.md?plain=1#L30

modern because it loops through a list effectively built at runtime, preventing compiler optimizations. A "modern" solution would use modern macros. :) Or a hardcoded cond expression where the compiler can inline the primitives.

An independent question: What is a compelling use case for the procedure?

jcubic commented 1 year ago

The use of this procedure is for multiple dispatch in https://github.com/schemedoc/cookbook/issues/60.

Also note, that this has to be runtime I don't think that will be any use of a procedure like this that is optimized by the compiler (But I may be wrong on this).

mnieper commented 1 year ago

The use of this procedure is for multiple dispatch in #60.

Then you would do a double dispatch. First you would dispatch according to the type to retrieve a symbol and then you would dispatch according to the symbol to pick a specialized procedure. This sounds like a recipe for Python-like performance to me...

Also note, that this has to be runtime I don't think that will be any use of a procedure like this that is optimized by the compiler (But I may be wrong on this).

If a compiler sees code like

(cond
  [(pair? ...) ...]
  [(string? ...) ...]
  [(fixnum? ...) ...]
  ...)

it may be able to optimize it with knowledge of its tagging system. In fact, an implementation's internal tagging system is like a type-of procedure.

lassik commented 1 year ago

Is there any problem where you'd use a homegrown type-of in the hot path? It sounds exceedingly unlikely.

lassik commented 1 year ago

What is a compelling use case for the procedure? Interactive use, most likely. For "production use" you'd rely on a built-in (and non-standard) type system or object system of a Scheme implementation.

mnieper commented 12 months ago

When would you need the procedure interactively? Instead of printing the type-of of an object, I can just print the object itself, which also shows me the type.

jcubic commented 12 months ago

It's useful to have a type as a symbol or a string. Because you can create Alist where keys are types and have a logic based on that. Sometimes you don't want to hardcode typechecking to every code you have, and often you need type as value.

(define l (list
            (cons 'string (lambda (x)
                            (display (string-append x " is a string"))
                            (newline)))))

(let ((x "something"))
  ((cdr (assoc (type-of x) l)) x))

The same can be done with hashtable. I'm not sure about Scheme but in language like JavaScript you often change swith..case (scheme cond or case) with assoc table so to make the code cleaner and probably also faster.

Another usecase if you want to throw exception with type of the value you have, if you have some kind of typechecking. The same you don't want in every place use cond with individual typechecking.

mnieper commented 12 months ago

It's useful to have a type as a symbol or a string. Because you can create Alist where keys are types and have a logic based on that. Sometimes you don't want to hardcode typechecking to every code you have, and often you need type as value.

(define l (list
            (cons 'string (lambda (x)
                            (display (string-append x " is a string"))
                            (newline)))))

(let ((x "something"))
  ((cdr (assoc (type-of x) l)) x))

This is IMHO bad code. What the code effectively does is a double dispatch where a single dispatch would suffice. The code first cooks up a symbol by iterating through possible type predicates and then has to iterate through the list of possible symbols.

The same can be done with hashtable. I'm not sure about Scheme but in language like JavaScript you often change swith..case (scheme cond or case) with assoc table so to make the code cleaner and probably also faster.

The compiler would have to understand the double dispatching and recreate a static conditional to get back the efficiency of a hard-coded conditional (compilers know how to optimize switch statements very well).

If you want "cleaner" code, make type-of a macro type-case (or whatever), and don't use runtime dispatch tables.

Another usecase if you want to throw exception with type of the value you have, if you have some kind of typechecking. The same you don't want in every place use cond with individual typechecking.

It is usually better to throw the value and not a symbol representing the type. The value is more informative and you will also get something for user-defined (record) types.