My experience is that sugaring each term intuitively seems unattractive, but worth pursuing.
The interest in persistent storage led me to a workaround to address the problems of mixing expressions and atomic values. To support this, I decided to completely separate them into a type system; this way int, char, etc. are type constructors for integer and character values ββonly. Expression types are formed using the constructor of the polymorphic type Exp; for example, Exp Int refers to a value that is reduced by one step to Int.
The relevance of this issue arises when we consider the assessment. At the basic level, there are primitives that require atomic values: COND, addInt, etc. Some people refer to this as a forced expression, I prefer to see it simply as a cast between values ββof different types.
The challenge is to check if this can be done without requiring explicit reduction directives. One solution is exactly as you suggest: for example, consider coercion as a form of overload.
Say we have a script input: foo x
Then after sugaring it becomes: (coerce foo) (coerce x)
Where, unofficially:
coerce :: a -> b coerce x = REDUCE (cast x) if a and b are incompatible x otherwise
Thus, coercion is either a person or a cast application, where b is the type of return value for a given context.
Now
casting can be considered as a class type method, for example
class Cast a, b where {cast :: a -> b }; -- Β¬:: is an operator, literally meaning: don't cast --(!) is the reduction operator. Perform one stage of reduction. -- Reduce on demand instance Cast Exp c, c where { inline cast = Β¬::(!)(\x::(Exp c) -> Β¬::(!)x) };
Annotations Β¬:: are used to suppress syntactic sugar with duress.
It is suggested that other Cast instances may be introduced to expand the range of transformations, although I have not studied this. As you say, overlapping instances seem necessary.