Difference between revisions of "Tangible Value"

From HaskellWiki
Jump to navigation Jump to search
m
Line 243: Line 243:
 
==== IO ====
 
==== IO ====
   
We can use <hask>IO</hask> operations in TV interfaces via the <hask>KIO</hask> arrow (defined as <hask>Kleisli IO</hask>). TV provides a few functions in its [http://darcs.haskell.org/packages/TV/doc/html/Graphics-UI-TV-IO.html <hask>IO</hask> module], including a close counterpart to the standard <hask>interact</hask> function.
+
We can use <hask>IO</hask> operations in TV interfaces via the <hask>KIO</hask> arrow (defined as <hask>Kleisli IO</hask>). TV provides a few functions in its [http://darcs.haskell.org/packages/TV/doc/html/Interface-TV-IO.html <hask>IO</hask> module], including a close counterpart to the standard <hask>interact</hask> function.
 
<haskell>
 
<haskell>
 
interactOut :: Output KIO (String -> String)
 
interactOut :: Output KIO (String -> String)
Line 255: Line 255:
 
</haskell>
 
</haskell>
   
First, here's a handy wrapping pattern (found in TV's [http://darcs.haskell.org/packages/TV/doc/html/Graphics-UI-TV-Misc.html <hask>Misc</hask> module]). For instance, <hask>wrapF show read</hask> turns a string function into value function.
+
First, here's a handy wrapping pattern (found in TV's [http://darcs.haskell.org/packages/TV/doc/html/Interface-TV-Misc.html <hask>Misc</hask> module]). For instance, <hask>wrapF show read</hask> turns a string function into value function.
 
<haskell>
 
<haskell>
 
wrapF :: (c->d) -> (a->b) -> ((b->c) -> (a->d))
 
wrapF :: (c->d) -> (a->b) -> ((b->c) -> (a->d))

Revision as of 07:45, 17 January 2007

Abstract

TV is a library for composing tangible values ("TVs"), i.e., values that carry along external interfaces. In particular, TVs can be composed to create new TVs, and they can be directly executed with a friendly GUI, a process that reads and writes character streams, or many other kinds interfaces. Values and interfaces are combined for direct use, and separable for composition.

TV is for creating software that is ready to use and ready to reuse.

Beside this page, here are some ways to explore TV:

Tangible values

As a first example, here is a tangible reverse function:

reverseT :: CTV (String -> String)
reverseT = tv (oTitle "reverse" defaultOut) reverse

The tv function combines an interface and a value. In this example, the interface is the default for string functions, wrapped with the title "reverse".

TV "interfaces" are more than just GUIs. Here are two different renderings of reverseT. (User input is shown underlined in the runIO version).

Running:

runUI reverseT runIO reverseT
ReverseT.png

*Examples> runIO reverseT reverse: Hello, reversible world. .dlrow elbisrever ,olleH *Examples>

Outputs

What I've been calling an "interface" is a value of type COutput a for a type a (for reverseT, a == String->String). (The reason for the C prefix is explained below.) At the heart of TV is a small algebra for constructing these outputs. Weve already seen one output function, oTitle. Another one is showOut, which is an output for all Show types. For instance,

total :: Show a => COutput a
total = oTitle "total" showOut

Inputs and function-valued outputs

Just as an output is a way to deliver a value, an "input" is a way to obtain a value. For example, here are two inputs, each specifying an initial value and a value range, and each given a title.

apples, bananas :: CInput Int
apples  = iTitle "apples"  defaultIn
bananas = iTitle "bananas" defaultIn

Now for the fun part. Lets combine the apples and bananas inputs and the total output to make a function-valued output.

shoppingO :: COutput (Int -> Int -> Int)
shoppingO = oTitle "shopping list" $
            oLambda apples (oLambda bananas total)

And a TV:

shopping :: CTV (Int -> Int -> Int)
shopping = tv shoppingO (+)

Running:

runUI shopping runIO shopping
Shopping.png

shopping list: apples: 8 bananas: 5 total: 13

A variation

Here is an uncurried variation:

shoppingPr :: CTV ((Int,Int) -> Int)
shoppingPr = tv ( oTitle "shopping list -- uncurried" $ 
                  oLambda (iPair apples bananas) total )
                (uncurry (+))

However, there's a much more elegant formulation, using the uncurryA method and $$ operator from DeepArrow.

shoppingPr = uncurryA $$ shopping

Running:

runUI shoppingPr runIO shoppingPr
ShoppingPr.png

shopping list: apples: 8 bananas: 5 total: 13

The general story

TVs, outputs and inputs are not restricted to GUIs and IO. In general, theyre parameterized by an arrow.

data Output (~>) a
data Input  (~>) a
type TV     (~>) a

In the examples above, we've used two different arrows, namely Phooey's UI arrow and KIO, defined simply as

type KIO = Kleisli IO

Any other monad may be used in place of IO, and other arrows in place of UI and KIO.

Common Ins and Outs

The examples reverseT and shoppingT above used not only the generic Output and Input operations, but also some operations that apply to arrows belonging to the CommonInsOuts class, which includes UI and KIO. The type constructors CInput, COutput, and CTV are universally quantified over CommonInsOuts arrows.

type Common f a = forall (~>). CommonInsOuts (~>) => f (~>) a
type CInput  a = Common Input a
type COutput a = Common Output a
type CTV     a = Common TV a

Sorting examples

Here's a sorting TV:

sortT :: (Read a, Show a, Ord a) => CTV ([a] -> [a])
sortT = tv (oTitle "sort" $ interactRSOut []) sort

Since sortT is polymorphic in value, you may want to type-annotate its uses, e.g.,

runUI (sortT :: CTV ([String] -> [String]))

Otherwise, a will default to Int.

With runUI:

SortT.png

Composition of TVs

So far, we done a little composition of interfaces and combined them with values to construct TVs. Now let's look at composition of TVs.

First, wrap up the words and unwords functions:

wordsT :: CTV (String -> [String]) 
wordsT = tv ( oTitle "function: words" $
              oLambda (iTitle "sentence in" defaultIn)
                      (oTitle "words out"   defaultOut))
            words
WordsT.png
unwordsT :: CTV ([String] -> String) 
unwordsT = tv ( oTitle "function: unwords" $
                oLambda (iTitle "words in"     defaultIn)
                        (oTitle "sentence out" defaultOut))
              unwords
UnwordsT.png

Finally, compose wordsT, unwordsT, and sortT

sortWordsT :: CTV (String -> String)
sortWordsT = wordsT ->| sortT ->| unwordsT

Running:

runUI sortWordsT runIO sortWordsT
SortWordsT.png

*Examples> runIO sortWordsT sentence in: The night Max wore his wolf suit sentence out: Max The his night suit wolf wore

The operator "->|" is part of a general approach to value composition from DeepArrow.

Arrow-specific interfaces

While some interfaces can be implemented for different kinds of interfaces, others are more specialized.

GUIs

Here are inputs for our shopping example above that specifically work with Phooey's UI arrow.

applesU, bananasU :: Input UI Int
applesU  = iTitle "apples"  (islider 3 (0,10))
bananasU = iTitle "bananas" (islider 7 (0,10))

shoppingUO :: Output UI (Int -> Int -> Int)
shoppingUO = oTitle "shopping list" $ oLambda applesU (oLambda bananasU total)

We can then make curried and uncurried TVs:

code runUI rendering
tv shoppingUO (+) ShoppingU.png
uncurryA $$ tv shoppingUO (+) ShoppingPrU.png

Note: We could define other type classes, besides CommonInsOuts. For instance, islider could be made a method of a GuiArrow class, allowing it to be rendered in different ways with different GUI toolkits or even using HTML and Javascript.

IO

We can use IO operations in TV interfaces via the KIO arrow (defined as Kleisli IO). TV provides a few functions in its IO module, including a close counterpart to the standard interact function.

interactOut :: Output KIO (String -> String)
interactOut = oLambda contentsIn stringOut

Assuming we have a file "test.txt" containing some lines of text, we can use it to test string transformations.

testO :: Output KIO (String -> String)
testO = oLambda (fileIn "test.txt") defaultOut

First, here's a handy wrapping pattern (found in TV's Misc module). For instance, wrapF show read turns a string function into value function.

wrapF :: (c->d) -> (a->b) -> ((b->c) -> (a->d))
wrapF after before f = after . f . before

Using wrapF it's easy to define higher-order functions that apply another function to the lines or on the words of a string.

onLines, onWords :: ([String] -> [String]) -> (String -> String)
onLines = wrapF unlines lines
onWords = wrapF unwords words

Some examples:

string function f runIO (tv test0 f)
id

To see a World in a Grain of Sand And a Heaven in a Wild Flower, Hold Infinity in the palm of your hand And Eternity in an hour. - William Blake

onLines reverse

- William Blake And Eternity in an hour. Hold Infinity in the palm of your hand And a Heaven in a Wild Flower, To see a World in a Grain of Sand

onLines (map reverse)

dnaS fo niarG a ni dlroW a ees oT ,rewolF dliW a ni nevaeH a dnA dnah ruoy fo mlap eht ni ytinifnI dloH .ruoh na ni ytinretE dnA ekalB mailliW -

There are more examples in the TV repository.