Tangible Value
From HaskellWiki
m (→Composition of TVs) |
(Changed category from "Interfaces" to "User interfaces") |
||
| (20 intermediate revisions not shown.) | |||
| Line 1: | Line 1: | ||
| + | [[Category:User interfaces]] | ||
| + | [[Category:IO]] | ||
| + | [[Category:Arrow]] | ||
| + | [[Category:Libraries]] | ||
| + | [[Category:Packages]] | ||
| + | |||
== Abstract == | == Abstract == | ||
| - | |||
| - | TV is for | + | '''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. This combination makes for software that is ''ready to use and ready to reuse''. |
| - | + | TV can be thought of as a simple functional formulation of the Model-View-Controller pattern. (My thanks to an anonymous ICFP referee for pointing out this connection.) The value part of a TV is the ''model'', and the "interface" part, or "output" as it is called below, is the ''viewer''. Outputs are built up compositionally from other outputs and from inputs (the ''controllers''), as described below. | |
| - | * [http:// | + | |
| - | * | + | Besides this wiki page, here are more ways to learn about TV: |
| - | * | + | * Visit the [http://hackage.haskell.org/package/project-foo Hackage page] for library documentation and to download & install. |
| + | * Or install with <tt>cabal install project-foo</tt>. | ||
| + | * See the use of TV in [[Eros]]. | ||
As of version 0.2, I have moved the GUI functionality out of TV and into a small new package [[GuiTV]]. I moved it out to eliminate the dependency of core TV on [[Phooey]] and hence on [[wxHaskell]], as the latter can be difficult to install. The GUI examples below require [[GuiTV]]. | As of version 0.2, I have moved the GUI functionality out of TV and into a small new package [[GuiTV]]. I moved it out to eliminate the dependency of core TV on [[Phooey]] and hence on [[wxHaskell]], as the latter can be difficult to install. The GUI examples below require [[GuiTV]]. | ||
| - | + | GuiTV (better named "wxTV") is bit-rotten. There is also a very similar [http://hackage.haskell.org/package/GtkTV package to generate Gtk-based GUIs]. | |
| + | |||
| + | I'd love to hear your comments at the [[Talk:TV]] page. | ||
| - | + | == First Example == | |
Here is a tangible reverse function: | Here is a tangible reverse function: | ||
| Line 43: | Line 52: | ||
We'll see [[#The_general_story|later]] that "<hask>runUI</hask>" and "<hask>runIO</hask>" are both type-specialized synonyms for a more general function. | We'll see [[#The_general_story|later]] that "<hask>runUI</hask>" and "<hask>runIO</hask>" are both type-specialized synonyms for a more general function. | ||
| - | + | == Outputs == | |
What I've been calling an "interface" is a value of type <hask>COutput a</hask> for a type <hask>a</hask>. For instance, for <hask>reverseT</hask>, <hask>a</hask> is <hask>String->String</hask>. The reason for the <hask>C</hask> prefix is explained below. At the heart of TV is a small algebra for constructing these outputs. Weve already seen one output function, <hask>oTitle</hask>. Another one is <hask>showOut</hask>, which is an output for all <hask>Show</hask> types. For instance, | What I've been calling an "interface" is a value of type <hask>COutput a</hask> for a type <hask>a</hask>. For instance, for <hask>reverseT</hask>, <hask>a</hask> is <hask>String->String</hask>. The reason for the <hask>C</hask> prefix is explained below. At the heart of TV is a small algebra for constructing these outputs. Weve already seen one output function, <hask>oTitle</hask>. Another one is <hask>showOut</hask>, which is an output for all <hask>Show</hask> types. For instance, | ||
| Line 52: | Line 61: | ||
</haskell> | </haskell> | ||
| - | + | == 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. | + | Just as an output is a way to ''deliver'' (or ''consume'') a value, an "input" is a way to ''obtain'' (or ''produce'') a value. For example, here are two inputs, each specifying an initial value and a value range, and each given a title. |
<haskell> | <haskell> | ||
| Line 62: | Line 71: | ||
</haskell> | </haskell> | ||
| - | Now for the fun part. | + | Now for the fun part. Let's combine the <hask>apples</hask> and <hask>bananas</hask> inputs and the <hask>total</hask> output to make a ''function-valued'' output. |
<haskell> | <haskell> | ||
| Line 89: | Line 98: | ||
</blockquote> | </blockquote> | ||
| - | + | == A variation == | |
Here is an uncurried variation: | Here is an uncurried variation: | ||
| Line 99: | Line 108: | ||
(uncurry (+)) | (uncurry (+)) | ||
</haskell> | </haskell> | ||
| - | However, there's a much more elegant formulation, using | + | However, there's a much more elegant formulation, using <hask>uncurryA</hask> and <hask>$$</hask> from [[DeepArrow]]: |
<haskell> | <haskell> | ||
shoppingPr = uncurryA $$ shopping | shoppingPr = uncurryA $$ shopping | ||
| Line 111: | Line 120: | ||
| style="padding:20px;" | [[Image:shoppingPr.png]] | | style="padding:20px;" | [[Image:shoppingPr.png]] | ||
| style="padding:20px;" | | | style="padding:20px;" | | ||
| - | shopping list: apples: <b><i>8</i></b> | + | shopping list -- uncurried: apples: <b><i>8</i></b> |
bananas: <b><i>5</i></b> | bananas: <b><i>5</i></b> | ||
total: 13 | total: 13 | ||
| Line 117: | Line 126: | ||
</blockquote> | </blockquote> | ||
| - | + | == The general story == | |
| - | TVs, outputs and inputs are not restricted to GUIs and IO. In general, they are parameterized by | + | TVs, outputs, and inputs are not restricted to GUIs and IO. In general, they are parameterized by the mechanics of "transmitting values", i.e., delivering ("sinking") output and gathering ("sourcing") input. |
<haskell> | <haskell> | ||
| - | data | + | data Input src a |
| - | data | + | data Output src snk a |
| - | type TV | + | type TV src snk a |
</haskell> | </haskell> | ||
| - | + | The "sources" will be [[applicative functor]]s (AFs), and the "sinks" will be contravariant functors. | |
| - | < | + | In the examples above, we've used two different mechanisms, namely [[Phooey]]'s <hask>UI</hask> AF and <hask>IO</hask>. The sinks are counterparts <hask>IU</hask> and <hask>OI</hask>. |
| - | + | ||
| - | </ | + | |
| - | + | The functions <hask>runUI</hask> and <hask>runIO</hask> used in examples above are simply type-specialized synonyms for [http://hackage.haskell.org/package/TV/latest/doc/html/Interface-TV.html#v%3ArunTV <hask>runTV</hask>]. | |
| - | + | ||
| - | The functions <hask>runUI</hask> and <hask>runIO</hask> used in examples above are simply type-specialized synonyms for [http:// | + | |
<haskell> | <haskell> | ||
| - | runUI :: TV UI a -> IO () | + | runUI :: TV UI IU a -> IO () |
runUI = runTV | runUI = runTV | ||
| - | runIO :: TV | + | runIO :: TV IO OI a -> IO () |
runIO = runTV | runIO = runTV | ||
</haskell> | </haskell> | ||
| - | + | == Common Ins and Outs == | |
| - | The examples <hask>reverseT</hask> and <hask>shoppingT</hask> above used not only the generic <hask>Output</hask> and <hask>Input</hask> operations, but also some operations that apply to | + | The examples <hask>reverseT</hask> and <hask>shoppingT</hask> above used not only the generic <hask>Output</hask> and <hask>Input</hask> operations, but also some operations that apply to AFs having a few methods for sourcing and sinking a few common types (strings, readables, showables, and booleans). The type constructors <hask>CInput</hask>, <hask>COutput</hask>, and <hask>CTV</hask> are universally quantified over sources and sinks having the required methods. |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
<haskell> | <haskell> | ||
| - | type CInput | + | type CInput a = forall src. |
| - | type COutput a = | + | (CommonIns src) => Input src a |
| - | type CTV | + | type COutput a = forall src snk. |
| + | (CommonIns src, CommonOuts snk) => Output src snk a | ||
| + | type CTV a = forall src snk. | ||
| + | (CommonIns src, CommonOuts snk) => TV src snk a | ||
</haskell> | </haskell> | ||
| - | + | == Sorting examples == | |
| - | Here's a sorting TV (see [http:// | + | Here's a sorting TV (see [http://hackage.haskell.org/packages/archive/TV/latest/doc/html/Interface-TV-Common.html#v:interactLineRS <hask>interactLineRS</hask>]), tested with <hask>runUI</hask>: |
<blockquote> | <blockquote> | ||
| Line 167: | Line 171: | ||
<haskell> | <haskell> | ||
sortT :: (Read a, Show a, Ord a) => CTV ([a] -> [a]) | sortT :: (Read a, Show a, Ord a) => CTV ([a] -> [a]) | ||
| - | sortT = tv (oTitle "sort" $ | + | sortT = tv (oTitle "sort" $ interactLineRS []) sort |
</haskell> | </haskell> | ||
|- | |- | ||
| Line 178: | Line 182: | ||
: <hask>runUI (sortT :: CTV ([String] -> [String]))</hask> | : <hask>runUI (sortT :: CTV ([String] -> [String]))</hask> | ||
| - | + | == 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. | 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. | ||
| Line 188: | Line 192: | ||
| style="padding-right:2em;" | | | style="padding-right:2em;" | | ||
<haskell> | <haskell> | ||
| - | + | wordsT :: CTV (String -> [String]) | |
| - | + | wordsT = tv ( oTitle "function: words" $ | |
| - | oLambda (iTitle " | + | oLambda (iTitle "sentence in" defaultIn) |
| - | (oTitle " | + | (oTitle "words out" defaultOut)) |
| - | + | words | |
</haskell> | </haskell> | ||
|- | |- | ||
| Line 205: | Line 209: | ||
unwordsT :: CTV ([String] -> String) | unwordsT :: CTV ([String] -> String) | ||
unwordsT = tv ( oTitle "function: unwords" $ | unwordsT = tv ( oTitle "function: unwords" $ | ||
| - | + | oLambda (iTitle "words in" defaultIn) | |
| - | + | (oTitle "sentence out" defaultOut)) | |
unwords | unwords | ||
</haskell> | </haskell> | ||
| Line 233: | Line 237: | ||
</blockquote> | </blockquote> | ||
| - | The operator "[http:// | + | The operator "[http://hackage.haskell.org/package/DeepArrow/latest/doc/html/Control-Arrow-DeepArrow.html#v%3A-%3E%7C <hask>->|</hask>]" is part of a general approach to value composition from [[DeepArrow]]. |
| - | == | + | == Transmission-specific interfaces == |
| - | While some interfaces can be implemented for different | + | While some interfaces can be implemented for different means of transmission, others are more specialized. |
| - | + | === GUIs === | |
| - | Here are inputs for our shopping example above that specifically work with [[Phooey]]'s UI | + | Here are inputs for our shopping example above that specifically work with [[Phooey]]'s UI applicative functor. |
<haskell> | <haskell> | ||
applesU, bananasU :: Input UI Int | applesU, bananasU :: Input UI Int | ||
| Line 266: | Line 270: | ||
'''Note''': We could define other type classes, besides <hask>CommonInsOuts</hask>. For instance, <hask>islider</hask> could be made a method of a <hask>GuiArrow</hask> class, allowing it to be rendered in different ways with different GUI toolkits or even using HTML and Javascript. | '''Note''': We could define other type classes, besides <hask>CommonInsOuts</hask>. For instance, <hask>islider</hask> could be made a method of a <hask>GuiArrow</hask> class, allowing it to be rendered in different ways with different GUI toolkits or even using HTML and Javascript. | ||
| - | + | === IO === | |
| - | We can use <hask>IO</hask> operations in TV interfaces | + | We can use <hask>IO</hask> operations in TV interfaces. The corresponding sink is <hask>OI</hask>, defined in [[TypeCompose]]. TV provides a few functions in its [http://hackage.haskell.org/package/TV/latest/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 | + | interactOut :: Output IO OI (String -> String) |
interactOut = oLambda contentsIn stringOut | interactOut = oLambda contentsIn stringOut | ||
</haskell> | </haskell> | ||
| Line 276: | Line 280: | ||
Assuming we have a file <tt>"test.txt"</tt> containing some lines of text, we can use it to test string transformations. | Assuming we have a file <tt>"test.txt"</tt> containing some lines of text, we can use it to test string transformations. | ||
<haskell> | <haskell> | ||
| - | testO :: Output | + | testO :: Output IO OI (String -> String) |
testO = oLambda (fileIn "test.txt") defaultOut | testO = oLambda (fileIn "test.txt") defaultOut | ||
</haskell> | </haskell> | ||
| - | First, | + | First, let's define higher-order functions that apply another function to the lines or on the words of a string. |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
<haskell> | <haskell> | ||
onLines, onWords :: ([String] -> [String]) -> (String -> String) | onLines, onWords :: ([String] -> [String]) -> (String -> String) | ||
| - | onLines = | + | onLines f = unlines . f . lines |
| - | onWords = | + | onWords f = unwords . f . words |
</haskell> | </haskell> | ||
| - | + | Next, specializations that operate on ''each'' line or word: | |
<haskell> | <haskell> | ||
perLine,perWord :: (String -> String) -> (String -> String) | perLine,perWord :: (String -> String) -> (String -> String) | ||
| Line 348: | Line 346: | ||
</blockquote> | </blockquote> | ||
| - | There are more examples [http:// | + | There are more examples [http://code.haskell.org/~conal/code/TV/src/Examples.hs in the TV repository] and in the [http://code.haskell.org/~conal/code/GuiTV/src/Examples.hs in the GuiTV repository]. See also "[http://journal.conal.net/#%5B%5Bseparating%20IO%20from%20logic%20--%20example%5D%5D separating IO from logic -- example]". |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
Current revision
Contents |
1 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. This combination makes for software that is ready to use and ready to reuse.
TV can be thought of as a simple functional formulation of the Model-View-Controller pattern. (My thanks to an anonymous ICFP referee for pointing out this connection.) The value part of a TV is the model, and the "interface" part, or "output" as it is called below, is the viewer. Outputs are built up compositionally from other outputs and from inputs (the controllers), as described below.
Besides this wiki page, here are more ways to learn about TV:
- Visit the Hackage page for library documentation and to download & install.
- Or install with cabal install project-foo.
- See the use of TV in Eros.
As of version 0.2, I have moved the GUI functionality out of TV and into a small new package GuiTV. I moved it out to eliminate the dependency of core TV on Phooey and hence on wxHaskell, as the latter can be difficult to install. The GUI examples below require GuiTV.
GuiTV (better named "wxTV") is bit-rotten. There is also a very similar package to generate Gtk-based GUIs.
I'd love to hear your comments at the Talk:TV page.
2 First Example
Here is a tangible reverse function:
reverseT :: CTV (String -> String) reverseT = tv (oTitle "reverse" defaultOut) reverse
Running:
We'll see later that "
runUI reverseT runIO reverseT![]()
*Examples> runIO reverseT reverse: Hello, reversible world. .dlrow elbisrever ,olleH *Examples>
3 Outputs
What I've been calling an "interface" is a value of typetotal :: Show a => COutput a total = oTitle "total" showOut
4 Inputs and function-valued outputs
Just as an output is a way to deliver (or consume) a value, an "input" is a way to obtain (or produce) 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
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 list: apples: 8 bananas: 5 total: 13
5 A variation
Here is an uncurried variation:
shoppingPr :: CTV ((Int,Int) -> Int) shoppingPr = tv ( oTitle "shopping list -- uncurried" $ oLambda (iPair apples bananas) total ) (uncurry (+))
shoppingPr = uncurryA $$ shopping
Running:
runUI shoppingPr runIO shoppingPr![]()
shopping list -- uncurried: apples: 8 bananas: 5 total: 13
6 The general story
TVs, outputs, and inputs are not restricted to GUIs and IO. In general, they are parameterized by the mechanics of "transmitting values", i.e., delivering ("sinking") output and gathering ("sourcing") input.
data Input src a data Output src snk a type TV src snk a
The "sources" will be applicative functors (AFs), and the "sinks" will be contravariant functors.
In the examples above, we've used two different mechanisms, namely Phooey'srunUI :: TV UI IU a -> IO () runUI = runTV runIO :: TV IO OI a -> IO () runIO = runTV
7 Common Ins and Outs
The examplestype CInput a = forall src. (CommonIns src) => Input src a type COutput a = forall src snk. (CommonIns src, CommonOuts snk) => Output src snk a type CTV a = forall src snk. (CommonIns src, CommonOuts snk) => TV src snk a
8 Sorting examples
Here's a sorting TV (see <div class="inline-code">Note that
sortT :: (Read a, Show a, Ord a) => CTV ([a] -> [a]) sortT = tv (oTitle "sort" $ interactLineRS []) sort![]()
- runUI (sortT :: CTV ([String] -> [String]))
9 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
wordsT :: CTV (String -> [String]) wordsT = tv ( oTitle "function: words" $ oLambda (iTitle "sentence in" defaultIn) (oTitle "words out" defaultOut)) words![]()
Finally, compose
unwordsT :: CTV ([String] -> String) unwordsT = tv ( oTitle "function: unwords" $ oLambda (iTitle "words in" defaultIn) (oTitle "sentence out" defaultOut)) unwords![]()
sortWordsT :: CTV (String -> String) sortWordsT = wordsT ->| sortT ->| unwordsT
Running:
The operator "<div class="inline-code">
runUI sortWordsT runIO sortWordsT![]()
sentence in: The night Max wore his wolf suit sentence out: Max The his night suit wolf wore
10 Transmission-specific interfaces
While some interfaces can be implemented for different means of transmission, others are more specialized.
10.1 GUIs
Here are inputs for our shopping example above that specifically work with Phooey's UI applicative functor.
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:
Note: We could define other type classes, besides
code runUI rendering tv shoppingUO (+)![]()
uncurryA $$ tv shoppingUO (+)![]()
10.2 IO
We can useinteractOut :: Output IO OI (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 IO OI (String -> String) testO = oLambda (fileIn "test.txt") defaultOut
First, let's 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 f = unlines . f . lines onWords f = unwords . f . words
Next, specializations that operate on each line or word:
perLine,perWord :: (String -> String) -> (String -> String) perLine f = onLines (map f) perWord f = onWords (map f)
Some examples:
string function f runIO (tv test0 f) idTo 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 reverse ekalB mailliW - .ruoh na ni ytinretE dnA dnah ruoy fo mlap eht ni ytinifnI dloH ,rewolF dliW a ni nevaeH a dnA dnaS fo niarG a ni dlroW a ees oT 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 perLine 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 - perLine (perWord reverse) oT ees a dlroW ni a niarG fo dnaS dnA a nevaeH ni a dliW ,rewolF dloH ytinifnI ni eht mlap fo ruoy dnah dnA ytinretE ni na .ruoh - mailliW ekalB
There are more examples in the TV repository and in the in the GuiTV repository. See also "separating IO from logic -- example".
Categories: User interfaces | IO | Arrow | Libraries | Packages









