I realize I'm coming late to the party, but you've had two theoretical answers here, and I wanted to provide a practical alternative to chew over. I'm coming at this as a relative Haskell noob who nonetheless has been recently force-marched through the subject of Arrows for a project I'm currently working on.
First, you can productively solve most problems in Haskell without reaching for Arrows. Some notable Haskellers genuinely do not like and do not use them (see here, here, and here for more on this). So if you're saying to yourself "Hey, I don't need these," understand that you may genuinely be correct.
What I found most frustrating about Arrows when I first learned them was how the tutorials on the subject inevitably reached for the analogy of circuitry. If you look at Arrow code -- the sugared variety, at least -- it resembles nothing so much as a Hardware Defnition Language. Your inputs line up on the right, your outputs on the left, and if you fail to wire them all up properly they simply fail to fire. I thought to myself: Really? Is this where we've ended up? Have we created a language so completely high-level that it once again consists of copper wires and solder?
The correct answer to this, as far as I've been able to determine, is: Actually, yes. The killer use case right now for Arrows is FRP (think Yampa, games, music, and reactive systems in general). The problem facing FRP is largely the same problem facing all other synchronous messaging systems: how to wire a continuous stream of inputs into a continuous stream of outputs without dropping relevant information or springing leaks. You can model the streams as lists -- several recent FRP systems use this approach -- but when you have a lot of inputs lists become almost impossible to manage. You need to insulate yourself from the current.
What Arrows allow in FRP systems is the composition of functions into a network while at the same time entirely abstracting away any reference at all to the underlying values being passed by those functions. If you're new to FP, this can be confusing at first, and then mind-blowing when you've absorbed the implications of it. You've only recently absorbed the idea that functions can be abstracted, and how to understand a list like [(*), (+), (-)]
as being of type [(a -> a -> a)]
. With Arrows, you can push the abstraction one layer further.
This additional ability to abstract carries with it its own dangers. For one thing, it can push GHC into corner cases where it doesn't know what to make of your type assumptions. You'll have to be prepared to think at the type level -- this is an excellent opportunity to learn about kinds and RankNTypes and other such topics.
There are also a number of examples of what I'd call "Stupid Arrow Stunts" where the coder reaches for some Arrow combinator just because he or she wants to show off a neat trick with tuples. (Here's my own trivial contribution to the madness.) Feel free to ignore such hot-dogging when you come across it in the wild.
NOTE: As I mentioned above, I'm a relative noob. If I've promulgated any misconceptions above, please feel free to correct me.
Well, I don't know of any baked in ideas that market themselves as representing "function-y" things. But there are several that come close
Categories
If you have a simple function concept that has identities and composition, than you have a category.
class Category c where
id :: c a a
(.) :: c b c -> c a b -> c a c
The disadvantage is that you cannot create a nice category instance with a set of objects (a
, b
, and c
). You can create a custom category class I suppose.
Arrows
If your functions have a notion of products and can inject arbitrary functions, than arrows are for you
class Arrow a where
arr :: (b -> c) -> a b c
first :: a b c -> a (b, d) (c, d)
second :: a b c -> a (d, b) (d, c)
ArrowApply
has a notion of application which looks important for what you want.
Applicatives
Applicatives have your notion of application, I've used them in an AST to represent function application.
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f b -> f c
There are many other ideas. But a common theme is to build up some data structure representing your function, and than pass it to an interpretation function.
This also how many free monads work. I'd suggest poking at these if you're feeling brave, they're a powerful tool for the stuff that you're suggesting and essentially let you build up a datastructure using do
notation and then collapse it into a side effecting computation with different functions. But the beauty is that these functions just operation on the datastructure, and aren't really aware of how you made it all. This is what'd I'd suggest for your example of an interpreter.
Best Answer
Java's
Optional
is essentially equivalent to Haskell'sMaybe
monad, so that's a good starting place for understanding how these "wrapped values" behave.Functors
,Applicatives
andMonads
are ways of modelling some kind of additional behavior that can modify an existing type.Maybe
takes a type, and modifies it so that it can model both a one single instance or no instance of that type.List
modifies a type so that it models a sequence of instances of the given type.Each of these typeclasses provides a way to apply a given function to the wrapped type, respecting the modifications that the wrapping type makes. For example, in Haskell,
Functors
Functors use the
fmap
or<$>
functions (different names for the same thing):fmap
or<$>
Functor f => (a -> b) -> f a -> f b
This takes a function and applies to to the wrapped elements
Applicatives
Applicatives use the
<*>
function:<*>
Applicative f => f (a -> b) -> f a -> f b
This takes a wrapped function and applies it to the wrapped elements
Monads
There are two relevant functions in the Monad typeclass:
return
Monad m => a -> m a
(>>=)
Monad m => m a -> (a -> m b) -> m b
(pronounced "bind")The
return
function bears no resemblance to the one you may be familiar with in C-style languages. It takes a raw, unwrapped value, and wraps it up in the desired monadic type.The bind function lets you temporarily unwrap the inner elements of a Monad and pass them to a function that performs some action that wraps them back UP in the same monad. This can be used with the
return
function in trivial cases:Where it gets interesting is when you have functions to chain together that don't require you to use
return
. I like to usegetLine
andputStrLn
as examples, which have the following signatures using theIO
monad:getLine
IO String
putStrLn
String -> IO ()
You can call these functions like so:
You could also use the
>>=
function to chain a few operations together.