Task Oriented Programming in using Rinus Plasmeijer Bas
Task Oriented Programming in using Rinus Plasmeijer – Bas Lijnse - Peter Achten Pieter Koopman - Steffen Michels - Jurriën Stutterheim Jan Martin Jansen (NLDA) - Laszlo Domoszlai (ELTE) ASSIGNMENTS - ANSWERS 1
1. Getting started: additional information § § Once you have successfully created an i. Task application and started it, you and anybody can connect to it via a web browser. If you only want to test the application for yourself, it is sufficient to navigate to http: //localhost/. If you want other people to use your application as well (as with the Ligretto case study), then these persons need to navigate to the IP-address of your computer. In both cases, the application uses the web browser for the interaction with your program(s). 2
2. Tasks panel • • The function basic. APIExamples in the main module Basic. APIExamples. icl enumerates all tasks that can be explored by the user(s). An element in this list is specified with: workflow task_tree_spec brief_explanation task in which: • task_tree_spec: is a string of shape g 1/g 2/…/gn/task_name such that the names gi are used to group similar tasks, and task_name is the name of the task • brief_explanation: is a short string that explains the purpose of executing this task • task: a task of any arbitrary result value 3
3. Persistent tasks • • In the i. Task system all tasks are persistent, i. e. : their state is stored in background storage at all times. Consequently, it does not matter if you stop working on that task for a period of time, or simply close your browser and reconnect later. It is possible to completely erase all background storage. On your computer, navigate to the folder in which your main module is located. Suppose your main module is called 'main. icl'. You should find a folder called 'main. exe-data'. Simply remove this folder. For instance, in the case of Basic. APIExamples, navigate to 'i. Tasks. SDKExamples' and remove the folder 'Basic. APIExamples. exe-data' 4
4. Custom types • • • The i. Task system is a domain specific language for defining dynamic workflow style application. It is embedded in the host language Clean. The implementation of the i. Task system uses the generic programming facilities of Clean to automatically create web pages for any custom data type that you can think of. If you create a new type and want the i. Task system to be able to use it, then you need to invoke the generic programming mechanism for that type. In the case of the My. Person type, this looks as follows: derive class i. Task My. Person If you forget to include this derive class declaration in your code you will get swamped with lots of compiler error messages, e. g. : Overloading error [Basic. APIExamples. icl, 66, Start]: "JSONEncode_s" no instance available of type My. Person Overloading error [Basic. APIExamples. icl, 66, Start]: "g. Visualize. Text_s" no instance available of type My. Person Overloading error [Basic. APIExamples. icl, 66, Start]: "g. Headers_s" no instance available of type My. Person Overloading error [Basic. APIExamples. icl, 66, Start]: "g. Grid. Rows_s" no instance available of type My. Person Overloading error [Basic. APIExamples. icl, 66, Start]: "g. Default_s" no instance available of type My. Person … 5
5. Customized view • • • The basic interactive tasks enter. Information, update(Shared)Information, view(Shared)Information, and so on can be fine-tuned by means of a list of options. Currently, these options concern only the way the task value is rendered. In a future version, more option-types will be added to these lists. 6
Intermezzo • • • During the lab sessions we suggested to put new Clean implementation and definition modules in the same folder as the Basic. APIExamples. icl module. It is better practice to store modules that belong to each other in a separate module. In that case you need to add the directory-path to these modules in your project file. In the Clean. IDE this can be done as follows: • choose 'Project: Project Options…' • this opens the project window (right) • choose 'Project Paths' • this opens the project paths window • choose the 'Append…' button • navigate to the folder(s) that you wish to add • choose the 'OK' button to confirm 7
6. Play with HTML • • The Html type is quite a large algebraic data type. You can find its definition in module HTML. dcl. If you are already familiar with html, then you probably recognize most of the tags being coded as data constructors of this type. The Html and Html. Atts types are examples of deeply embedded domain specific languages, in this case for representing html. 8
7. Viewing a Player • • A possible solution to view_player is the following: view_player : : Player -> Task Player view_player = view. Information ("Player " <+++ player. color) [View. With player_view] player_view : : !Player -> Html. Tag player_view player = Table. Tag [Border. Attr "2"] [view_row player. row , view_ligretto player. ligretto , view_hand player. hand ] This definition shows, in left-to-right order, the row, ligretto pile, and the two hand piles (concealed and discarded cards) of the player. 9
8. Viewing the middle piles • A possible solution to view_piles is: view_piles : : [Pile] -> Task [Pile] view_piles = view. Information "Piles" [View. With piles_view] piles_view : : ![Pile] -> Html. Tag piles_view piles = Table. Tag [Border. Attr "2"] [ Tr. Tag [] [ if (is. Empty pile) (Td. Tag [] [Text "empty!"]) (view_card Front (hd pile)) \ pile <- piles ] , Tr. Tag [] [ Td. Tag [] [Text (to. String i)] \ i <- [1. . length piles] ] ] • this definition shows all piles of cards in a single row; underneath each (empty) pile a number is shown as well (1 upto maximum number of piles) 10
9. Add people • • The i. Task system is a distributed, multi-user system. The currently registered users are stored in a shared data source, called user. Accounts (module User. Admin. dcl). This module contains a number of useful functions to get access to the current users (shared data source users), find users with a certain role (users. With. Role), authentication (authenticate. User), and more… 11
10. Invite friends A solution to invite_friends is: invite_friends : : Task [User] invite_friends = enter. Shared. Multiple. Choice "Select friends to play with" [] users >>= friends -> if (not (is. Member (length friends) [1. . 3])) ( view. Information "Oops" [] "number of friends must be 1, 2, or 3" >>| invite_friends ) ( return friends ) • This defines a recursive task that keeps asking the current user to enter 1 upto 3 persons from the users shared data source • 12
11. Shared data source • Solutions to middle_state and player_state shared data sources are: middle_state : : Shared Middle middle_state = shared. Store "middle" (repeat 16 []) player_state : : Color -> Shared Player player_state color = shared. Store ("player " <+++ color) {color = color, row = [], ligretto = [], hand = {conceal = [], discard = []}} • • • The first argument of the shared. Store function is used as unique identifier of the shared data store that is to be created. Therefor, the players are identified using their color. The second argument of the shared. Store function is the initial value of the shared data store. In case of the Ligretto case, the initial player value is overwritten once a game starts, providing the player with cards. 13
12. Viewing the middle piles, now as shared view • Solutions to viewing the piles and players are (changes are underlined): view_player : : Player -> Task Player view_player = view. Shared. Information ("Player " <+++ player. color) [View. With player_view] (player_state player. color) view_piles : : Task [Pile] view_piles = view. Shared. Information "Middle" [View. With piles_view] middle_state • • Instead of looking at a particular value (with view. Information) your application now looks at a shared data source (with view. Shared. Information). As an effect, whenever the shared data source is altered the view is updated is well. Note that the altered view_piles task no longer requires a [Pile] argument, as it gets information from the middle_state shared data source. 14
13. One player plays a game of Ligretto A possible solution to the game function is: game : : Nr. Of. Players Color -> Task Color game nr_of_players color = get random. Int >>= r -> let player = initial_player nr_of_players color (abs r) in set player (player_state color) >>| view_piles ||view_player ||play_cards nr_of_players player obtain a random number from the random. Int shared data source create a new player with shuffled cards and proper row, ligretto, and hand set this new player value in the corresponding shared data source look at the middle piles look at yourself start to play cards according to the rules yet to be defined in play_cards • 15
14. Playing the cards • • The suggested structure for the play_cards task is: play_cards : : Nr. Of. Players Player -> Task Color play_cards nr_of_players player = watch (player_state color >+< middle_state) >>* (watch sds) is a task that continuously keeps an eye on the read value of sds. The task value of (watch sds) is always that of the read value of sds. (sds 1 >+< sds 2) turns two separate shared data sources, sds 1 and sds 2, into one shared data source. The read value of the new shared data source is (r 1, r 2), with ri the read value of sdsi. The write value of the new shared data source is (w 1, w 2), with wi the write value of sdsi. (t >>* steps) is a new task that performs task t while keeping an eye on the task value tv of t. The list steps enumerate all possible ways to continue from here, based on tv. There are four kinds of steps: 1. 2. 3. 4. (On. Value (On. Action action (On. Exception (On. All. Exceptions tf): perform a task depending on (tf tv) tf): provide user with action to do a task depending on (tf tv) tf): catch specific exception e and do task depending on (tf e) tf): catch any exception as str and do task depending on (tf str) 16
14. Playing the cards • • • The suggested structure for the play_cards task is: play_cards : : Nr. Of. Players Player -> Task Color play_cards nr_of_players player = watch (player_state color >+< middle_state) >>* Finish with appropriate actions (On. Action): 1. the player can put a row card on a (new) pile in the middle 2. the player can put the top discard hand card on a (new) pile in the middle 3. the player can move the top three cards from conceal to discard pile 4. the player can shuffle all discards to restart with the conceal pile Finish with an appropriate predicate (On. Value): 5. immediately when the ligretto pile is empty, return the color of the player 17
14. Playing the cards: 1. put a row card on a (new) pile in the middle • Offer the actions to the user (summarize with a list comprehension): [ On. Action ("Play card " <+++ cardnr) []) (play_card nr_of_players player cardnr) \ cardnr <- [1. . nr_of_cards_in_row nr_of_players] ] • The action can only be performed if a middle pile matches: play_card : : Nr. Of. Players Player Int (Task. Value (Player, Middle)) -> Maybe (Task Color) play_card nr_of_players player cardnr (Value (me, middle) _) | not (is. Empty matching_piles) = let (index, pile) = hd matching_piles in Just ( update (update. At index [card : pile]) middle_state >>| set (move_ligretto_card_to_row cardnr me) (player_state player. color) >>| play_cards nr_of_players player ) where card = row_cardnr me matching_piles = [ (index, pile) \ pile <- middle & index <- [0. . ] | card_matches_top_of_pile card pile ] play_card _ _ = Nothing 18
14. Playing the cards: 2. put top discard on a (new) pile in the middle • Offer the action to the user: On. Action (Action "Play hand" []) (play_hand nr_of_players player) • The action can only be performed if the discard pile has a card and a middle pile matches: play_hand : : Nr. Of. Players Player (Task. Value (Player, Middle)) -> Maybe (Task Color) play_hand nr_of_players player (Value (me, middle) _) | is. Just maybe_card && not (is. Empty matching_piles) = let (index, pile) = hd matching_piles in Just ( update (update. At index [card : pile]) middle_state >>| set (remove_top_of_discard me) (player_state player. color) >>| play_cards nr_of_players player ) where maybe_card = top_discard me card = from. Just maybe_card matching_piles = [ (index, pile) \ pile <- middle & index <- [0. . ] | card_matches_top_of_pile card pile] play_hand _ _ _ = Nothing 19
14. Playing the cards: 3. move top 3 concealed cards to discard pile • Offer the action to the user: On. Action (Action "Next hand" []) (next_hand nr_of_players player) • The action can only be performed if the concealed pile is not empty: next_hand : : Nr. Of. Players Player (Task. Value (Player, Middle)) -> Maybe (Task Color) next_hand nr_of_players player (Value (me, middle) _) | not (is. Empty conceal) = Just ( set (swap_discards me) (player_state player. color) >>| play_cards nr_of_players player ) where conceal = me. hand. conceal next_hand _ _ _ = Nothing 20
14. Playing the cards: 4. shuffle all discards and make them concealed • Offer the action to the user: On. Action (Action "Shuffle hand" []) (shuffle nr_of_players player) • The action can only be performed if the concealed pile is empty: shuffle : : Nr. Of. Players Player (Task. Value (Player, Middle)) -> Maybe (Task Color) shuffle nr_of_players player (Value (me, middle) _) | is. Empty conceal = Just ( get random. Int >>= r -> set (shuffle_hand (abs r) me) (player_state player. color) >>| play_cards nr_of_players player ) where conceal = me. hand. conceal shuffle _ _ _ = Nothing 21
14. Playing the cards: 5. return when ligretto pile is empty • Offer the predicate to the step combinator: On. Value player_wins • Return color of player only if the ligretto pile is empty: player_wins : : (Task. Value (Player, Middle)) -> Maybe (Task Color) player_wins (Value (me, middle) _) | is. Empty me. ligretto = Just (return me. color) player_wins _ = Nothing 22
14. Playing the cards: include 1. upto 5. • Thus, the complete solution for the play_cards task is: play_cards : : Nr. Of. Players Player -> Task Color play_cards nr_of_players player = watch (player_state color >+< middle_state) >>* ( [ On. Action ("Play card "<+++cardnr) []) (play_card nr_of_players player cardnr) \ cardnr <- [1. . nr_of_cards_in_row nr_of_players] ] ++ [ On. Action (Action "Play hand" []) (play_hand nr_of_players player) , On. Action (Action "Next hand" []) (next_hand nr_of_players player) , On. Action (Action "Shuffle hand" []) (shuffle nr_of_players player) , On. Value player_wins ] ) 23
15. All players play a game of Ligretto • A possible solution to the main task play_ligretto is: play_ligretto : : Task Color play_ligretto = get current. User >>= me -> invite_friends >>= friends -> set (repeatn 16 []) middle_state >>| let nr_of_players = length friends + 1 in any. Task [ player @: game nr_of_players color \ player <- [me : friends] & color <- colors nr_of_players ] >>= winner -> all. Tasks [ player @: (view. Information "The winner is: " [] winner >>= return) \ player <- [me : friends] ] >>| return winner 24
- Slides: 24