But can it dispatch based on the return value?
But can it dispatch based on the return value?
Mind you - its 20 years since I was paid to write Lisp/CLOS so take this with a grain of salt :-)
http://stackoverflow.com/questions/22740178/clojure-dispatch...
(The Haskell purists point out that the dispatch isn't determined at compile time - form your own opinion about whether they have a point).
The return type isn't enforced, incidentally:
user> (defn ^String my-function [^Integer foo] (apply concat (repeat foo "x")))
#'user/my-function
user> (my-function 3)
(\x \x \x)
Whoops, I meant (apply str ...).You also can't use the metadata on a function introduced by (defn ^ReturnType ...) in more or less any useful way, if you don't have a reference to the var itself. If you pass it to a function, you basically can't recover it:
user> (defn do-something-interesting-with-return-type [f] (println "here's my metadata:" (meta f)))
#'user/do-something-interesting-with-return-type
user> (do-something-interesting-with-return-type my-function)
here's my metadata: nil
nil
Gone!You can tell that something fishy is going on in the SO answer because the author has to pass in the function by name, #'return-string.
Are there examples where this actually leads to clearer code in the wild?
The bounds of a bounded type:
minBound :: Bounded a => a
maxBound :: Bounded a => a
Converting an integer to an enumerated type: toEnum :: Enum a => Int -> a
Casting between numeric types: fromIntegral :: (Integral a, Num b) -> a -> b
Parsing a string into a value: read :: Read a => String -> a
An empty instance of a container that can be concatenated: mempty :: Monoid m => m
An effectful action that simply returns a constant value: return :: Monad m => a -> m a
It leads to clearer code because you don’t need to specify so many types—either it’s clear (and inferable) from context, or you want to write generic code. For example, with Monoid (mempty & mappend) I can implement a function called mconcat, which concatenates a list of stuff: mconcat :: Monoid m => [m] -> m
mconcat = foldr mappend mempty
Now I can join a list of strings, concatenate a list of lists, union a list of sets, or even the optional versions of any of those: mconcat [Just "foo", Nothing, Nothing, Just "bar"]
=
Just "foobar" name :: constraints_for_type => type_of_name
says "Thing named 'name' has type 'type_of_name' with constraints 'constraints_for_type'"So,
minBound :: Bounded a => a
maxBound :: Bounded a => a
"minBound has type 'a' for any 'a', so long as 'a' is an instance of Bounded"These two are more values than functions, but the polymorphism happens the same way. If you use them where some particular bounded type is expected, that's the type they evaluate to. If you use them where an unbounded type is expected, you'll get a compile error.
----
toEnum :: Enum a => Int -> a
"toEnum has type 'Int -> a' (that is, a function from 'Int' to 'a') for any type 'a', provided that 'a' is an instance of Enum"i.e., Convert an int to the Enum type that the context is asking for.
----
fromIntegral :: (Integral a, Num b) => a -> b
"fromIntegral has type 'a -> b' (that is, a function from 'a' to 'b') for any types 'a' and 'b' where 'a' is an integral type and 'b' is a numeric type"The tuple syntax just means that all these constraints need to apply. I'm not actually certain why the syntax requires it.
----
read :: Read a => String -> a
"read has type 'String -> a' for any type 'a', provided that 'a' is an instance of Read"----
mempty :: Monoid m => m
"mempty has type 'm' for any type 'm' (different letter is just stylistic - m for monoid), provided that 'm' is an instance of Monoid"This is particularly interesting when you start doing polymorphic things with fold and friends.
----
return :: Monad m => a -> m a
"return has type 'a -> m a', so long as 'm' is a Monad"Here we see a "higher-kinded type" - m is a function at the type level that takes a type argument and produces another type, like a C++ template.
e.g. List parameterized by Integer gives us a List of Integers (List is spelled [] in Haskell)
----
mconcat :: Monoid m => [m] -> m
"mconcat is a function from any 'list of m' to a single 'm', provided 'm' is an instance of Monoid" mconcat = foldr mappend mempty
"we define mconcat to be the right fold of mappend over the list, using mempty as our initial value"I am curious on minBound and maxBound. They seem to be the same... What distinguishes them? Or, you are just saying these are two values that are defined. And they can only be given a value of a type that can be bounded?
Also, thanks for expanding!
So the Bounded typeclass is defined as
class Bounded a where
minBound :: a
maxBound :: a
If you have a function and you want the argument to be Bounded you write allLessThan :: (Enum a, Bounded a)=> a -> [a]
allLessThan x = [minBound..x]
Here `a` is a generic type. That implements the Enum and Bounded typeclass. You could just write `allLessThan x = [minBound..x]` and Haskell with infer the type classes by itself.To implement the typeclass you use `instance`, like so:
data MyType = A | B | C | D
instance Bounded MyType where
minBound = A
maxBound = DSo in the case of minBound and maxBound in particular, you'd define them appropriately for a particular type when you declare a type to be Bounded.
trait Bounded {
fn min_bound() -> Self;
fn max_bound() -> Self;
}
Which can be used like so. Using type inference. fn add_min_to_set<T:Bounded, S:MutableSet<T>>(set: &mut S) {
set.insert(Bounded::min_bound())
}
Or you can be more explicit let mut i: u8 = Bounded::min_bound();
while i < Bounded::max_bound() { println!("{}", i); i += 1; }
Edit: spacing, s/++/+= 1/Can you explain this with an example of what the syntax would look like, along with a description of the fantasy semantics?
Of course if a specializable argument to a CLOS generic function call is itself a function call expression, then it is evaluated to its return value, and the type of that value is dispatched on.
The return value of the generic function itself doesn't dispatch anywhere; return "dispatches" to the caller.
We can think about dispatching a different method based on some type which the caller expects. A way to do it in CLOS is to add an extra parameter, perhaps a class symbol. CLOS can dispatch on symbols via the (eql ...) specialization, so we can do something like (foo 'integer arg) to call that specialization of foo which returns integers. This won't be deduced from context; evaluation of a form is context free in ordinary Lisp. Doing that sort of thing requires a code walker to transform the program.
(Not sure why I got a downvote. Is there a possible perception that I wasn't being civil?)
It occurs to me that some dynamic dispatch can take place based on what the method call returns. Suppose we split the computation of the function into two functions: the function proper, and a fixup which is applied to the return value.
The main function's method could have a CLOS :around method wrapped around it which obtains the return value of the primary method (via en explicit (call-next-method) call, and then invokes the fixup method on the argument. The fixup method is then dynamically dispatched on this return value, and possibly provides the ultimate return value.
Of course, this is different from dispatching a method based on the context in which it occurs. Still, no time machine needed.
Splitting functions is an interesting idea, though unless you're passing in context as an argument it'd be another way of dispatching on the same arguments (with, presumably, more user control but probably also more boiler plate).
By the way, it appears I can explain why I had used the word "fantasy": I have been recently involved in some discussions about Gödel's Incompleteness Theorem. That stirred up distant memories of reading Hofstadter, dredging up some of his vocabulary.