cloudchor (empty) → 0.1.0.0
raw patch · 35 files changed
+3549/−0 lines, 35 filesdep +asyncdep +basedep +cloudchor
Dependencies added: async, base, cloudchor, containers, http-client, mtl, random, servant, servant-client, servant-server, sop-core, split, template-haskell, time, transformers, unordered-containers, warp
Files
- LICENSE-HasChor.txt +32/−0
- LICENSE.txt +373/−0
- README.md +21/−0
- bench/Benchmark.hs +73/−0
- bench/Choreos/Bookseller.hs +54/−0
- bench/Choreos/CleanRoom.hs +160/−0
- bench/Choreos/Kvs.hs +102/−0
- bench/Main.hs +22/−0
- cloudchor.cabal +290/−0
- examples/HasChor/bank-2pc/Main.hs +105/−0
- examples/HasChor/bookseller-0-network/Main.hs +55/−0
- examples/HasChor/bookseller-1-simple/Main.hs +97/−0
- examples/HasChor/bookseller-2-higher-order/Main.hs +93/−0
- examples/HasChor/bookseller-3-loc-poly/Main.hs +69/−0
- examples/HasChor/diffiehellman/Main.hs +91/−0
- examples/HasChor/karatsuba/Main.hs +93/−0
- examples/HasChor/kvs-1-simple/Main.hs +103/−0
- examples/HasChor/kvs-2-primary-backup/Main.hs +125/−0
- examples/HasChor/kvs-3-higher-order/Main.hs +156/−0
- examples/HasChor/kvs-4-loc-poly/Main.hs +196/−0
- examples/HasChor/mergesort/Main.hs +118/−0
- examples/HasChor/playground/Main.hs +31/−0
- examples/HasChor/quicksort/Main.hs +56/−0
- examples/HasChor/ring-leader/Main.hs +82/−0
- examples/clean-room/Main.hs +170/−0
- examples/unsoundness/Main.hs +102/−0
- src/Choreography.hs +43/−0
- src/Choreography/Choreo.hs +190/−0
- src/Choreography/Location.hs +44/−0
- src/Choreography/Location/Multi.hs +79/−0
- src/Choreography/Network.hs +61/−0
- src/Choreography/Network/Http.hs +112/−0
- src/Choreography/Network/Local.hs +51/−0
- src/Choreography/Network/LocalMsgCount.hs +57/−0
- src/Control/Monad/Freer.hs +43/−0
+ LICENSE-HasChor.txt view
@@ -0,0 +1,32 @@+MIT License++Copyright (c) 2022, Gan Shen++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Gan Shen nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ LICENSE.txt view
@@ -0,0 +1,373 @@+Mozilla Public License Version 2.0+==================================++1. Definitions+--------------++1.1. "Contributor"+ means each individual or legal entity that creates, contributes to+ the creation of, or owns Covered Software.++1.2. "Contributor Version"+ means the combination of the Contributions of others (if any) used+ by a Contributor and that particular Contributor's Contribution.++1.3. "Contribution"+ means Covered Software of a particular Contributor.++1.4. "Covered Software"+ means Source Code Form to which the initial Contributor has attached+ the notice in Exhibit A, the Executable Form of such Source Code+ Form, and Modifications of such Source Code Form, in each case+ including portions thereof.++1.5. "Incompatible With Secondary Licenses"+ means++ (a) that the initial Contributor has attached the notice described+ in Exhibit B to the Covered Software; or++ (b) that the Covered Software was made available under the terms of+ version 1.1 or earlier of the License, but not also under the+ terms of a Secondary License.++1.6. "Executable Form"+ means any form of the work other than Source Code Form.++1.7. "Larger Work"+ means a work that combines Covered Software with other material, in+ a separate file or files, that is not Covered Software.++1.8. "License"+ means this document.++1.9. "Licensable"+ means having the right to grant, to the maximum extent possible,+ whether at the time of the initial grant or subsequently, any and+ all of the rights conveyed by this License.++1.10. "Modifications"+ means any of the following:++ (a) any file in Source Code Form that results from an addition to,+ deletion from, or modification of the contents of Covered+ Software; or++ (b) any new file in Source Code Form that contains any Covered+ Software.++1.11. "Patent Claims" of a Contributor+ means any patent claim(s), including without limitation, method,+ process, and apparatus claims, in any patent Licensable by such+ Contributor that would be infringed, but for the grant of the+ License, by the making, using, selling, offering for sale, having+ made, import, or transfer of either its Contributions or its+ Contributor Version.++1.12. "Secondary License"+ means either the GNU General Public License, Version 2.0, the GNU+ Lesser General Public License, Version 2.1, the GNU Affero General+ Public License, Version 3.0, or any later versions of those+ licenses.++1.13. "Source Code Form"+ means the form of the work preferred for making modifications.++1.14. "You" (or "Your")+ means an individual or a legal entity exercising rights under this+ License. For legal entities, "You" includes any entity that+ controls, is controlled by, or is under common control with You. For+ purposes of this definition, "control" means (a) the power, direct+ or indirect, to cause the direction or management of such entity,+ whether by contract or otherwise, or (b) ownership of more than+ fifty percent (50%) of the outstanding shares or beneficial+ ownership of such entity.++2. License Grants and Conditions+--------------------------------++2.1. Grants++Each Contributor hereby grants You a world-wide, royalty-free,+non-exclusive license:++(a) under intellectual property rights (other than patent or trademark)+ Licensable by such Contributor to use, reproduce, make available,+ modify, display, perform, distribute, and otherwise exploit its+ Contributions, either on an unmodified basis, with Modifications, or+ as part of a Larger Work; and++(b) under Patent Claims of such Contributor to make, use, sell, offer+ for sale, have made, import, and otherwise transfer either its+ Contributions or its Contributor Version.++2.2. Effective Date++The licenses granted in Section 2.1 with respect to any Contribution+become effective for each Contribution on the date the Contributor first+distributes such Contribution.++2.3. Limitations on Grant Scope++The licenses granted in this Section 2 are the only rights granted under+this License. No additional rights or licenses will be implied from the+distribution or licensing of Covered Software under this License.+Notwithstanding Section 2.1(b) above, no patent license is granted by a+Contributor:++(a) for any code that a Contributor has removed from Covered Software;+ or++(b) for infringements caused by: (i) Your and any other third party's+ modifications of Covered Software, or (ii) the combination of its+ Contributions with other software (except as part of its Contributor+ Version); or++(c) under Patent Claims infringed by Covered Software in the absence of+ its Contributions.++This License does not grant any rights in the trademarks, service marks,+or logos of any Contributor (except as may be necessary to comply with+the notice requirements in Section 3.4).++2.4. Subsequent Licenses++No Contributor makes additional grants as a result of Your choice to+distribute the Covered Software under a subsequent version of this+License (see Section 10.2) or under the terms of a Secondary License (if+permitted under the terms of Section 3.3).++2.5. Representation++Each Contributor represents that the Contributor believes its+Contributions are its original creation(s) or it has sufficient rights+to grant the rights to its Contributions conveyed by this License.++2.6. Fair Use++This License is not intended to limit any rights You have under+applicable copyright doctrines of fair use, fair dealing, or other+equivalents.++2.7. Conditions++Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted+in Section 2.1.++3. Responsibilities+-------------------++3.1. Distribution of Source Form++All distribution of Covered Software in Source Code Form, including any+Modifications that You create or to which You contribute, must be under+the terms of this License. You must inform recipients that the Source+Code Form of the Covered Software is governed by the terms of this+License, and how they can obtain a copy of this License. You may not+attempt to alter or restrict the recipients' rights in the Source Code+Form.++3.2. Distribution of Executable Form++If You distribute Covered Software in Executable Form then:++(a) such Covered Software must also be made available in Source Code+ Form, as described in Section 3.1, and You must inform recipients of+ the Executable Form how they can obtain a copy of such Source Code+ Form by reasonable means in a timely manner, at a charge no more+ than the cost of distribution to the recipient; and++(b) You may distribute such Executable Form under the terms of this+ License, or sublicense it under different terms, provided that the+ license for the Executable Form does not attempt to limit or alter+ the recipients' rights in the Source Code Form under this License.++3.3. Distribution of a Larger Work++You may create and distribute a Larger Work under terms of Your choice,+provided that You also comply with the requirements of this License for+the Covered Software. If the Larger Work is a combination of Covered+Software with a work governed by one or more Secondary Licenses, and the+Covered Software is not Incompatible With Secondary Licenses, this+License permits You to additionally distribute such Covered Software+under the terms of such Secondary License(s), so that the recipient of+the Larger Work may, at their option, further distribute the Covered+Software under the terms of either this License or such Secondary+License(s).++3.4. Notices++You may not remove or alter the substance of any license notices+(including copyright notices, patent notices, disclaimers of warranty,+or limitations of liability) contained within the Source Code Form of+the Covered Software, except that You may alter any license notices to+the extent required to remedy known factual inaccuracies.++3.5. Application of Additional Terms++You may choose to offer, and to charge a fee for, warranty, support,+indemnity or liability obligations to one or more recipients of Covered+Software. However, You may do so only on Your own behalf, and not on+behalf of any Contributor. You must make it absolutely clear that any+such warranty, support, indemnity, or liability obligation is offered by+You alone, and You hereby agree to indemnify every Contributor for any+liability incurred by such Contributor as a result of warranty, support,+indemnity or liability terms You offer. You may include additional+disclaimers of warranty and limitations of liability specific to any+jurisdiction.++4. Inability to Comply Due to Statute or Regulation+---------------------------------------------------++If it is impossible for You to comply with any of the terms of this+License with respect to some or all of the Covered Software due to+statute, judicial order, or regulation then You must: (a) comply with+the terms of this License to the maximum extent possible; and (b)+describe the limitations and the code they affect. Such description must+be placed in a text file included with all distributions of the Covered+Software under this License. Except to the extent prohibited by statute+or regulation, such description must be sufficiently detailed for a+recipient of ordinary skill to be able to understand it.++5. Termination+--------------++5.1. The rights granted under this License will terminate automatically+if You fail to comply with any of its terms. However, if You become+compliant, then the rights granted under this License from a particular+Contributor are reinstated (a) provisionally, unless and until such+Contributor explicitly and finally terminates Your grants, and (b) on an+ongoing basis, if such Contributor fails to notify You of the+non-compliance by some reasonable means prior to 60 days after You have+come back into compliance. Moreover, Your grants from a particular+Contributor are reinstated on an ongoing basis if such Contributor+notifies You of the non-compliance by some reasonable means, this is the+first time You have received notice of non-compliance with this License+from such Contributor, and You become compliant prior to 30 days after+Your receipt of the notice.++5.2. If You initiate litigation against any entity by asserting a patent+infringement claim (excluding declaratory judgment actions,+counter-claims, and cross-claims) alleging that a Contributor Version+directly or indirectly infringes any patent, then the rights granted to+You by any and all Contributors for the Covered Software under Section+2.1 of this License shall terminate.++5.3. In the event of termination under Sections 5.1 or 5.2 above, all+end user license agreements (excluding distributors and resellers) which+have been validly granted by You or Your distributors under this License+prior to termination shall survive termination.++************************************************************************+* *+* 6. Disclaimer of Warranty *+* ------------------------- *+* *+* Covered Software is provided under this License on an "as is" *+* basis, without warranty of any kind, either expressed, implied, or *+* statutory, including, without limitation, warranties that the *+* Covered Software is free of defects, merchantable, fit for a *+* particular purpose or non-infringing. The entire risk as to the *+* quality and performance of the Covered Software is with You. *+* Should any Covered Software prove defective in any respect, You *+* (not any Contributor) assume the cost of any necessary servicing, *+* repair, or correction. This disclaimer of warranty constitutes an *+* essential part of this License. No use of any Covered Software is *+* authorized under this License except under this disclaimer. *+* *+************************************************************************++************************************************************************+* *+* 7. Limitation of Liability *+* -------------------------- *+* *+* Under no circumstances and under no legal theory, whether tort *+* (including negligence), contract, or otherwise, shall any *+* Contributor, or anyone who distributes Covered Software as *+* permitted above, be liable to You for any direct, indirect, *+* special, incidental, or consequential damages of any character *+* including, without limitation, damages for lost profits, loss of *+* goodwill, work stoppage, computer failure or malfunction, or any *+* and all other commercial damages or losses, even if such party *+* shall have been informed of the possibility of such damages. This *+* limitation of liability shall not apply to liability for death or *+* personal injury resulting from such party's negligence to the *+* extent applicable law prohibits such limitation. Some *+* jurisdictions do not allow the exclusion or limitation of *+* incidental or consequential damages, so this exclusion and *+* limitation may not apply to You. *+* *+************************************************************************++8. Litigation+-------------++Any litigation relating to this License may be brought only in the+courts of a jurisdiction where the defendant maintains its principal+place of business and such litigation shall be governed by laws of that+jurisdiction, without reference to its conflict-of-law provisions.+Nothing in this Section shall prevent a party's ability to bring+cross-claims or counter-claims.++9. Miscellaneous+----------------++This License represents the complete agreement concerning the subject+matter hereof. If any provision of this License is held to be+unenforceable, such provision shall be reformed only to the extent+necessary to make it enforceable. Any law or regulation which provides+that the language of a contract shall be construed against the drafter+shall not be used to construe this License against a Contributor.++10. Versions of the License+---------------------------++10.1. New Versions++Mozilla Foundation is the license steward. Except as provided in Section+10.3, no one other than the license steward has the right to modify or+publish new versions of this License. Each version will be given a+distinguishing version number.++10.2. Effect of New Versions++You may distribute the Covered Software under the terms of the version+of the License under which You originally received the Covered Software,+or under the terms of any subsequent version published by the license+steward.++10.3. Modified Versions++If you create software not governed by this License, and you want to+create a new license for such software, you may create and use a+modified version of this License if you rename the license and remove+any references to the name of the license steward (except to note that+such modified license differs from this License).++10.4. Distributing Source Code Form that is Incompatible With Secondary+Licenses++If You choose to distribute Source Code Form that is Incompatible With+Secondary Licenses under the terms of this version of the License, the+notice described in Exhibit B of this License must be attached.++Exhibit A - Source Code Form License Notice+-------------------------------------------++ This Source Code Form is subject to the terms of the Mozilla Public+ License, v. 2.0. If a copy of the MPL was not distributed with this+ file, You can obtain one at https://mozilla.org/MPL/2.0/.++If it is not possible or desirable to put the notice in a particular+file, then You may include the notice in a location (such as a LICENSE+file in a relevant directory) where a recipient would be likely to look+for such a notice.++You may add additional accurate notices of copyright ownership.++Exhibit B - "Incompatible With Secondary Licenses" Notice+---------------------------------------------------------++ This Source Code Form is "Incompatible With Secondary Licenses", as+ defined by the Mozilla Public License, v. 2.0.
+ README.md view
@@ -0,0 +1,21 @@+# CloudChor++CloudChor is a Haskell library for lighweight and efficient choreographic programming with multiply-located values.+It is an extension of the [HasChor](https://github.com/gshen42/HasChor) library.++For more details, see the PEPM '26 paper: [Towards Lightweight and Efficient Choreographic Cloud Services](https://doi.org/10.1145/3779209.3779537).++## Repository structure++* [`src/`](src/) contains the source code of the library.+ * [`Choreography.Location.Multi`](src/Choreography/Location/Multi.hs) is the module that implements multiply-located values.+* [`examples/`](examples/) includes example programs using the library.+ * [`clean-room`](examples/clean-room/): An implementation of a data clean room protocol as a choreography.+ * [`unsoundness`](examples/unsoundness): Two counter-examples showing how unrestricted IO can lead to unsoundness.+ * [`HasChor`](examples/HasChor): The original examples from the HasChor repository.+* [`bench/`](bench/) contains the setup for running benchmarks to compare `cond` vs. `cond_`.++## License++This repository is provided under the terms of the [Mozilla Public License 2.0](LICENSE.txt).+It is based on HasChor, which is provided under the [MIT License](LICENSE-HasChor.txt).
+ bench/Benchmark.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ParallelListComp #-}+{-# LANGUAGE ImpredicativeTypes #-}++module Benchmark where++import Control.Concurrent+import Control.Monad(when)+import Data.Foldable(for_)+import Data.IORef+import Data.Proxy(Proxy(..))+import Data.Time+import Data.Traversable(for)+import GHC.TypeLits(KnownSymbol)++import Choreography hiding (cond, cond', cond_, cond_')+import Choreography qualified as C+import Choreography.Choreo+import Choreography.Network+import Choreography.Network.Local+import Choreography.Network.LocalMsgCount++-- Benchmarking infrastructure++-- The type of the `cond_'` function, to avoid writing it out multiple times.+-- NB: This is a more specific version of the original `cond'` function's type.+type Cond_' =+ forall m l a b+ . (Bounded a, Enum a, Show a, Read a, KnownSymbol l)+ => (Proxy l, Unwrap l -> m a)+ -> (a -> Choreo m (b @ l))+ -> Choreo m (b @ l)++httpConfig :: [LocTm] -> HttpConfig+httpConfig ls = mkHttpConfig [(l, ("localhost", port)) | l <- ls | port <- [3000..]]++data SomeBackend = forall b. Backend b => SomeBackend b++runWithBackend :: SomeBackend -> Choreo IO () -> LocTm -> IO ()+runWithBackend (SomeBackend b) = runChoreography b++mkBackendConfig :: String -> IORef Int -> [LocTm] -> IO SomeBackend+mkBackendConfig "http" _ = pure . SomeBackend . httpConfig+mkBackendConfig "local" _ = fmap SomeBackend . mkLocalConfig+mkBackendConfig "count" msgCounter = fmap SomeBackend . mkLocalMsgCountConfig msgCounter+mkBackendConfig _ _ = error "Invalid backend"++timed :: IO () -> IO ()+timed io = do+ before <- getCurrentTime+ io+ after <- getCurrentTime+ print $ diffUTCTime after before++-- Run a choreography with both the original `cond'` and the non-broadcasting `cond_'` and report their execution time.+benchmarkChoreography :: String -> String -> [LocTm] -> (Cond_' -> Choreo IO ()) -> IO ()+benchmarkChoreography backend cond locs choreo = do+ let chor = choreo $ if cond == "full" then C.cond' else C.cond_'+ msgCounter <- newIORef 0+ someCfg <- mkBackendConfig backend msgCounter locs++ timed do+ mvars <- for locs \l -> do+ mvar <- newEmptyMVar++ forkFinally (runWithBackend someCfg chor l) \_ -> putMVar mvar ()+ pure mvar++ for_ mvars takeMVar++ when (backend == "count") do+ count <- readIORef msgCounter+ putStrLn $ "Sends: " <> show count
+ bench/Choreos/Bookseller.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE LambdaCase #-}++-- This is a version of the `bookseller-1-simple` choreography, adapted for the benchmarking setup.+module Choreos.Bookseller where++import Data.Proxy+import Data.Time++import Benchmark+import Choreography hiding (cond, cond_, cond', cond_')++buyer :: Proxy "buyer"+buyer = Proxy++seller :: Proxy "seller"+seller = Proxy++-- `bookseller'` is a simplified version of `bookseller` that utilizes `~~>`+bookseller' :: Cond_' -> Choreo IO ()+bookseller' cond_' = do+ title <- (buyer, \_ -> pure "Types and Programming Languages") ~~> seller++ price <- (seller, \un -> return $ priceOf (un title)) ~~> buyer++ cond_' (buyer, \un -> return $ (un price) < budget) \case+ True -> do+ deliveryDate <- (seller, \un -> return $ deliveryDateOf (un title)) ~~> buyer++ buyer `locally` \un -> do+ -- putStrLn $ "The book will be delivered on " ++ show (un deliveryDate)+ return $ Just (un deliveryDate)++ False -> do+ buyer `locally` \_ -> do+ -- putStrLn "The book's price is out of the budget"+ return Nothing++ pure ()++budget :: Int+budget = 100++priceOf :: String -> Int+priceOf "Types and Programming Languages" = 80+priceOf "Homotopy Type Theory" = 120+priceOf _ = error "Unknown book"++deliveryDateOf :: String -> Day+deliveryDateOf "Types and Programming Languages" = fromGregorian 2022 12 19+deliveryDateOf "Homotopy Type Theory" = fromGregorian 2023 01 01+deliveryDateOf _ = error "Unknown book"
+ bench/Choreos/CleanRoom.hs view
@@ -0,0 +1,160 @@+{-# OPTIONS_GHC -Wno-redundant-constraints #-}++{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE LambdaCase #-}++-- This is a version of the `clean-room` choreography, adapted for the benchmarking setup.+module Choreos.CleanRoom where++import Data.Function((&))+import Data.Proxy(Proxy(..))+import Data.SOP.BasicFunctors(mapKK, K (..))+import Data.SOP.Classes(HCollapse(..), hmap, hsequenceK)+import Data.SOP.Constraint(All)+import Data.SOP.NP(NP(..))+import GHC.TypeLits(KnownSymbol, SSymbol, withKnownSymbol, withSomeSSymbol)++import Benchmark+import Choreography hiding (cond, cond_, cond', cond_')+import Choreography.Location+import Choreography.Location.Multi++-- Schema and dataset handling (stubbed out)++data Schema = Schema+ deriving stock (Show, Read)++data Dataset = Dataset+ deriving stock (Show, Read)++data Query = Query+ deriving stock (Show, Read)++loadSchema :: KnownSymbol l => Proxy l -> IO Schema+loadSchema _ = pure Schema++mergeSchemas :: [Schema] -> Schema+mergeSchemas _ = Schema++conformsToSchema :: Schema -> Dataset -> IO Bool+conformsToSchema _ _ = pure True++emptyDataset :: Dataset+emptyDataset = Dataset++-- Simulate some I/O to load a dataset (e.g. from a file)+loadDataset :: KnownSymbol l => Proxy l -> IO Dataset+loadDataset _ = pure Dataset++mergeDatasets :: [Dataset] -> Dataset+mergeDatasets _ = Dataset++loadQuery :: KnownSymbol l => Proxy l -> Schema -> IO Query+loadQuery _ _ = pure Query++agreeWithQueries :: KnownSymbol l => Proxy l -> [Query] -> IO Bool+agreeWithQueries _ _ = pure True++runQuery :: Dataset -> Query -> IO Int+runQuery _ _ = pure 0++-- Error-handling (if datasets don't conform to schema)++getClientDataset :: (KnownSymbol server, KnownSymbol client) => Cond_' -> Int -> Int -> Schema @ server -> Proxy server -> Proxy client -> Choreo IO (Dataset @ server)+getClientDataset cond_' failures tries schema server client =+ if tries <= 0 then+ pure $ wrap emptyDataset+ else do+ dataset <- (client, \_ -> loadDataset client) ~~> server+ cond_'+ (server, \unwrap ->+ if failures > 0 then+ pure False -- Simulate a fixed number of failures for each client+ else+ conformsToSchema (unwrap schema) (unwrap dataset)+ )+ \case+ True -> pure dataset+ False -> getClientDataset cond_' (failures - 1) (tries - 1) schema server client++getClientDatasets :: (KnownSymbol server, All KnownSymbol clients) => Cond_' -> Int -> Int -> Schema @ server -> Proxy server -> NP Proxy clients -> Choreo IO (NP (K Dataset) clients @ server)+getClientDatasets _ _ _ _ _ Nil = pure $ wrap Nil+getClientDatasets cond_' failures tries schema server (client :* clients) = do+ dataset <- getClientDataset cond_' failures tries schema server client+ datasets <- getClientDatasets cond_' failures tries schema server clients+ locally server \unwrap -> pure $ K (unwrap dataset) :* unwrap datasets++-- Main choreography++cleanRoom :: (KnownSymbol server, All KnownSymbol clients) => Cond_' -> Int -> Proxy server -> NP Proxy clients -> Choreo IO ()+cleanRoom cond_' failures server clients = do+ -- Every client loads their data schema and sends it to the server+ schemas <- (clients, \c _ -> loadSchema c) *~~> server++ -- Server merges the schemas+ finalSchemaS <- locally server \unwrap -> pure $ mergeSchemas $ hcollapse $ unwrap schemas++ -- Server broadcasts the final schema+ finalSchema <- (server, finalSchemaS) ~>* clients++ -- Based on the final schema, each client computes what query it wants to run, and sends it back to the server+ queriesS <- (clients, \c unwrap -> loadQuery c $ unwrap finalSchema) *~~> server++ -- Server broadcasts the list of queries to all clients+ queries <- (server, \unwrap -> pure $ hcollapse $ unwrap queriesS) ~~>* clients++ -- Each client locally decides if they agree with everyone else's queries, and sends back a response+ agreements <- (clients, \c unwrap -> agreeWithQueries c $ unwrap queries) *~~> server++ -- Server branches on the responses+ cond_' (server, \unwrap -> pure $ and $ hcollapse $ unwrap agreements) \case+ False -> do -- Someone disagrees, end the protocol+ multilocally clients \_ _ -> pure () -- print "Disagreement"+ pure $ wrap ()+ True -> do -- Everyone agrees, proceed+ -- The amount of times the server will ask for a valid dataset+ let tries = 5++ -- Clients send their datasets to the server+ datasets <- getClientDatasets cond_' failures tries finalSchemaS server clients++ -- Server locally merges the datasets and runs all the queries+ resultsS <- locally server \unwrap -> do+ let aggregatedDataset = mergeDatasets $ hcollapse $ unwrap datasets+ hsequenceK $ hmap (mapKK $ runQuery aggregatedDataset) $ unwrap queriesS++ -- Server sends the result of each query "pointwise"+ results <- (server, resultsS) ~>. clients++ -- Clients consume the received query results+ multilocally clients \_ unwrap -> pure $ unwrap results+ pure $ wrap ()++ pure ()++-- Instantiating the choreography++withProxy :: String -> (forall s. KnownSymbol s => Proxy s -> a) -> a+withProxy s f = withSomeSSymbol s \(ss :: SSymbol s) -> withKnownSymbol ss $ f @s Proxy++withProxies :: [String] -> (forall ss. All KnownSymbol ss => NP Proxy ss -> a) -> a+withProxies [] f = f Nil+withProxies (s : ss) f =+ withProxy s \s ->+ withProxies ss \ss ->+ f $ s :* ss++cleanRoomLocs :: Int -> [LocTm]+cleanRoomLocs numClients = server : clients+ where+ server = "s"+ clients = fmap (\i -> "c" <> show i) [1..numClients]++mkCleanRoom :: Int -> [LocTm] -> Cond_' -> Choreo IO ()+mkCleanRoom numFailures (server : clients) cond_' =+ cleanRoom cond_' numFailures & withProxy server & withProxies clients+mkCleanRoom _ [] _ = error "Empty locs"
+ bench/Choreos/Kvs.hs view
@@ -0,0 +1,102 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ScopedTypeVariables #-}++-- This is a version of the `kvs-2-primary-backup` choreography, adapted for the benchmarking setup.+module Choreos.Kvs where++import Data.Foldable(for_)+import Data.IORef+import Data.Map (Map)+import Data.Map qualified as Map+import Data.Proxy++import Benchmark+import Choreography hiding (cond, cond_, cond', cond_')+import Choreography.Location(wrap)++client :: Proxy "client"+client = Proxy++primary :: Proxy "primary"+primary = Proxy++backup :: Proxy "backup"+backup = Proxy++type State = Map String String++data Request = Put String String | Get String deriving stock (Show, Read)++isPutRequest :: Request -> Bool+isPutRequest (Put _ _) = True+isPutRequest _ = False++requests :: [Request]+requests =+ [ Put "x" "a"+ , Put "y" "b"+ , Get "x"+ , Put "y" "c"+ , Get "y"+ ]++type Response = Maybe String++-- | `handleRequest` handle a request and returns the new the state.+handleRequest :: Request -> IORef State -> IO Response+handleRequest request stateRef = case request of+ Put key value -> do+ modifyIORef stateRef (Map.insert key value)+ return (Just value)+ Get key -> do+ state <- readIORef stateRef+ return (Map.lookup key state)++-- | `kvs` is a choreography that processes a single request located at the client and returns the response.+-- If the request is a `PUT`, it will forward the request to the backup node.+kvs ::+ Cond_' ->+ Request @ "client" ->+ IORef State @ "primary" ->+ IORef State @ "backup" ->+ Choreo IO (Response @ "client")+kvs cond_' request primaryStateRef backupStateRef = do+ -- send request to the primary node+ request' <- (client, request) ~> primary++ -- branch on the request+ cond_' (primary, \unwrap -> pure $ isPutRequest $ unwrap request') \case+ -- if the request is a `PUT`, forward the request to the backup node+ True -> do+ request'' <- (primary, request') ~> backup+ ack <-+ backup `locally` \unwrap -> do+ handleRequest (unwrap request'') (unwrap backupStateRef)+ (backup, ack) ~> primary+ return $ wrap ()+ _ -> do+ return $ wrap ()++ -- process request on the primary node+ response <-+ primary `locally` \unwrap ->+ handleRequest (unwrap request') (unwrap primaryStateRef)++ -- send response to client+ (primary, response) ~> client++kvs' :: Cond_' -> Choreo IO ()+kvs' cond_' = do+ primaryStateRef <- primary `locally` \_ -> newIORef (Map.empty :: State)+ backupStateRef <- backup `locally` \_ -> newIORef (Map.empty :: State)+ for_ requests \request -> do+ -- Replaced the interactive prompt with a dummy `locally` to keep the choreography's structure,+ -- for testing the overhead of `cond_`'s static analysis.+ clientRequest <- client `locally` \_ -> pure request+ kvs cond_' clientRequest primaryStateRef backupStateRef+ client `locally` \_ -> pure () -- putStrLn ("> " ++ show (unwrap response)) -- Don't print during benchmarking+ pure ()
+ bench/Main.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE ImpredicativeTypes #-}++module Main where++import System.Environment(getArgs)+import System.Exit(exitFailure)++import Benchmark+import Choreos.Bookseller+import Choreos.CleanRoom+import Choreos.Kvs++main :: IO ()+main = getArgs >>= \case+ [backend, cond, "bookseller"] -> benchmarkChoreography backend cond ["buyer", "seller"] bookseller'+ [backend, cond, "kvs"] -> benchmarkChoreography backend cond ["client", "primary", "backup"] kvs'+ [backend, cond, "clean-room", read -> numClients, read -> numFailures] ->+ let locs = cleanRoomLocs numClients+ in benchmarkChoreography backend cond locs $ mkCleanRoom numFailures locs+ _ -> putStrLn "Invalid args" *> exitFailure
+ cloudchor.cabal view
@@ -0,0 +1,290 @@+cabal-version: 3.8++name: cloudchor+version: 0.1.0.0+synopsis: Lightweight and efficient choreographic programming for cloud services+description: Please see the README on GitHub at <https://github.com/aionescu/cloudchor#readme>+category: Concurrency++homepage: https://github.com/aionescu/cloudchor+bug-reports: https://github.com/aionescu/cloudchor/issues++author: Alex Ionescu+maintainer: Alex Ionescu <hackage@ionescu.sh>+copyright: Copyright (C) 2025 Alex Ionescu++license: MPL-2.0+license-files:+ LICENSE.txt+ LICENSE-HasChor.txt++build-type: Simple+extra-doc-files: README.md++tested-with:+ GHC == 9.10.3+ GHC == 9.8.4++source-repository head+ type: git+ location: https://github.com/aionescu/cloudchor++common threaded+ ghc-options:+ -threaded+ -rtsopts+ -with-rtsopts=-N++common warnings+ ghc-options:+ -Wall+ -Wcompat+ -Widentities+ -Wmissing-deriving-strategies+ -Wno-name-shadowing+ -Wno-unused-do-bind+ -Wno-type-defaults+ -Wpartial-fields+ -Wprepositive-qualified-module+ -Wredundant-constraints+ -Wunused-packages++library+ import: warnings+ default-language: GHC2021+ hs-source-dirs: src++ exposed-modules:+ Choreography+ Choreography.Choreo+ Choreography.Location+ Choreography.Location.Multi+ Choreography.Network+ Choreography.Network.Http+ Choreography.Network.Local+ Choreography.Network.LocalMsgCount+ Control.Monad.Freer++ build-depends:+ base >= 4.16 && < 4.21+ , containers >= 0.6 && < 0.7+ , http-client >= 0.7 && < 0.8+ , mtl >= 2.3 && < 2.4+ , servant >= 0.19 && < 0.21+ , servant-client >= 0.19 && < 0.21+ , servant-server >= 0.19 && < 0.21+ , sop-core >= 0.5 && < 0.6+ , template-haskell >= 2.18 && < 2.23+ , unordered-containers >= 0.2 && < 0.3+ , warp >= 3.3 && < 3.4++executable bench+ import: threaded, warnings+ default-language: GHC2021+ hs-source-dirs: bench+ main-is: Main.hs++ other-modules:+ Benchmark+ Choreos.Bookseller+ Choreos.CleanRoom+ Choreos.Kvs++ build-depends:+ base >= 4.16 && < 4.21+ , containers >= 0.6 && < 0.7+ , sop-core >= 0.5 && < 0.6+ , time >= 1.11 && < 1.13+ , cloudchor++executable example-clean-room+ import: warnings+ default-language: GHC2021+ hs-source-dirs: examples/clean-room+ main-is: Main.hs++ build-depends:+ base >= 4.16 && < 4.21+ , random >= 1.2 && < 1.3+ , sop-core >= 0.5 && < 0.6+ , cloudchor++executable example-unsoundness+ import: warnings+ default-language: GHC2021+ hs-source-dirs: examples/unsoundness+ main-is: Main.hs++ build-depends:+ base >= 4.16 && < 4.21+ , http-client >= 0.7 && < 0.8+ , servant >= 0.19 && < 0.21+ , servant-client >= 0.19 && < 0.21+ , servant-server >= 0.19 && < 0.21+ , warp >= 3.3 && < 3.4+ , cloudchor++executable example-HasChor-bank-2pc+ default-language: GHC2021+ hs-source-dirs: examples/HasChor/bank-2pc+ main-is: Main.hs++ build-depends:+ base >= 4.16 && < 4.21+ , split >= 0.2 && < 0.3+ , time >= 1.11 && < 1.13+ , cloudchor++executable example-HasChor-bookseller-0-network+ default-language: GHC2021+ hs-source-dirs: examples/HasChor/bookseller-0-network+ main-is: Main.hs++ build-depends:+ base >= 4.16 && < 4.21+ , time >= 1.11 && < 1.13+ , cloudchor++executable example-HasChor-bookseller-1-simple+ default-language: GHC2021+ hs-source-dirs: examples/HasChor/bookseller-1-simple+ main-is: Main.hs++ build-depends:+ base >= 4.16 && < 4.21+ , time >= 1.11 && < 1.13+ , cloudchor++executable example-HasChor-bookseller-2-higher-order+ default-language: GHC2021+ hs-source-dirs: examples/HasChor/bookseller-2-higher-order+ main-is: Main.hs++ build-depends:+ base >= 4.16 && < 4.21+ , time >= 1.11 && < 1.13+ , cloudchor++executable example-HasChor-bookseller-3-loc-poly+ default-language: GHC2021+ hs-source-dirs: examples/HasChor/bookseller-3-loc-poly+ main-is: Main.hs++ build-depends:+ base >= 4.16 && < 4.21+ , time >= 1.11 && < 1.13+ , cloudchor++executable example-HasChor-diffiehellman+ default-language: GHC2021+ hs-source-dirs: examples/HasChor/diffiehellman+ main-is: Main.hs++ build-depends:+ base >= 4.16 && < 4.21+ , random >= 1.2 && < 1.3+ , time >= 1.11 && < 1.13+ , cloudchor++executable example-HasChor-karatsuba+ default-language: GHC2021+ hs-source-dirs: examples/HasChor/karatsuba+ main-is: Main.hs++ build-depends:+ base >= 4.16 && < 4.21+ , async >= 2.2 && < 2.3+ , containers >= 0.6 && < 0.7+ , cloudchor++executable example-HasChor-kvs1+ default-language: GHC2021+ hs-source-dirs: examples/HasChor/kvs-1-simple+ main-is: Main.hs++ build-depends:+ base >= 4.16 && < 4.21+ , containers >= 0.6 && < 0.7+ , time >= 1.11 && < 1.13+ , cloudchor++executable example-HasChor-kvs2+ default-language: GHC2021+ hs-source-dirs: examples/HasChor/kvs-2-primary-backup+ main-is: Main.hs++ build-depends:+ base >= 4.16 && < 4.21+ , containers >= 0.6 && < 0.7+ , time >= 1.11 && < 1.13+ , cloudchor++executable example-HasChor-kvs3+ default-language: GHC2021+ hs-source-dirs: examples/HasChor/kvs-3-higher-order+ main-is: Main.hs++ build-depends:+ base >= 4.16 && < 4.21+ , containers >= 0.6 && < 0.7+ , time >= 1.11 && < 1.13+ , cloudchor++executable example-HasChor-kvs4+ default-language: GHC2021+ hs-source-dirs: examples/HasChor/kvs-4-loc-poly+ main-is: Main.hs++ build-depends:+ base >= 4.16 && < 4.21+ , containers >= 0.6 && < 0.7+ , time >= 1.11 && < 1.13+ , cloudchor++executable example-HasChor-mergesort+ default-language: GHC2021+ hs-source-dirs: examples/HasChor/mergesort+ main-is: Main.hs++ ghc-options:+ -Wno-x-partial++ build-depends:+ base >= 4.16 && < 4.21+ , containers >= 0.6 && < 0.7+ , time >= 1.11 && < 1.13+ , cloudchor++executable example-HasChor-quicksort+ default-language: GHC2021+ hs-source-dirs: examples/HasChor/quicksort+ main-is: Main.hs++ ghc-options:+ -Wno-x-partial++ build-depends:+ base >= 4.16 && < 4.21+ , async >= 2.2 && < 2.3+ , containers >= 0.6 && < 0.7+ , time >= 1.11 && < 1.13+ , cloudchor++executable example-HasChor-ring-leader+ default-language: GHC2021+ hs-source-dirs: examples/HasChor/ring-leader+ main-is: Main.hs++ build-depends:+ base >= 4.16 && < 4.21+ , transformers >= 0.5 && < 0.7+ , cloudchor++executable example-HasChor-playground+ default-language: GHC2021+ hs-source-dirs: examples/HasChor/playground+ main-is: Main.hs++ build-depends:+ base >= 4.16 && < 4.21+ , cloudchor
+ examples/HasChor/bank-2pc/Main.hs view
@@ -0,0 +1,105 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Main where++import Choreography (runChoreography)+import Choreography.Choreo+import Choreography.Location+import Choreography.Network.Http+import Data.List.Split (splitOn)+import Data.Maybe (catMaybes, mapMaybe)+import Data.Proxy+import System.Environment+import Text.Read (readMaybe)++client :: Proxy "client"+client = Proxy++coordinator :: Proxy "coordinator"+coordinator = Proxy++alice :: Proxy "alice"+alice = Proxy++bob :: Proxy "bob"+bob = Proxy++type State = (Int @ "alice", Int @ "bob")++type Action = (String, Int)++type Transaction = [Action]++-- | `validate` checks if a transaction can be executed while keeping balance >= 0+-- returns if the transaction satisfies the property and the balance after the transaction+validate :: String -> Int -> Transaction -> (Bool, Int)+validate name balance tx = foldl (\(valid, i) (_, amount) -> (let next = i + amount in (valid && next >= 0, next))) (True, balance) actions+ where+ actions = filter (\(n, _) -> n == name) tx++-- | `parse` converts the user input into a transaction+parse :: String -> Transaction+parse s = tx+ where+ t = splitOn ";" s+ f :: String -> Maybe Action+ f l = do+ [target, amountStr] <- return $ words l+ amount <- readMaybe amountStr :: Maybe Int+ target' <- if target == "alice" || target == "bob" then Just target else Nothing+ return (target', amount)+ tx = mapMaybe f t++-- | `handleTransaction` is a choreography that handles a transaction.+-- Given the current state and a transaction, it will first ask alice and bob to vote,+-- then it will decide whether to commit the transaction or not.+-- If the transaction is committed, it will update the state.+-- Otherwise, it will keep the state unchanged.+handleTransaction :: State -> Transaction @ "coordinator" -> Choreo IO (Bool @ "coordinator", State)+handleTransaction (aliceBalance, bobBalance) tx = do+ -- Voting Phase+ txa <- (coordinator, tx) ~> alice+ voteAlice <- (alice, \unwrap -> do { return $ fst $ validate "alice" (unwrap aliceBalance) (unwrap txa) }) ~~> coordinator+ txb <- (coordinator, tx) ~> bob+ voteBob <- (bob, \unwrap -> do { return $ fst $ validate "bob" (unwrap bobBalance) (unwrap txb) }) ~~> coordinator++ -- Check if the transaction can be committed+ canCommit <- coordinator `locally` \unwrap -> do return $ unwrap voteAlice && unwrap voteBob++ -- Commit Phase+ cond (coordinator, canCommit) \case+ True -> do+ aliceBalance' <- alice `locally` \unwrap -> do return $ snd $ validate "alice" (unwrap aliceBalance) (unwrap txa)+ bobBalance' <- bob `locally` \unwrap -> do return $ snd $ validate "bob" (unwrap bobBalance) (unwrap txb)+ return (canCommit, (aliceBalance', bobBalance'))+ False -> do+ return (canCommit, (aliceBalance, bobBalance))++-- | `bank` loops forever and handles transactions.+bank :: State -> Choreo IO ()+bank state = do+ client `locally` \_ -> do+ putStrLn "Command? (alice|bob {amount};)+"+ tx <- (client, \_ -> do { parse <$> getLine }) ~~> coordinator+ (committed, state') <- handleTransaction state tx+ committed' <- (coordinator, committed) ~> client+ client `locally` \unwrap -> do+ putStrLn if unwrap committed' then "Committed" else "Not committed"+ alice `locally` \unwrap -> do putStrLn ("Alice's balance: " ++ show (unwrap (fst state')))+ bob `locally` \unwrap -> do putStrLn ("Bob's balance: " ++ show (unwrap (snd state')))+ bank state' -- repeat+ return ()++-- | `startBank` is a choreography that initializes the states and starts the bank application.+startBank :: Choreo IO ()+startBank = do+ aliceBalance <- alice `locally` \_ -> do return 0+ bobBalance <- bob `locally` \_ -> do return 0+ bank (aliceBalance, bobBalance)++main :: IO ()+main = do+ runChoreo startBank
+ examples/HasChor/bookseller-0-network/Main.hs view
@@ -0,0 +1,55 @@+module Main where++import Choreography.Network+import Choreography.Network.Http+import Data.Time+import System.Environment++buyer :: Network IO ()+buyer = do+ run $ putStrLn "Enter the title of the book to buy:"+ title <- run getLine+ send title "seller"+ price <- recv "seller"+ if price < budget+ then do+ send True "seller"+ (deliveryDate :: Day) <- recv "seller"+ run $ putStrLn ("The book will be delivered on " ++ (show deliveryDate))+ else do+ send False "seller"+ run $ putStrLn "The book's price is out of the budget"++seller :: Network IO ()+seller = do+ title <- recv "buyer"+ send (priceOf title) "buyer"+ decision <- recv "buyer"+ if decision+ then do+ send (deliveryDateOf title) "buyer"+ else do+ return ()++budget :: Int+budget = 100++priceOf :: String -> Int+priceOf "Types and Programming Languages" = 80+priceOf "Homotopy Type Theory" = 120++deliveryDateOf :: String -> Day+deliveryDateOf "Types and Programming Languages" = fromGregorian 2023 12 19+deliveryDateOf "Homotopy Type Theory" = fromGregorian 2023 09 18++main :: IO ()+main = do+ [loc] <- getArgs+ case loc of+ "buyer" -> runNetwork cfg "buyer" buyer+ "seller" -> runNetwork cfg "seller" seller+ return ()+ where+ cfg = mkHttpConfig [ ("buyer", ("localhost", 4242))+ , ("seller", ("localhost", 4343))+ ]
+ examples/HasChor/bookseller-1-simple/Main.hs view
@@ -0,0 +1,97 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE LambdaCase #-}++module Main where++import Choreography+import Data.Proxy+import Data.Time+import System.Environment++buyer :: Proxy "buyer"+buyer = Proxy++seller :: Proxy "seller"+seller = Proxy++-- | `bookseller` is a choreography that implements the bookseller protocol.+bookseller :: Choreo IO (Maybe Day @ "buyer")+bookseller = do+ -- the buyer node prompts the user to enter the title of the book to buy+ title <-+ buyer `locally` \_ -> do+ putStrLn "Enter the title of the book to buy"+ getLine+ -- the buyer sends the title to the seller+ title' <- (buyer, title) ~> seller++ -- the seller checks the price of the book+ price <- seller `locally` \un -> return $ priceOf (un title')+ -- the seller sends back the price of the book to the buyer+ price' <- (seller, price) ~> buyer++ -- the buyer decides whether to buy the book or not+ decision <- buyer `locally` \un -> return $ (un price') < budget++ -- if the buyer decides to buy the book, the seller sends the delivery date to the buyer+ cond (buyer, decision) \case+ True -> do+ deliveryDate <- seller `locally` \un -> return $ deliveryDateOf (un title')+ deliveryDate' <- (seller, deliveryDate) ~> buyer++ buyer `locally` \un -> do+ putStrLn $ "The book will be delivered on " ++ show (un deliveryDate')+ return $ Just (un deliveryDate')++ False -> do+ buyer `locally` \_ -> do+ putStrLn "The book's price is out of the budget"+ return Nothing++-- `bookseller'` is a simplified version of `bookseller` that utilizes `~~>`+bookseller' :: Choreo IO (Maybe Day @ "buyer")+bookseller' = do+ title <- (buyer, \_ -> do+ putStrLn "Enter the title of the book to buy"+ getLine+ )+ ~~> seller++ price <- (seller, \un -> return $ priceOf (un title)) ~~> buyer++ cond' (buyer, \un -> return $ (un price) < budget) \case+ True -> do+ deliveryDate <- (seller, \un -> return $ deliveryDateOf (un title)) ~~> buyer++ buyer `locally` \un -> do+ putStrLn $ "The book will be delivered on " ++ show (un deliveryDate)+ return $ Just (un deliveryDate)++ False -> do+ buyer `locally` \_ -> do+ putStrLn "The book's price is out of the budget"+ return Nothing++budget :: Int+budget = 100++priceOf :: String -> Int+priceOf "Types and Programming Languages" = 80+priceOf "Homotopy Type Theory" = 120++deliveryDateOf :: String -> Day+deliveryDateOf "Types and Programming Languages" = fromGregorian 2022 12 19+deliveryDateOf "Homotopy Type Theory" = fromGregorian 2023 01 01++main :: IO ()+main = do+ [loc] <- getArgs+ case loc of+ "buyer" -> runChoreography cfg bookseller' "buyer"+ "seller" -> runChoreography cfg bookseller' "seller"+ return ()+ where+ cfg = mkHttpConfig [ ("buyer", ("localhost", 4242))+ , ("seller", ("localhost", 4343))+ ]
+ examples/HasChor/bookseller-2-higher-order/Main.hs view
@@ -0,0 +1,93 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE LambdaCase #-}++module Main where++import Choreography+import Data.Proxy+import Data.Time+import System.Environment++buyer :: Proxy "buyer"+buyer = Proxy++seller :: Proxy "seller"+seller = Proxy++buyer2 :: Proxy "buyer2"+buyer2 = Proxy++-- | `bookseller` is a choreography that implements the bookseller protocol.+-- This version takes a choreography `mkDecision` that implements the decision making process.+bookseller :: (Int @ "buyer" -> Choreo IO (Bool @ "buyer")) -> Choreo IO (Maybe Day @ "buyer")+bookseller mkDecision = do+ -- the buyer reads the title of the book and sends it to the seller+ title <- (buyer, \_ -> do+ putStrLn "Enter the title of the book to buy"+ getLine+ )+ ~~> seller++ -- the seller checks the price of the book and sends it to the buyer+ price <- (seller, \un -> return $ priceOf (un title)) ~~> buyer++ -- the buyer makes a decision using the `mkDecision` choreography+ decision <- mkDecision price++ -- if the buyer decides to buy the book, the seller sends the delivery date to the buyer+ cond (buyer, decision) \case+ True -> do+ deliveryDate <- (seller, \un -> return $ deliveryDateOf (un title)) ~~> buyer++ buyer `locally` \un -> do+ putStrLn $ "The book will be delivered on " ++ show (un deliveryDate)+ return $ Just (un deliveryDate)++ False -> do+ buyer `locally` \_ -> do+ putStrLn "The book's price is out of the budget"+ return Nothing++-- | `mkDecision1` checks if buyer's budget is greater than the price of the book+mkDecision1 :: Int @ "buyer" -> Choreo IO (Bool @ "buyer")+mkDecision1 price = do+ buyer `locally` \un -> return $ un price < budget++-- | `mkDecision2` asks buyer2 how much they're willing to contribute and checks+-- if the buyer's budget is greater than the price of the book minus buyer2's contribution+mkDecision2 :: Int @ "buyer" -> Choreo IO (Bool @ "buyer")+mkDecision2 price = do+ contrib <- (buyer2, \_ -> do+ putStrLn "How much you're willing to contribute?"+ read <$> getLine+ )+ ~~> buyer+ buyer `locally` \un -> return $ un price - un contrib <= budget++budget :: Int+budget = 100++priceOf :: String -> Int+priceOf "Types and Programming Languages" = 80+priceOf "Homotopy Type Theory" = 120++deliveryDateOf :: String -> Day+deliveryDateOf "Types and Programming Languages" = fromGregorian 2022 12 19+deliveryDateOf "Homotopy Type Theory" = fromGregorian 2023 01 01++main :: IO ()+main = do+ [loc] <- getArgs+ case loc of+ "buyer" -> runChoreography cfg choreo "buyer"+ "seller" -> runChoreography cfg choreo "seller"+ "buyer2" -> runChoreography cfg choreo "buyer2"+ return ()+ where+ choreo = bookseller mkDecision2++ cfg = mkHttpConfig [ ("buyer", ("localhost", 4242))+ , ("seller", ("localhost", 4343))+ , ("buyer2", ("localhost", 4444))+ ]
+ examples/HasChor/bookseller-3-loc-poly/Main.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE LambdaCase #-}++module Main where++import Choreography+import Data.Proxy+import Data.Time+import GHC.TypeLits+import System.Environment++buyer :: Proxy "buyer"+buyer = Proxy++seller :: Proxy "seller"+seller = Proxy++-- | `bookseller` is a choreography that implements the bookseller protocol.+-- This version takes the name of the buyer as a parameter (`someBuyer`).+bookseller :: KnownSymbol a => Proxy a -> Choreo IO (Maybe Day @ a)+bookseller someBuyer = do+ -- the buyer reads the title of the book and sends it to the seller+ title <- (buyer, \_ -> do+ putStrLn "Enter the title of the book to buy"+ getLine+ )+ ~~> seller++ -- the seller checks the price of the book and sends it to the buyer+ price <- (seller, \un -> return $ priceOf (un title)) ~~> someBuyer++ cond' (someBuyer, \un -> return $ (un price) < budget) \case+ True -> do+ deliveryDate <- (seller, \un -> return $ deliveryDateOf (un title)) ~~> someBuyer++ someBuyer `locally` \un -> do+ putStrLn $ "The book will be delivered on " ++ show (un deliveryDate)+ return $ Just (un deliveryDate)++ False -> do+ someBuyer `locally` \_ -> do+ putStrLn "The book's price is out of the budget"+ return Nothing++budget :: Int+budget = 100++priceOf :: String -> Int+priceOf "Types and Programming Languages" = 80+priceOf "Homotopy Type Theory" = 120++deliveryDateOf :: String -> Day+deliveryDateOf "Types and Programming Languages" = fromGregorian 2022 12 19+deliveryDateOf "Homotopy Type Theory" = fromGregorian 2023 01 01++main :: IO ()+main = do+ [loc] <- getArgs+ case loc of+ "buyer" -> runChoreography cfg choreo "buyer"+ "seller" -> runChoreography cfg choreo "seller"+ return ()+ where+ choreo = bookseller buyer+ + cfg = mkHttpConfig [ ("buyer", ("localhost", 4242))+ , ("seller", ("localhost", 4343))+ ]
+ examples/HasChor/diffiehellman/Main.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE LambdaCase #-}++module Main where++import Choreography (mkHttpConfig, runChoreography)+import Choreography.Choreo+import Choreography.Location+import Data.Proxy+import Data.Time+import System.Environment+import System.Random++-- helper functions around prime number+-- https://nulldereference.wordpress.com/2012/02/04/generating-prime-numbers-with-haskell/+divisors :: Integer -> [Integer]+divisors 1 = [1]+divisors x = 1 : [y | y <- [2 .. (x `div` 2)], x `mod` y == 0] ++ [x]++isPrime :: Integer -> Bool+isPrime x = divisors x == [1, x]++primeNums :: [Integer]+primeNums = [x | x <- [2 ..], isPrime x]++-- set up proxies+alice :: Proxy "alice"+alice = Proxy++bob :: Proxy "bob"+bob = Proxy++diffieHellman :: Choreo IO (Integer @ "alice", Integer @ "bob")+diffieHellman = do+ -- wait for alice to initiate the process+ alice `locally` \unwrap -> do+ putStrLn "enter to start key exchange..."+ getLine+ bob `locally` \unwrap -> do+ putStrLn "waiting for alice to initiate key exchange"++ -- alice picks p and g and sends them to bob+ pa <-+ alice `locally` \unwrap -> do+ x <- randomRIO (200, 1000 :: Int)+ return $ primeNums !! x+ pb <- (alice, pa) ~> bob+ ga <- alice `locally` \unwrap -> do randomRIO (10, unwrap pa)+ gb <- (alice, ga) ~> bob++ -- alice and bob select secrets+ a <- alice `locally` \unwrap -> do randomRIO (200, 1000 :: Integer)+ b <- bob `locally` \unwrap -> do randomRIO (200, 1000 :: Integer)++ -- alice and bob computes numbers that they exchange+ a' <- alice `locally` \unwrap -> do return $ unwrap ga ^ unwrap a `mod` unwrap pa+ b' <- bob `locally` \unwrap -> do return $ unwrap gb ^ unwrap b `mod` unwrap pb++ -- exchange numbers+ a'' <- (alice, a') ~> bob+ b'' <- (bob, b') ~> alice++ -- compute shared key+ s1 <-+ alice `locally` \unwrap ->+ let s = unwrap b'' ^ unwrap a `mod` unwrap pa+ in do+ putStrLn ("alice's shared key: " ++ show s)+ return s+ s2 <-+ bob `locally` \unwrap ->+ let s = unwrap a'' ^ unwrap b `mod` unwrap pb+ in do+ putStrLn ("bob's shared key: " ++ show s)+ return s+ return (s1, s2)++main :: IO ()+main = do+ [loc] <- getArgs+ x <- case loc of+ "alice" -> runChoreography config diffieHellman "alice"+ "bob" -> runChoreography config diffieHellman "bob"+ return ()+ where+ config =+ mkHttpConfig+ [ ("alice", ("localhost", 5000)),+ ("bob", ("localhost", 5001))+ ]
+ examples/HasChor/karatsuba/Main.hs view
@@ -0,0 +1,93 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE LambdaCase #-}++module Main where++import Choreography (runChoreography)+import Choreography.Choreo+import Choreography.Location+import Choreography.Network.Local+import Control.Concurrent.Async (async, mapConcurrently_, wait)+import Data.Proxy+import GHC.TypeLits (KnownSymbol)+import System.Environment++primary :: Proxy "primary"+primary = Proxy++worker1 :: Proxy "worker1"+worker1 = Proxy++worker2 :: Proxy "worker2"+worker2 = Proxy++data KaratsubaNums = KaratsubaNums+ { splitter :: Integer,+ h1 :: Integer,+ h2 :: Integer,+ l1 :: Integer,+ l2 :: Integer+ }++karatsuba ::+ (KnownSymbol a, KnownSymbol b, KnownSymbol c) =>+ Proxy a ->+ Proxy b ->+ Proxy c ->+ (Integer @ a) ->+ (Integer @ a) ->+ Choreo IO (Integer @ a)+karatsuba a b c n1 n2 = do+ done <- a `locally` \unwrap -> return $ unwrap n1 < 10 || unwrap n2 < 10+ cond+ (a, done)+ \case+ True -> do+ a `locally` \unwrap -> return $ unwrap n1 * unwrap n2+ False -> do+ x <- a `locally` \unwrap -> return $ f (unwrap n1) (unwrap n2)+ l1' <- (a, \unwrap -> return $ l1 (unwrap x)) ~~> b+ l2' <- (a, \unwrap -> return $ l2 (unwrap x)) ~~> b+ h1' <- (a, \unwrap -> return $ h1 (unwrap x)) ~~> c+ h2' <- (a, \unwrap -> return $ h2 (unwrap x)) ~~> c+ z0' <- karatsuba b c a l1' l2'+ z0 <- (b, z0') ~> a+ z2' <- karatsuba c a b h1' h2'+ z2 <- (c, z2') ~> a+ s1 <- a `locally` \unwrap -> return $ l1 (unwrap x) + h1 (unwrap x)+ s2 <- a `locally` \unwrap -> return $ l2 (unwrap x) + h2 (unwrap x)+ z1' <- karatsuba a b c s1 s2+ z1 <- a `locally` \unwrap -> return $ unwrap z1' - unwrap z2 - unwrap z0+ a `locally` \unwrap -> return let s = splitter (unwrap x) in (unwrap z2 * s * s) + (unwrap z1 * s) + unwrap z0+ where+ f n1 n2 = KaratsubaNums {splitter = splitter, h1 = h1, l1 = l1, h2 = h2, l2 = l2}+ where+ log10 :: Integer -> Double+ log10 = logBase 10 . fromIntegral+ m = max (log10 n1) (log10 n2) + 1+ m2 = floor (m / 2)+ splitter = 10 ^ m2+ h1 = n1 `div` splitter+ l1 = n1 `mod` splitter+ h2 = n2 `div` splitter+ l2 = n2 `mod` splitter++mainChoreo :: Integer -> Integer -> Choreo IO ()+mainChoreo n1 n2 = do+ n1 <- primary `locally` \_ -> return n1+ n2 <- primary `locally` \_ -> return n2+ result <- karatsuba primary worker1 worker2 n1 n2+ primary `locally` \unwrap -> do+ print (unwrap result)+ return ()+ return ()++main :: IO ()+main = do+ [n1, n2] <- map read <$> getArgs+ config <- mkLocalConfig locs+ mapConcurrently_ (runChoreography config (mainChoreo n1 n2)) locs+ return ()+ where+ locs = ["primary", "worker1", "worker2"]
+ examples/HasChor/kvs-1-simple/Main.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Main where++import Choreography (runChoreography)+import Choreography.Choreo+import Choreography.Location+import Choreography.Network.Http+import Control.Concurrent (threadDelay)+import Control.Monad+import Data.IORef+import Data.Map (Map, (!))+import Data.Map qualified as Map+import Data.Maybe (fromMaybe, isJust)+import Data.Proxy+import GHC.IORef (IORef (IORef))+import GHC.TypeLits (KnownSymbol)+import System.Environment++client :: Proxy "client"+client = Proxy++server :: Proxy "server"+server = Proxy++type State = Map String String++data Request = Put String String | Get String deriving (Show, Read)++type Response = Maybe String++-- | `readRequest` reads a request from the terminal.+readRequest :: IO Request+readRequest = do+ putStrLn "Command?"+ line <- getLine+ case parseRequest line of+ Just t -> return t+ Nothing -> putStrLn "Invalid command" >> readRequest+ where+ parseRequest :: String -> Maybe Request+ parseRequest s =+ let l = words s+ in case l of+ ["GET", k] -> Just (Get k)+ ["PUT", k, v] -> Just (Put k v)+ _ -> Nothing++-- | `handleRequest` handle a request and returns the new the state.+handleRequest :: Request -> IORef State -> IO Response+handleRequest request stateRef = case request of+ Put key value -> do+ modifyIORef stateRef (Map.insert key value)+ return (Just value)+ Get key -> do+ state <- readIORef stateRef+ return (Map.lookup key state)++-- | `kvs` is a choreography that processes a single request located at the client and returns the response.+kvs ::+ Request @ "client" ->+ IORef State @ "server" ->+ Choreo IO (Response @ "client")+kvs request stateRef = do+ -- send the request to the server+ request' <- (client, request) ~> server+ -- the server handles the response and creates a response+ response <-+ server `locally` \unwrap ->+ handleRequest (unwrap request') (unwrap stateRef)+ -- send the response back to the client+ (server, response) ~> client++-- | `mainChoreo` is a choreography that serves as the entry point of the program.+-- It initializes the state and loops forever.+mainChoreo :: Choreo IO ()+mainChoreo = do+ stateRef <- server `locally` \_ -> newIORef (Map.empty :: State)+ loop stateRef+ where+ loop :: IORef State @ "server" -> Choreo IO ()+ loop stateRef = do+ request <- client `locally` \_ -> readRequest+ response <- kvs request stateRef+ client `locally` \unwrap -> do putStrLn ("> " ++ (show (unwrap response)))+ loop stateRef++main :: IO ()+main = do+ [loc] <- getArgs+ case loc of+ "client" -> runChoreography config mainChoreo "client"+ "server" -> runChoreography config mainChoreo "server"+ return ()+ where+ config =+ mkHttpConfig+ [ ("client", ("localhost", 3000)),+ ("server", ("localhost", 4000))+ ]
+ examples/HasChor/kvs-2-primary-backup/Main.hs view
@@ -0,0 +1,125 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Main where++import Choreography (runChoreography)+import Choreography.Choreo+import Choreography.Location+import Choreography.Network.Http+import Control.Concurrent (threadDelay)+import Control.Monad+import Data.IORef+import Data.Map (Map, (!))+import Data.Map qualified as Map+import Data.Maybe (fromMaybe, isJust)+import Data.Proxy+import GHC.IORef (IORef (IORef))+import GHC.TypeLits (KnownSymbol)+import System.Environment++client :: Proxy "client"+client = Proxy++primary :: Proxy "primary"+primary = Proxy++backup :: Proxy "backup"+backup = Proxy++type State = Map String String++data Request = Put String String | Get String deriving (Show, Read)++type Response = Maybe String++-- | `readRequest` reads a request from the terminal.+readRequest :: IO Request+readRequest = do+ putStrLn "Command?"+ line <- getLine+ case parseRequest line of+ Just t -> return t+ Nothing -> putStrLn "Invalid command" >> readRequest+ where+ parseRequest :: String -> Maybe Request+ parseRequest s =+ let l = words s+ in case l of+ ["GET", k] -> Just (Get k)+ ["PUT", k, v] -> Just (Put k v)+ _ -> Nothing++-- | `handleRequest` handle a request and returns the new the state.+handleRequest :: Request -> IORef State -> IO Response+handleRequest request stateRef = case request of+ Put key value -> do+ modifyIORef stateRef (Map.insert key value)+ return (Just value)+ Get key -> do+ state <- readIORef stateRef+ return (Map.lookup key state)++-- | `kvs` is a choreography that processes a single request located at the client and returns the response.+-- If the request is a `PUT`, it will forward the request to the backup node.+kvs ::+ Request @ "client" ->+ (IORef State @ "primary", IORef State @ "backup") ->+ Choreo IO (Response @ "client")+kvs request (primaryStateRef, backupStateRef) = do+ -- send request to the primary node+ request' <- (client, request) ~> primary++ -- branch on the request+ cond (primary, request') \case+ -- if the request is a `PUT`, forward the request to the backup node+ Put key value -> do+ request'' <- (primary, request') ~> backup+ ack <-+ backup `locally` \unwrap -> do+ handleRequest (unwrap request'') (unwrap backupStateRef)+ (backup, ack) ~> primary+ return ()+ _ -> do+ return ()++ -- process request on the primary node+ response <-+ primary `locally` \unwrap ->+ handleRequest (unwrap request') (unwrap primaryStateRef)++ -- send response to client+ (primary, response) ~> client++-- | `mainChoreo` is a choreography that serves as the entry point of the program.+-- It initializes the state and loops forever.+mainChoreo :: Choreo IO ()+mainChoreo = do+ primaryStateRef <- primary `locally` \_ -> newIORef (Map.empty :: State)+ backupStateRef <- backup `locally` \_ -> newIORef (Map.empty :: State)+ loop (primaryStateRef, backupStateRef)+ where+ loop :: (IORef State @ "primary", IORef State @ "backup") -> Choreo IO ()+ loop stateRefs = do+ request <- client `locally` \_ -> readRequest+ response <- kvs request stateRefs+ client `locally` \unwrap -> do putStrLn ("> " ++ show (unwrap response))+ loop stateRefs++main :: IO ()+main = do+ [loc] <- getArgs+ case loc of+ "client" -> runChoreography config mainChoreo "client"+ "primary" -> runChoreography config mainChoreo "primary"+ "backup" -> runChoreography config mainChoreo "backup"+ return ()+ where+ config =+ mkHttpConfig+ [ ("client", ("localhost", 3000)),+ ("primary", ("localhost", 4000)),+ ("backup", ("localhost", 5000))+ ]
+ examples/HasChor/kvs-3-higher-order/Main.hs view
@@ -0,0 +1,156 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Main where++import Choreography (runChoreography)+import Choreography.Choreo+import Choreography.Location+import Choreography.Network.Http+import Control.Concurrent (threadDelay)+import Control.Monad+import Data.IORef+import Data.Map (Map, (!))+import Data.Map qualified as Map+import Data.Maybe (fromMaybe, isJust)+import Data.Proxy+import GHC.IORef (IORef (IORef))+import GHC.TypeLits (KnownSymbol)+import System.Environment++client :: Proxy "client"+client = Proxy++primary :: Proxy "primary"+primary = Proxy++backup :: Proxy "backup"+backup = Proxy++type State = Map String String++data Request = Put String String | Get String deriving (Show, Read)++type Response = Maybe String++-- | `readRequest` reads a request from the terminal.+readRequest :: IO Request+readRequest = do+ putStrLn "Command?"+ line <- getLine+ case parseRequest line of+ Just t -> return t+ Nothing -> putStrLn "Invalid command" >> readRequest+ where+ parseRequest :: String -> Maybe Request+ parseRequest s =+ let l = words s+ in case l of+ ["GET", k] -> Just (Get k)+ ["PUT", k, v] -> Just (Put k v)+ _ -> Nothing++-- | `handleRequest` handle a request and returns the new the state.+handleRequest :: Request -> IORef State -> IO Response+handleRequest request stateRef = case request of+ Put key value -> do+ modifyIORef stateRef (Map.insert key value)+ return (Just value)+ Get key -> do+ state <- readIORef stateRef+ return (Map.lookup key state)++-- | ReplicationStrategy specifies how a request should be handled on possibly replicated servers+-- `a` is a type that represent states across locations+type ReplicationStrategy a =+ Request @ "primary" -> a -> Choreo IO (Response @ "primary")++-- | `nullReplicationStrategy` is a replication strategy that does not replicate the state.+nullReplicationStrategy :: ReplicationStrategy (IORef State @ "primary")+nullReplicationStrategy request stateRef = do+ primary `locally` \unwrap ->+ handleRequest (unwrap request) (unwrap stateRef)++-- | `primaryBackupReplicationStrategy` is a replication strategy that replicates the state to a backup server.+primaryBackupReplicationStrategy ::+ ReplicationStrategy (IORef State @ "primary", IORef State @ "backup")+primaryBackupReplicationStrategy request (primaryStateRef, backupStateRef) = do+ -- relay request to backup if it is mutating (= PUT)+ cond (primary, request) \case+ Put _ _ -> do+ request' <- (primary, request) ~> backup+ ( backup,+ \unwrap ->+ handleRequest (unwrap request') (unwrap backupStateRef)+ )+ ~~> primary+ return ()+ _ -> do+ return ()++ -- process request on primary+ primary `locally` \unwrap ->+ handleRequest (unwrap request) (unwrap primaryStateRef)++-- | `kvs` is a choreography that processes a single request at the client and returns the response.+-- It uses the provided replication strategy to handle the request.+kvs ::+ forall a.+ Request @ "client" ->+ a ->+ ReplicationStrategy a ->+ Choreo IO (Response @ "client")+kvs request stateRefs replicationStrategy = do+ request' <- (client, request) ~> primary++ -- call the provided replication strategy+ response <- replicationStrategy request' stateRefs++ -- send response to client+ (primary, response) ~> client++-- | `nullReplicationChoreo` is a choreography that uses `nullReplicationStrategy`.+nullReplicationChoreo :: Choreo IO ()+nullReplicationChoreo = do+ stateRef <- primary `locally` \_ -> newIORef (Map.empty :: State)+ loop stateRef+ where+ loop :: IORef State @ "primary" -> Choreo IO ()+ loop stateRef = do+ request <- client `locally` \_ -> readRequest+ response <- kvs request stateRef nullReplicationStrategy+ client `locally` \unwrap -> do putStrLn (show (unwrap response))+ loop stateRef++-- | `primaryBackupChoreo` is a choreography that uses `primaryBackupReplicationStrategy`.+primaryBackupChoreo :: Choreo IO ()+primaryBackupChoreo = do+ primaryStateRef <- primary `locally` \_ -> newIORef (Map.empty :: State)+ backupStateRef <- backup `locally` \_ -> newIORef (Map.empty :: State)+ loop (primaryStateRef, backupStateRef)+ where+ loop :: (IORef State @ "primary", IORef State @ "backup") -> Choreo IO ()+ loop stateRefs = do+ request <- client `locally` \_ -> readRequest+ response <- kvs request stateRefs primaryBackupReplicationStrategy+ client `locally` \unwrap -> do putStrLn ("> " ++ show (unwrap response))+ loop stateRefs++main :: IO ()+main = do+ [loc] <- getArgs+ case loc of+ "client" -> runChoreography config mainChoreo "client"+ "primary" -> runChoreography config mainChoreo "primary"+ "backup" -> runChoreography config mainChoreo "backup"+ return ()+ where+ mainChoreo = primaryBackupChoreo -- or `nullReplicationChoreo`+ config =+ mkHttpConfig+ [ ("client", ("localhost", 3000)),+ ("primary", ("localhost", 4000)),+ ("backup", ("localhost", 5000))+ ]
+ examples/HasChor/kvs-4-loc-poly/Main.hs view
@@ -0,0 +1,196 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Main where++import Choreography (runChoreography)+import Choreography.Choreo+import Choreography.Location+import Choreography.Network.Http+import Control.Concurrent (threadDelay)+import Control.Monad+import Data.IORef+import Data.Map (Map, (!))+import Data.Map qualified as Map+import Data.Maybe (fromMaybe, isJust)+import Data.Proxy+import GHC.IORef (IORef (IORef))+import GHC.TypeLits (KnownSymbol)+import System.Environment++client :: Proxy "client"+client = Proxy++primary :: Proxy "primary"+primary = Proxy++backup1 :: Proxy "backup1"+backup1 = Proxy++backup2 :: Proxy "backup2"+backup2 = Proxy++type State = Map String String++data Request = Put String String | Get String deriving (Show, Read)++type Response = Maybe String++-- | `readRequest` reads a request from the terminal.+readRequest :: IO Request+readRequest = do+ putStrLn "Command?"+ line <- getLine+ case parseRequest line of+ Just t -> return t+ Nothing -> putStrLn "Invalid command" >> readRequest+ where+ parseRequest :: String -> Maybe Request+ parseRequest s =+ let l = words s+ in case l of+ ["GET", k] -> Just (Get k)+ ["PUT", k, v] -> Just (Put k v)+ _ -> Nothing++-- | `handleRequest` handle a request and returns the new the state.+handleRequest :: Request -> IORef State -> IO Response+handleRequest request stateRef = case request of+ Put key value -> do+ modifyIORef stateRef (Map.insert key value)+ return (Just value)+ Get key -> do+ state <- readIORef stateRef+ return (Map.lookup key state)++-- | ReplicationStrategy specifies how a request should be handled on possibly replicated servers+-- `a` is a type that represent states across locations+type ReplicationStrategy a = Request @ "primary" -> a -> Choreo IO (Response @ "primary")++-- | `nullReplicationStrategy` is a replication strategy that does not replicate the state.+nullReplicationStrategy :: ReplicationStrategy (IORef State @ "primary")+nullReplicationStrategy request stateRef = do+ primary `locally` \unwrap -> case unwrap request of+ Put key value -> do+ modifyIORef (unwrap stateRef) (Map.insert key value)+ return (Just value)+ Get key -> do+ state <- readIORef (unwrap stateRef)+ return (Map.lookup key state)++-- | `doBackup` relays a mutating request to a backup location.+doBackup ::+ KnownSymbol a =>+ KnownSymbol b =>+ Proxy a ->+ Proxy b ->+ Request @ a ->+ IORef State @ b ->+ Choreo IO ()+doBackup locA locB request stateRef = do+ cond (locA, request) \case+ Put _ _ -> do+ request' <- (locA, request) ~> locB+ (locB, \unwrap -> handleRequest (unwrap request') (unwrap stateRef))+ ~~> locA+ return ()+ _ -> do+ return ()++-- | `primaryBackupReplicationStrategy` is a replication strategy that replicates the state to a backup server.+primaryBackupReplicationStrategy :: ReplicationStrategy (IORef State @ "primary", IORef State @ "backup1")+primaryBackupReplicationStrategy request (primaryStateRef, backupStateRef) = do+ -- relay request to backup if it is mutating (= PUT)+ doBackup primary backup1 request backupStateRef++ -- process request on primary+ primary `locally` \unwrap -> handleRequest (unwrap request) (unwrap primaryStateRef)++-- | `doubleBackupReplicationStrategy` is a replication strategy that replicates the state to two backup servers.+doubleBackupReplicationStrategy ::+ ReplicationStrategy+ (IORef State @ "primary", IORef State @ "backup1", IORef State @ "backup2")+doubleBackupReplicationStrategy+ request+ (primaryStateRef, backup1StateRef, backup2StateRef) = do+ -- relay to two backup locations+ doBackup primary backup1 request backup1StateRef+ doBackup primary backup2 request backup2StateRef++ -- process request on primary+ primary `locally` \unwrap ->+ handleRequest (unwrap request) (unwrap primaryStateRef)++-- | `kvs` is a choreography that processes a single request at the client and returns the response.+-- It uses the provided replication strategy to handle the request.+kvs :: Request @ "client" -> a -> ReplicationStrategy a -> Choreo IO (Response @ "client")+kvs request stateRefs replicationStrategy = do+ request' <- (client, request) ~> primary++ -- call the provided replication strategy+ response <- replicationStrategy request' stateRefs++ -- send response to client+ (primary, response) ~> client++-- | `nullReplicationChoreo` is a choreography that uses `nullReplicationStrategy`.+nullReplicationChoreo :: Choreo IO ()+nullReplicationChoreo = do+ stateRef <- primary `locally` \_ -> newIORef (Map.empty :: State)+ loop stateRef+ where+ loop :: IORef State @ "primary" -> Choreo IO ()+ loop stateRef = do+ request <- client `locally` \_ -> readRequest+ response <- kvs request stateRef nullReplicationStrategy+ client `locally` \unwrap -> do putStrLn (show (unwrap response))+ loop stateRef++-- | `primaryBackupChoreo` is a choreography that uses `primaryBackupReplicationStrategy`.+primaryBackupChoreo :: Choreo IO ()+primaryBackupChoreo = do+ primaryStateRef <- primary `locally` \_ -> newIORef (Map.empty :: State)+ backupStateRef <- backup1 `locally` \_ -> newIORef (Map.empty :: State)+ loop (primaryStateRef, backupStateRef)+ where+ loop :: (IORef State @ "primary", IORef State @ "backup1") -> Choreo IO ()+ loop stateRefs = do+ request <- client `locally` \_ -> readRequest+ response <- kvs request stateRefs primaryBackupReplicationStrategy+ client `locally` \unwrap -> do putStrLn (show (unwrap response))+ loop stateRefs++-- | `doubleBackupChoreo` is a choreography that uses `doubleBackupReplicationStrategy`.+doubleBackupChoreo :: Choreo IO ()+doubleBackupChoreo = do+ primaryStateRef <- primary `locally` \_ -> newIORef (Map.empty :: State)+ backup1StateRef <- backup1 `locally` \_ -> newIORef (Map.empty :: State)+ backup2StateRef <- backup2 `locally` \_ -> newIORef (Map.empty :: State)+ loop (primaryStateRef, backup1StateRef, backup2StateRef)+ where+ loop :: (IORef State @ "primary", IORef State @ "backup1", IORef State @ "backup2") -> Choreo IO ()+ loop stateRefs = do+ request <- client `locally` \_ -> readRequest+ response <- kvs request stateRefs doubleBackupReplicationStrategy+ client `locally` \unwrap -> do putStrLn ("> " ++ show (unwrap response))+ loop stateRefs++main :: IO ()+main = do+ [loc] <- getArgs+ case loc of+ "client" -> runChoreography config primaryBackupChoreo "client"+ "primary" -> runChoreography config primaryBackupChoreo "primary"+ "backup1" -> runChoreography config primaryBackupChoreo "backup1"+ "backup2" -> runChoreography config primaryBackupChoreo "backup2"+ return ()+ where+ config =+ mkHttpConfig+ [ ("client", ("localhost", 3000)),+ ("primary", ("localhost", 4000)),+ ("backup1", ("localhost", 5000)),+ ("backup2", ("localhost", 6000))+ ]
+ examples/HasChor/mergesort/Main.hs view
@@ -0,0 +1,118 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE LambdaCase #-}++module Main where++import Choreography (runChoreography)+import Choreography.Choreo+import Choreography.Location+import Choreography.Network.Http+import Data.Proxy+import Data.Time+import GHC.TypeLits (KnownSymbol)+import System.Environment++divide :: [a] -> ([a], [a])+divide xs = splitAt lhx xs+ where+ lhx = length xs `div` 2++primary :: Proxy "primary"+primary = Proxy++worker1 :: Proxy "worker1"+worker1 = Proxy++worker2 :: Proxy "worker2"+worker2 = Proxy++sort ::+ KnownSymbol a =>+ Proxy a ->+ KnownSymbol b =>+ Proxy b ->+ KnownSymbol c =>+ Proxy c ->+ ([Int] @ a) ->+ Choreo IO ([Int] @ a)+sort a b c lst = do+ condition <- a `locally` \unwrap -> do return $ length (unwrap lst) > 1+ cond (a, condition) \case+ True -> do+ pivot <- a `locally` \unwrap -> do return $ length (unwrap lst) `div` 2+ divided <- a `locally` \unwrap -> do return $ divide (unwrap lst)+ l <- a `locally` \unwrap -> do return $ fst (unwrap divided)+ r <- a `locally` \unwrap -> do return $ snd (unwrap divided)+ l' <- (a, l) ~> b+ r' <- (a, r) ~> c+ ls' <- sort b c a l'+ rs' <- sort c a b r'+ merge a b c ls' rs'+ False -> do+ return lst++merge ::+ KnownSymbol a =>+ Proxy a ->+ KnownSymbol b =>+ Proxy b ->+ KnownSymbol c =>+ Proxy c ->+ [Int] @ b ->+ [Int] @ c ->+ Choreo IO ([Int] @ a)+merge a b c lhs rhs = do+ lhsHasElements <- b `locally` \unwrap -> do return $ not (null (unwrap lhs))+ cond (b, lhsHasElements) \case+ True -> do+ rhsHasElements <- c `locally` \unwrap -> do return $ not (null (unwrap rhs))+ cond (c, rhsHasElements) \case+ True -> do+ rhsHeadAtC <- c `locally` \unwrap -> do return $ head (unwrap rhs)+ rhsHeadAtB <- (c, rhsHeadAtC) ~> b+ takeLhs <- b `locally` \unwrap -> do return $ head (unwrap lhs) <= unwrap rhsHeadAtB+ cond (b, takeLhs) \case+ True -> do+ -- take (head lhs) and merge the rest+ lhs' <- b `locally` \unwrap -> do return $ tail (unwrap lhs)+ merged <- merge a b c lhs' rhs+ lhsHeadAtB <- b `locally` \unwrap -> do return $ head (unwrap lhs)+ lhsHeadAtA <- (b, lhsHeadAtB) ~> a+ a `locally` \unwrap -> do return $ unwrap lhsHeadAtA : unwrap merged+ False -> do+ -- take (head rhs) and merge the rest+ rhs' <- c `locally` \unwrap -> do return $ tail (unwrap rhs)+ merged <- merge a b c lhs rhs'+ rhsHeadAtC <- c `locally` \unwrap -> do return $ head (unwrap rhs)+ rhsHeadAtA <- (c, rhsHeadAtC) ~> a+ a `locally` \unwrap -> do return $ unwrap rhsHeadAtA : unwrap merged+ False -> do+ (b, lhs) ~> a+ False -> do+ (c, rhs) ~> a++mainChoreo :: Choreo IO ()+mainChoreo = do+ lst <- primary `locally` \unwrap -> do return [1, 6, 5, 3, 4, 2, 7, 8]+ sorted <- sort primary worker1 worker2 lst+ primary `locally` \unwrap -> do+ print (unwrap sorted)+ return ()+ return ()++main :: IO ()+main = do+ [loc] <- getArgs+ case loc of+ "primary" -> runChoreography config mainChoreo "primary"+ "worker1" -> runChoreography config mainChoreo "worker1"+ "worker2" -> runChoreography config mainChoreo "worker2"+ return ()+ where+ config =+ mkHttpConfig+ [ ("primary", ("localhost", 3000)),+ ("worker1", ("localhost", 4000)),+ ("worker2", ("localhost", 5000))+ ]
+ examples/HasChor/playground/Main.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE LambdaCase #-}++module Main where++import Choreography+import Control.Monad+import Data.Proxy+import System.Environment++-- Step 1: Defining locations+alice :: Proxy "alice"+alice = Proxy++-- Step 2: Writing a choreography+choreography :: Choreo IO (() @ "alice")+choreography = do+ alice `locally` \_ -> putStrLn "Hello, world!"++-- Step 3: Projecting and running the chreography+main :: IO ()+main = do+ args <- getArgs+ case args of+ [loc] -> void $ runChoreography cfg choreography loc+ _ -> error "wrong usage: must provide exactly one location"+ where+ -- Step 4: Mapping locations to HTTP ports+ cfg = mkHttpConfig [ ("alice", ("localhost", 4242))+ ]
+ examples/HasChor/quicksort/Main.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE LambdaCase #-}++module Main where++import Choreography (runChoreography)+import Choreography.Choreo+import Choreography.Location+import Choreography.Network.Local+import Control.Concurrent.Async (async, mapConcurrently_, wait)+import Data.Proxy+import Data.Time+import GHC.TypeLits (KnownSymbol)+import System.Environment++primary :: Proxy "primary"+primary = Proxy++worker1 :: Proxy "worker1"+worker1 = Proxy++worker2 :: Proxy "worker2"+worker2 = Proxy++quicksort :: (KnownSymbol a, KnownSymbol b, KnownSymbol c) => Proxy a -> Proxy b -> Proxy c -> [Int] @ a -> Choreo IO ([Int] @ a)+quicksort a b c lst = do+ isEmpty <- a `locally` \unwrap -> pure (null (unwrap lst))+ cond (a, isEmpty) \case+ True -> do+ a `locally` \_ -> pure []+ False -> do+ smaller <- (a, \unwrap -> let x : xs = unwrap lst in pure [i | i <- xs, i <= x]) ~~> b+ smaller' <- quicksort b c a smaller+ smaller'' <- (b, smaller') ~> a+ bigger <- (a, \unwrap -> let x : xs = unwrap lst in pure [i | i <- xs, i > x]) ~~> c+ bigger' <- quicksort c a b bigger+ bigger'' <- (c, bigger') ~> a+ a `locally` \unwrap -> pure $ unwrap smaller'' ++ [head (unwrap lst)] ++ unwrap bigger''++mainChoreo :: Choreo IO ()+mainChoreo = do+ lst <- primary `locally` \unwrap -> do return [1, 6, 5, 3, 4, 2, 7, 8]+ sorted <- quicksort primary worker1 worker2 lst+ primary `locally` \unwrap -> do+ print (unwrap sorted)+ return ()+ return ()++main :: IO ()+main = do+ config <- mkLocalConfig locs+ mapConcurrently_ (runChoreography config mainChoreo) locs+ return ()+ where+ locs = ["primary", "worker1", "worker2"]
+ examples/HasChor/ring-leader/Main.hs view
@@ -0,0 +1,82 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE LambdaCase #-}++module Main where++import Choreography+import Data.Proxy+import GHC.TypeLits (KnownSymbol)+import Control.Monad+import Control.Monad.Trans.Class+import Control.Monad.Trans.State+import System.Environment++-- an edge of the ring is represented as a tuple of two locaitons l and l' where+-- l is on the left of l'+data Edge = forall l l'.+ (KnownSymbol l, KnownSymbol l') => Edge (Proxy l) (Proxy l')++-- a ring is a sequence of edges+type Ring = [Edge]++type Label = Int++ringLeader :: Ring -> Choreo (StateT Label IO) ()+ringLeader ring = loop ring+ where+ loop :: Ring -> Choreo (StateT Label IO) ()+ loop [] = loop ring+ loop (x:xs) = do+ finished <- talkToRight x+ if finished+ then return ()+ else loop xs++ talkToRight :: Edge -> Choreo (StateT Label IO) Bool+ talkToRight (Edge left right) = do+ labelLeft <- (left, \_ -> get) ~~> right+ labelRight <- right `locally` \_ -> get++ finished <- right `locally` \un ->+ return $ un labelLeft == un labelRight++ cond (right, finished) \case+ True -> do+ right `locally` \_ -> lift $ putStrLn "I'm the leader"+ return True+ False -> do+ right `locally` \un -> put (max (un labelLeft) (un labelRight))+ return False++nodeA :: Proxy "A"+nodeA = Proxy++nodeB :: Proxy "B"+nodeB = Proxy++nodeC :: Proxy "C"+nodeC = Proxy++nodeD :: Proxy "D"+nodeD = Proxy++ring = [ Edge nodeA nodeB+ , Edge nodeB nodeC+ , Edge nodeC nodeD+ , Edge nodeD nodeA+ ]++main :: IO ()+main = do+ [loc] <- getArgs+ putStrLn "Please input a label:"+ label <- read <$> getLine+ runStateT (runChoreography config (ringLeader ring) loc) label+ return ()+ where+ config = mkHttpConfig [ ("A", ("localhost", 4242))+ , ("B", ("localhost", 4343))+ , ("C", ("localhost", 4444))+ , ("D", ("localhost", 4545))+ ]
+ examples/clean-room/Main.hs view
@@ -0,0 +1,170 @@+{-# OPTIONS_GHC -Wno-redundant-constraints #-}++{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ParallelListComp #-}++module Main where++import Control.Monad(replicateM)+import Data.Foldable(fold)+import Data.Function((&))+import Data.Proxy(Proxy(..))+import Data.SOP.BasicFunctors(mapKK, K (..))+import Data.SOP.Classes(HCollapse(..), hmap, hsequenceK)+import Data.SOP.Constraint(All)+import Data.SOP.NP(NP(..))+import GHC.TypeLits(KnownSymbol, SSymbol, withKnownSymbol, withSomeSSymbol)+import System.Environment(getArgs)+import System.Random(randomIO, randomRIO)++import Choreography+import Choreography.Location+import Choreography.Location.Multi++-- Schema and dataset handling (illustrative)++-- For simplicity, all datasets follow the same trivial schema: a list of numbers+data Schema = Schema+ deriving stock (Show, Read)++newtype Dataset = Dataset [Int]+ deriving stock (Show, Read)+ deriving newtype (Semigroup, Monoid)++data Query+ = Len+ | Sum+ | Avg+ | Min+ | Max+ deriving stock (Eq, Ord, Show, Read, Enum, Bounded)++loadSchema :: KnownSymbol l => Proxy l -> IO Schema+loadSchema _ = pure Schema++mergeSchemas :: [Schema] -> Schema+mergeSchemas _ = Schema++conformsToSchema :: Schema -> Dataset -> IO Bool+conformsToSchema _ _ = randomIO -- To simulate failures++emptyDataset :: Dataset+emptyDataset = Dataset []++-- Simulate some I/O to load a dataset (e.g. from a file)+loadDataset :: KnownSymbol l => Proxy l -> IO Dataset+loadDataset _ = do+ n <- randomRIO (5, 10)+ Dataset <$> replicateM n (randomRIO (-100, 100))++mergeDatasets :: [Dataset] -> Dataset+mergeDatasets = fold++-- Simulate the selection of a query (by picking one randomly)+loadQuery :: KnownSymbol l => Proxy l -> Schema -> IO Query+loadQuery _ _ = toEnum <$> randomRIO (fromEnum @Query minBound, fromEnum @Query maxBound)++agreeWithQueries :: KnownSymbol l => Proxy l -> [Query] -> IO Bool+agreeWithQueries _ _ = pure True++runQuery :: Dataset -> Query -> IO Int+runQuery (Dataset ds) q =+ pure $+ case q of+ Len -> length ds+ Sum -> sum ds+ Avg -> sum ds `quot` length ds+ Min -> minimum ds+ Max -> maximum ds++-- Error-handling (if datasets don't conform to schema)++getClientDataset :: (KnownSymbol server, KnownSymbol client) => Int -> Schema @ server -> Proxy server -> Proxy client -> Choreo IO (Dataset @ server)+getClientDataset tries schema server client =+ if tries <= 0 then+ pure $ wrap emptyDataset+ else do+ dataset <- (client, \_ -> loadDataset client) ~~> server+ cond_' (server, \unwrap -> conformsToSchema (unwrap schema) (unwrap dataset)) \case+ True -> pure dataset+ False -> getClientDataset (tries - 1) schema server client++getClientDatasets :: (KnownSymbol server, All KnownSymbol clients) => Int -> Schema @ server -> Proxy server -> NP Proxy clients -> Choreo IO (NP (K Dataset) clients @ server)+getClientDatasets _ _ _ Nil = pure $ wrap Nil+getClientDatasets tries schema server (client :* clients) = do+ dataset <- getClientDataset tries schema server client+ datasets <- getClientDatasets tries schema server clients+ locally server \unwrap -> pure $ K (unwrap dataset) :* unwrap datasets++-- Main choreography++cleanRoom :: (KnownSymbol server, All KnownSymbol clients) => Proxy server -> NP Proxy clients -> Choreo IO ()+cleanRoom server clients = do+ -- Every client loads their data schema and sends it to the server+ schemas <- (clients, \c _ -> loadSchema c) *~~> server++ -- Server merges the schemas+ finalSchemaS <- locally server \unwrap -> pure $ mergeSchemas $ hcollapse $ unwrap schemas++ -- Server broadcasts the final schema+ finalSchema <- (server, finalSchemaS) ~>* clients++ -- Based on the final schema, each client computes what query it wants to run, and sends it back to the server+ queriesS <- (clients, \c unwrap -> loadQuery c $ unwrap finalSchema) *~~> server++ -- Server broadcasts the list of queries to all clients+ queries <- (server, \unwrap -> pure $ hcollapse $ unwrap queriesS) ~~>* clients++ -- Each client locally decides if they agree with everyone else's queries, and sends back a response+ agreements <- (clients, \c unwrap -> agreeWithQueries c $ unwrap queries) *~~> server++ -- Server branches on the responses+ cond_' (server, \unwrap -> pure $ and $ hcollapse $ unwrap agreements) \case+ False -> do -- Someone disagrees, end the protocol+ multilocally clients \_ _ -> print "Disagreement"+ pure $ wrap ()+ True -> do -- Everyone agrees, proceed+ -- The amount of times the server will ask for a valid dataset+ let tries = 5++ -- Clients send their datasets to the server+ datasets <- getClientDatasets tries finalSchemaS server clients++ -- Server locally merges the datasets and runs all the queries+ resultsS <- locally server \unwrap -> do+ let aggregatedDataset = mergeDatasets $ hcollapse $ unwrap datasets+ hsequenceK $ hmap (mapKK $ runQuery aggregatedDataset) $ unwrap queriesS++ -- Server sends the result of each query "pointwise"+ results <- (server, resultsS) ~>. clients++ -- Clients consume the received query results+ multilocally clients \_ unwrap -> print $ unwrap results+ pure $ wrap ()++ pure ()++-- Running the choreography++withProxy :: String -> (forall s. KnownSymbol s => Proxy s -> a) -> a+withProxy s f = withSomeSSymbol s \(ss :: SSymbol s) -> withKnownSymbol ss $ f @s Proxy++withProxies :: [String] -> (forall ss. All KnownSymbol ss => NP Proxy ss -> a) -> a+withProxies [] f = f Nil+withProxies (s : ss) f =+ withProxy s \s ->+ withProxies ss \ss ->+ f $ s :* ss++httpConfig :: [String] -> HttpConfig+httpConfig ls = mkHttpConfig [(l, ("localhost", port)) | l <- ls | port <- [3000..]]++main :: IO ()+main = do+ self : ls@(server : clients) <- getArgs+ runChoreography (httpConfig ls) (cleanRoom & withProxy server & withProxies clients) self
+ examples/unsoundness/Main.hs view
@@ -0,0 +1,102 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE ParallelListComp #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ViewPatterns #-}++module Main where++import Data.Proxy(Proxy(..))+import Data.IORef (newIORef, readIORef, writeIORef)+import Control.Concurrent (forkIO, killThread, threadDelay)+import Control.Monad.IO.Class (MonadIO(..))+import System.Environment(getArgs)++import Servant.API+import Servant.Server (serve)+import Network.Wai.Handler.Warp (run)+import Network.HTTP.Client (defaultManagerSettings, newManager)+import Servant.Client (client, runClientM, BaseUrl(..), mkClientEnv, Scheme(..))++import Choreography+import Choreography.Location+import System.Exit (exitSuccess)++a :: Proxy "a"+a = Proxy++b :: Proxy "b"+b = Proxy++c :: Proxy "c"+c = Proxy++type API = "send" :> Capture "from" LocTm :> ReqBody '[PlainText] String :> PostNoContent++httpSend :: Show a => LocTm -> Int -> a -> IO ()+httpSend sender targetPort a = liftIO $ do+ mgr <- newManager defaultManagerSettings+ res <- runClientM (client @API Proxy sender $ show a) $ mkClientEnv mgr $ BaseUrl Http "localhost" targetPort ""++ case res of+ Left err -> putStrLn $ "Error: " <> show err+ Right _ -> pure ()++ threadDelay 1000000++httpRecv :: Int -> IO (Maybe String)+httpRecv port = do+ ref <- newIORef Nothing++ recvThread <- forkIO do+ run port $ serve @API Proxy \_ msg -> do+ NoContent <$ liftIO (writeIORef ref $ Just msg)++ threadDelay 1000000+ killThread recvThread++ readIORef ref++-- "Frontrun" choreographic communication by sending data to the same port.+frontrun :: Choreo IO ()+frontrun = do+ locally a \_ -> do+ httpSend "a" 3001 False+ exitSuccess++ cond (a, wrap True) \case+ False -> pure ()+ True -> do+ (b, wrap 2) ~> c+ pure ()++-- Open an an "under-the-table" HTTP connecion between `a` and `b` to exfiltrate data from `c`.+collusion :: Choreo IO ()+collusion = do+ locally a \_ -> do+ v <- httpRecv 2999+ print v++ vb <- (c, wrap 2) ~> b++ locally b \unwrap -> do+ httpSend "b" 2999 $ unwrap vb++ pure ()++-- Helpers for running the choreographies++choreos :: [Choreo IO ()]+choreos = [frontrun, collusion]++-- Assigns different ports to each choreography so they can be run in parallel.+httpConfig :: Int -> [LocTm] -> HttpConfig+httpConfig choreo ls = mkHttpConfig [(l, ("localhost", port)) | l <- ls | port <- [3000 + 1000 * choreo..]]++main :: IO ()+main = do+ [read -> choreo, self] <- getArgs+ runChoreography (httpConfig choreo ["a", "b", "c"]) (choreos !! choreo) self
+ src/Choreography.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE ExplicitNamespaces #-}++-- | This module defines the interface to HasChor. The client of the library is+-- highly recommended to only use constructs exported by this module.+module Choreography (+ -- * Locations and Located Values+ LocTm,+ LocTy,+ type (@),+ mkLoc,++ -- * The Choreo monad+ Choreo,+ -- ** Choreo operations+ locally,+ (~>),+ (~~>),+ cond,+ cond',+ cond_,+ cond_',++ -- * Message transport backends+ -- ** The HTTP backend+ Host,+ Port,+ HttpConfig,+ mkHttpConfig,++ -- * Running choreographies+ runChoreo,+ runChoreography+ ) where++import Choreography.Location+import Choreography.Choreo+import Choreography.Network+import Choreography.Network.Http+import Control.Monad.IO.Class++-- | Run a choreography with a message transport backend.+runChoreography :: (Backend config, MonadIO m) => config -> Choreo m a -> LocTm -> m a+runChoreography cfg choreo l = runNetwork cfg l (epp (locs cfg) choreo l)
+ src/Choreography/Choreo.hs view
@@ -0,0 +1,190 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE LambdaCase #-}++-- | This module defines `Choreo`, the monad for writing choreographies.+module Choreography.Choreo where++import Control.Applicative(empty)+import Control.Monad.State.Strict(StateT, execStateT, modify')+import Data.Foldable(foldMap')+import Data.Functor(($>))+import Data.List(delete)+import Data.Maybe(fromMaybe)+import Data.Proxy(Proxy(..))+import Data.Set(Set)+import Data.Set qualified as S+import GHC.TypeLits(KnownSymbol)++import Choreography.Location+import Choreography.Network+import Control.Monad.Freer++-- * The Choreo monad++-- | A constrained version of `unwrap` that only unwraps values located at a+-- specific location.+type Unwrap l = forall a. a @ l -> a++-- | Effect signature for the `Choreo` monad. @m@ is a monad that represents+-- local computations.+data ChoreoSig m a where+ Local :: (KnownSymbol l)+ => Proxy l+ -> (Unwrap l -> m a)+ -> ChoreoSig m (a @ l)++ Comm :: (Show a, Read a, KnownSymbol l, KnownSymbol l')+ => Proxy l+ -> a @ l+ -> Proxy l'+ -> ChoreoSig m (a @ l')++ Cond :: (Show a, Read a, KnownSymbol l)+ => Proxy l+ -> a @ l+ -> (a -> Choreo m b)+ -> ChoreoSig m b++ Cond_ :: (Bounded a, Enum a, Show a, Read a, KnownSymbol l)+ => Proxy l+ -> a @ l+ -> (a -> Choreo m (b @ l))+ -> ChoreoSig m (b @ l)++-- | Monad for writing choreographies.+type Choreo m = Freer (ChoreoSig m)++-- | Run a `Choreo` monad directly.+runChoreo :: Monad m => Choreo m a -> m a+runChoreo = interpFreer handler+ where+ handler :: Monad m => ChoreoSig m a -> m a+ handler (Local _ m) = wrap <$> m unwrap+ handler (Comm _ a _) = return $ (wrap . unwrap) a+ handler (Cond _ a c) = runChoreo $ c (unwrap a)+ handler (Cond_ _ a c) = runChoreo $ c (unwrap a)++-- Static analysis++insertLoc :: KnownSymbol l => Proxy l -> Set LocTm -> Set LocTm+insertLoc = S.insert . toLocTm++insertLocs :: [LocTm] -> Set LocTm -> Set LocTm+insertLocs = S.union . S.fromList++-- | Apply function `f` to every possible value of type `a`, and collect all the resulting `participants`.+enumerate :: (Bounded a, Enum a) => [LocTm] -> (a -> Choreo m b) -> Set LocTm+enumerate ls f = foldMap' (participants ls . f) [minBound ..]++-- | Compute the set of locations participating in this choreography.+participants :: [LocTm] -> Choreo m a -> Set LocTm+participants ls = fromMaybe (S.fromList ls) . (`execStateT` S.empty) . interpFreer handler+ where+ handler :: ChoreoSig m a -> StateT (Set LocTm) Maybe a+ handler = \case+ Local l _ -> modify' (insertLoc l) $> Empty+ Comm l _ l' -> modify' (insertLoc l . insertLoc l') $> Empty+ Cond _ _ _ -> modify' (insertLocs ls) *> empty+ Cond_ l _ f -> modify' (insertLoc l . S.union (enumerate ls f)) $> Empty++-- | Endpoint projection.+epp :: [LocTm] -> Choreo m a -> LocTm -> Network m a+epp ls c l' = interpFreer handler c+ where+ handler :: ChoreoSig m a -> Network m a+ handler (Local l m)+ | toLocTm l == l' = wrap <$> run (m unwrap)+ | otherwise = return Empty+ handler (Comm s a r)+ | l' == s' && s' == r' = return $ wrap (unwrap a)+ | s' == l' = send (unwrap a) r' >> return Empty+ | r' == l' = wrap <$> recv s'+ | otherwise = return Empty+ where+ s' = toLocTm s+ r' = toLocTm r+ handler (Cond l a c)+ | toLocTm l == l' = broadcast (unwrap a) (delete l' ls) >> epp ls (c (unwrap a)) l'+ | otherwise = recv (toLocTm l) >>= \x -> epp ls (c x) l'+ handler (Cond_ pl a c)+ | l == l' = broadcast (unwrap a) (S.toList $ S.delete l ls') >> epp ls (c (unwrap a)) l'+ | S.member l' ls' = recv l >>= \x -> epp ls (c x) l'+ | otherwise = pure Empty+ where+ l = toLocTm pl+ ls' = enumerate ls c++-- * Choreo operations++-- | Perform a local computation at a given location.+locally :: KnownSymbol l+ => Proxy l -- ^ Location performing the local computation.+ -> (Unwrap l -> m a) -- ^ The local computation given a constrained+ -- unwrap funciton.+ -> Choreo m (a @ l)+locally l m = toFreer (Local l m)++-- | Communication between a sender and a receiver.+(~>) :: (Show a, Read a, KnownSymbol l, KnownSymbol l')+ => (Proxy l, a @ l) -- ^ A pair of a sender's location and a value located+ -- at the sender+ -> Proxy l' -- ^ A receiver's location.+ -> Choreo m (a @ l')+(~>) (l, a) l' = toFreer (Comm l a l')++-- | Conditionally execute choreographies based on a located value.+cond :: (Show a, Read a, KnownSymbol l)+ => (Proxy l, a @ l) -- ^ A pair of a location and a scrutinee located on+ -- it.+ -> (a -> Choreo m b) -- ^ A function that describes the follow-up+ -- choreographies based on the value of scrutinee.+ -> Choreo m b+cond (l, a) c = toFreer (Cond l a c)++-- | Conditionally execute choreographies at participating locations,+-- based on a located value.+cond_ :: (Bounded a, Enum a, Show a, Read a, KnownSymbol l)+ => (Proxy l, a @ l) -- ^ A pair of a location and a scrutinee located on+ -- it.+ -> (a -> Choreo m (b @ l)) -- ^ A function that describes the follow-up+ -- choreographies based on the value of scrutinee.+ -> Choreo m (b @ l)+cond_ (l, a) c = toFreer (Cond_ l a c)++-- | A variant of `~>` that sends the result of a local computation.+(~~>) :: (Show a, Read a, KnownSymbol l, KnownSymbol l')+ => (Proxy l, Unwrap l -> m a) -- ^ A pair of a sender's location and a local+ -- computation.+ -> Proxy l' -- ^ A receiver's location.+ -> Choreo m (a @ l')+(~~>) (l, m) l' = do+ x <- l `locally` m+ (l, x) ~> l'++-- | A variant of `cond` that conditonally executes choregraphies based on the+-- result of a local computation.+cond' :: (Show a, Read a, KnownSymbol l)+ => (Proxy l, Unwrap l -> m a) -- ^ A pair of a location and a local+ -- computation.+ -> (a -> Choreo m b) -- ^ A function that describes the follow-up+ -- choreographies based on the result of the+ -- local computation.+ -> Choreo m b+cond' (l, m) c = do+ x <- l `locally` m+ cond (l, x) c++-- | A variant of `cond_` that conditonally executes choregraphies at participating locations,+-- based on the result of a local computation.+cond_' :: (Bounded a, Enum a, Show a, Read a, KnownSymbol l)+ => (Proxy l, Unwrap l -> m a) -- ^ A pair of a location and a local+ -- computation.+ -> (a -> Choreo m (b @ l)) -- ^ A function that describes the follow-up+ -- choreographies based on the result of the+ -- local computation.+ -> Choreo m (b @ l)+cond_' (l, m) c = do+ x <- l `locally` m+ cond_ (l, x) c
+ src/Choreography/Location.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE DataKinds #-}++-- | This module defines locations and located values.+module Choreography.Location where++import Data.Proxy+import GHC.TypeLits+import Language.Haskell.TH++-- | Term-level locations.+type LocTm = String++-- | Type-level locations.+type LocTy = Symbol++-- | Convert a type-level location to a term-level location.+toLocTm :: forall (l :: LocTy). KnownSymbol l => Proxy l -> LocTm+toLocTm = symbolVal++-- | Located values.+--+-- @a \@ l@ represents a value of type @a@ at location @l@.+data a @ (l :: LocTy)+ = Wrap a -- ^ A located value @a \@ l@ from location @l@'s perspective.+ | Empty -- ^ A located value @a \@ l@ from locations other than @l@'s+ -- perspective.++-- | Wrap a value as a located value.+wrap :: a -> a @ l+wrap = Wrap++-- | Unwrap a located value.+--+-- /Note:/ Unwrapping a empty located value will throw an exception.+unwrap :: a @ l-> a+unwrap (Wrap a) = a+unwrap Empty = error "this should never happen for a well-typed choreography"++-- | Define a location at both type and term levels.+mkLoc :: String -> Q [Dec]+mkLoc loc = do+ let locName = mkName loc+ let p = mkName "Data.Proxy.Proxy"+ pure [SigD locName (AppT (ConT p) (LitT (StrTyLit loc))),ValD (VarP locName) (NormalB (ConE p)) []]
+ src/Choreography/Location/Multi.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE PartialTypeSignatures #-}+{-# LANGUAGE TypeFamilies #-}++-- | This module defines multiply-located values and combinators for manipulating them.+module Choreography.Location.Multi where++import GHC.TypeLits(KnownSymbol)+import Data.Proxy(Proxy(..))+import Data.SOP.BasicFunctors(K(..), mapKK, unK)+import Data.SOP.Classes(HSequence(..), hmap)+import Data.SOP.Constraint(All)+import Data.SOP.NP(NP(..), hd, tl)++import Choreography.Choreo+import Choreography.Location++-- | The type of multiply-located values.+type a @@ ls = NP ((@) a) ls++-- | "scatter": Send a located value from a single location to many others.+(~>*) :: (KnownSymbol l, All KnownSymbol ls, Show a, Read a) => (Proxy l, a @ l) -> NP Proxy ls -> Choreo m (a @@ ls)+(l, a) ~>* ls = hctraverse' (Proxy @KnownSymbol) ((l, a) ~>) ls++infix 4 ~>*++-- | "gather": Send a multiply-located value from many locations to one.+(*~>) :: (KnownSymbol l, All KnownSymbol ls, Applicative m, Show a, Read a) => a @@ ls -> Proxy l -> Choreo m (NP (K a) ls @ l)+as *~> l = do+ as' <- hctraverse' (Proxy @KnownSymbol) (\a -> K <$> ((Proxy, a) ~> l)) as+ locally l \unwrap -> pure $ hmap (mapKK unwrap) as'++infix 4 *~>++-- | Send a list of values from one location to many others "pointwise" (i.e. each target gets one value).+(~>.) :: (KnownSymbol l, All KnownSymbol ls, Applicative m, Show a, Read a) => (Proxy l, NP (K a) ls @ l) -> NP Proxy ls -> Choreo m (a @@ ls)+_ ~>. Nil = pure Nil+(l, as) ~>. (l' :* ls') = do+ a <- locally l \unwrap -> pure $ unK $ hd $ unwrap as+ as' <- locally l \unwrap -> pure $ tl $ unwrap as+ (:*) <$> ((l, a) ~> l') <*> ((l, as') ~>. ls')++infix 4 ~>.++-- | Multiply-located version of `Unwrap`.+type Unwraps ls = forall a. a @@ ls -> a++-- | The type of a (multi)local computation.+type LocalComp ls m a = forall l. KnownSymbol l => Proxy l -> Unwraps ls -> m a++-- | Multiply-located version of `locally`.+multilocally :: forall ls m a. All KnownSymbol ls => NP Proxy ls -> LocalComp ls m a -> Choreo m (a @@ ls)+multilocally ls f = go ls id+ where+ go :: forall ls'. All KnownSymbol ls' => NP Proxy ls' -> (forall a. a @@ ls -> a @@ ls') -> Choreo m (a @@ ls')+ go Nil _pick = pure Nil+ go (l :* ls) pick = do+ a <- locally l \unwrap -> f l $ unwrap . hd . pick+ (a :*) <$> go ls (tl . pick)++-- | A variant of `~>*` that sends the result of a local computation.+(~~>*) :: (KnownSymbol l, All KnownSymbol ls, Show a, Read a) => (Proxy l, Unwrap l -> m a) -> NP Proxy ls -> Choreo m (a @@ ls)+(l, f) ~~>* ls = do+ a <- locally l f+ (l, a) ~>* ls++infix 4 ~~>*++-- | A variant of `*~>` that sends the result of a local computation.+(*~~>) :: (KnownSymbol l, All KnownSymbol ls, Applicative m, Show a, Read a) => (NP Proxy ls, LocalComp ls m a) -> Proxy l -> Choreo m (NP (K a) ls @ l)+(ls, f) *~~> l = do+ a <- multilocally ls f+ a *~> l++infix 4 *~~>
+ src/Choreography/Network.hs view
@@ -0,0 +1,61 @@+-- | This module defines the `Network` monad, which represents programs run on+-- individual nodes in a distributed system with explicit sends and receives.+-- To run a `Network` program, we provide a `runNetwork` function that supports+-- multiple message transport backends.+module Choreography.Network where++import Choreography.Location+import Control.Monad.Freer+import Control.Monad.IO.Class++-- * The Network monad++-- | Effect signature for the `Network` monad.+data NetworkSig m a where+ -- | Local computation.+ Run :: m a+ -> NetworkSig m a+ -- | Sending.+ Send :: Show a+ => a+ -> LocTm+ -> NetworkSig m ()+ -- | Receiving.+ Recv :: Read a+ => LocTm+ -> NetworkSig m a+ -- | Broadcasting.+ BCast :: Show a+ => a+ -> [LocTm]+ -> NetworkSig m ()++-- | Monad that represents network programs.+type Network m = Freer (NetworkSig m)++-- * Network operations++-- | Perform a local computation.+run :: m a -> Network m a+run m = toFreer $ Run m++-- | Send a message to a receiver.+send :: Show a => a -> LocTm -> Network m ()+send a l = toFreer $ Send a l++-- | Receive a message from a sender.+recv :: Read a => LocTm -> Network m a+recv l = toFreer $ Recv l++-- | Broadcast a message to all participants.+broadcast :: Show a => a -> [LocTm] -> Network m ()+broadcast a ls = toFreer $ BCast a ls++-- * Message transport backends++-- | A message transport backend defines a /configuration/ of type @c@ that+-- carries necessary bookkeeping information, then defines @c@ as an instance+-- of `Backend` and provides a `runNetwork` function.+class Backend c where+ locs :: c -> [LocTm]+ runNetwork :: MonadIO m => c -> LocTm -> Network m a -> m a
+ src/Choreography/Network/Http.hs view
@@ -0,0 +1,112 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}++-- | This module implments the HTTP message transport backend for the `Network`+-- monad.+module Choreography.Network.Http where++import Choreography.Location+import Choreography.Network hiding (run)+import Data.Proxy (Proxy(..))+import Data.HashMap.Strict (HashMap, (!))+import Data.HashMap.Strict qualified as HashMap+import Network.HTTP.Client (Manager, defaultManagerSettings, newManager)+import Servant.API hiding (Host)+import Servant.Client (ClientM, client, runClientM, BaseUrl(..), mkClientEnv, Scheme(..))+import Servant.Server (Handler, Server, serve)+import Control.Concurrent+import Control.Monad+import Control.Monad.Freer+import Control.Monad.IO.Class+import Network.Wai.Handler.Warp (run)++-- * Servant API++type API = "send" :> Capture "from" LocTm :> ReqBody '[PlainText] String :> PostNoContent++-- * Http configuration++-- | The HTTP backend configuration specifies how locations are mapped to+-- network hosts and ports.+newtype HttpConfig = HttpConfig+ { locToUrl :: HashMap LocTm BaseUrl+ }++type Host = String+type Port = Int++-- | Create a HTTP backend configuration from a association list that maps+-- locations to network hosts and ports.+mkHttpConfig :: [(LocTm, (Host, Port))] -> HttpConfig+mkHttpConfig = HttpConfig . HashMap.fromList . fmap (fmap f)+ where+ f :: (Host, Port) -> BaseUrl+ f (host, port) = BaseUrl+ { baseUrlScheme = Http+ , baseUrlHost = host+ , baseUrlPort = port+ , baseUrlPath = ""+ }++locsHttp :: HttpConfig -> [LocTm]+locsHttp = HashMap.keys . locToUrl++-- * Receiving channels++type RecvChans = HashMap LocTm (Chan String)++mkRecvChans :: HttpConfig -> IO RecvChans+mkRecvChans cfg = foldM f HashMap.empty (locsHttp cfg)+ where+ f :: HashMap LocTm (Chan String) -> LocTm+ -> IO (HashMap LocTm (Chan String))+ f hm l = do+ c <- newChan+ return $ HashMap.insert l c hm++-- * HTTP backend++runNetworkHttp :: MonadIO m => HttpConfig -> LocTm -> Network m a -> m a+runNetworkHttp cfg self prog = do+ mgr <- liftIO $ newManager defaultManagerSettings+ chans <- liftIO $ mkRecvChans cfg+ recvT <- liftIO $ forkIO (recvThread cfg chans)+ result <- runNetworkMain mgr chans prog+ -- liftIO $ threadDelay 1000000 -- wait until all outstanding requests to be completed+ liftIO $ killThread recvT+ return result+ where+ runNetworkMain :: MonadIO m => Manager -> RecvChans -> Network m a -> m a+ runNetworkMain mgr chans = interpFreer handler+ where+ handler :: MonadIO m => NetworkSig m a -> m a+ handler (Run m) = m+ handler (Send a l) = liftIO $ do+ res <- runClientM (send self $ show a) (mkClientEnv mgr (locToUrl cfg ! l))+ case res of+ Left err -> putStrLn $ "Error : " ++ show err+ Right _ -> return ()+ handler (Recv l) = liftIO $ read <$> readChan (chans ! l)+ -- handler (Recv l) = liftIO $ putStrLn ("Recv from " <> l) *> (read <$> readChan (chans ! l))+ handler (BCast a ls) = mapM_ handler $ fmap (Send a) ls++ api :: Proxy API+ api = Proxy++ send :: LocTm -> String -> ClientM NoContent+ send = client api++ server :: RecvChans -> Server API+ server chans = handler+ where+ handler :: LocTm -> String -> Handler NoContent+ handler rmt msg = do+ liftIO $ writeChan (chans ! rmt) msg+ return NoContent++ recvThread :: HttpConfig -> RecvChans -> IO ()+ recvThread cfg chans = run (baseUrlPort $ locToUrl cfg ! self ) (serve api $ server chans)++instance Backend HttpConfig where+ locs = locsHttp+ runNetwork = runNetworkHttp
+ src/Choreography/Network/Local.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE GADTs #-}++-- | This module defines the multi-thread backend for the `Network` monad.+module Choreography.Network.Local where++import Choreography.Location+import Choreography.Network+import Control.Concurrent+import Control.Monad+import Control.Monad.Freer+import Control.Monad.IO.Class+import Data.HashMap.Strict (HashMap, (!))+import Data.HashMap.Strict qualified as HashMap++-- | Each location is associated with a message buffer which stores messages sent+-- from other locations.+type MsgBuf = HashMap LocTm (Chan String)++newtype LocalConfig = LocalConfig+ { locToBuf :: HashMap LocTm MsgBuf+ }++newEmptyMsgBuf :: [LocTm] -> IO MsgBuf+newEmptyMsgBuf = foldM f HashMap.empty+ where+ f hash loc = do+ chan <- newChan+ return (HashMap.insert loc chan hash)++mkLocalConfig :: [LocTm] -> IO LocalConfig+mkLocalConfig locs = LocalConfig <$> foldM f HashMap.empty locs+ where+ f hash loc = do+ buf <- newEmptyMsgBuf locs+ return (HashMap.insert loc buf hash)++locsLocal :: LocalConfig -> [LocTm]+locsLocal = HashMap.keys . locToBuf++runNetworkLocal :: MonadIO m => LocalConfig -> LocTm -> Network m a -> m a+runNetworkLocal cfg self prog = interpFreer handler prog+ where+ handler :: MonadIO m => NetworkSig m a -> m a+ handler (Run m) = m+ handler (Send a l) = liftIO $ writeChan ((locToBuf cfg ! l) ! self) (show a)+ handler (Recv l) = liftIO $ read <$> readChan ((locToBuf cfg ! self) ! l)+ handler (BCast a ls) = mapM_ handler $ fmap (Send a) ls++instance Backend LocalConfig where+ locs = locsLocal+ runNetwork = runNetworkLocal
+ src/Choreography/Network/LocalMsgCount.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE GADTs #-}++-- | This module defines an instrumented version of the `Local` backend,+-- which counts how many `Send` operations are performed during execution of the choreography.+-- It is used by the benchmarks in `bench/`.+module Choreography.Network.LocalMsgCount where++import Choreography.Location+import Choreography.Network+import Control.Concurrent+import Control.Monad+import Control.Monad.Freer+import Control.Monad.IO.Class+import Data.HashMap.Strict (HashMap, (!))+import Data.HashMap.Strict qualified as HashMap+import Data.IORef++-- | Each location is associated with a message buffer which stores messages sent+-- from other locations.+type MsgBuf = HashMap LocTm (Chan String)++data LocalMsgCountConfig = LocalMsgCountConfig+ { msgCounter :: IORef Int+ , locToBuf :: HashMap LocTm MsgBuf+ }++newEmptyMsgBuf :: [LocTm] -> IO MsgBuf+newEmptyMsgBuf = foldM f HashMap.empty+ where+ f hash loc = do+ chan <- newChan+ return (HashMap.insert loc chan hash)++mkLocalMsgCountConfig :: IORef Int -> [LocTm] -> IO LocalMsgCountConfig+mkLocalMsgCountConfig msgCounter locs = LocalMsgCountConfig msgCounter <$> foldM f HashMap.empty locs+ where+ f hash loc = do+ buf <- newEmptyMsgBuf locs+ return (HashMap.insert loc buf hash)++locsLocalMsgCount :: LocalMsgCountConfig -> [LocTm]+locsLocalMsgCount = HashMap.keys . locToBuf++runNetworkLocalMsgCount :: MonadIO m => LocalMsgCountConfig -> LocTm -> Network m a -> m a+runNetworkLocalMsgCount cfg self prog = interpFreer handler prog+ where+ handler :: MonadIO m => NetworkSig m a -> m a+ handler (Run m) = m+ handler (Send a l) = liftIO $ do+ atomicModifyIORef' (msgCounter cfg) (\c -> (c + 1, ()))+ writeChan ((locToBuf cfg ! l) ! self) (show a)+ handler (Recv l) = liftIO $ read <$> readChan ((locToBuf cfg ! self) ! l)+ handler (BCast a ls) = mapM_ handler $ fmap (Send a) ls++instance Backend LocalMsgCountConfig where+ locs = locsLocalMsgCount+ runNetwork = runNetworkLocalMsgCount
+ src/Control/Monad/Freer.hs view
@@ -0,0 +1,43 @@+-- | This module defines the freer monad `Freer`, which allows manipulating+-- effectful computations algebraically.+module Control.Monad.Freer where++import Control.Monad ((>=>))++-- | Freer monads.+--+-- A freer monad @Freer f a@ represents an effectful computation that returns a+-- value of type @a@. The parameter @f :: * -> *@ is a effect signature that+-- defines the effectful operations allowed in the computation. @Freer f a@ is+-- called a freer monad in that it's a `Monad` given any @f@.+data Freer f a where+ -- | A pure computation.+ Return :: a -> Freer f a+ -- | An effectful computation where the first argument @f b@ is the effect+ -- to perform and returns a result of type @b@; the second argument+ -- @b -> Freer f a@ is a continuation that specifies the rest of the+ -- computation given the result of the performed effect.+ Do :: f b -> (b -> Freer f a) -> Freer f a++instance Functor (Freer f) where+ fmap f (Return a) = Return (f a)+ fmap f (Do eff k) = Do eff (fmap f . k)++instance Applicative (Freer f) where+ pure = Return++ (Return f) <*> a = fmap f a+ (Do eff k) <*> a = Do eff $ (<*> a) . k++instance Monad (Freer f) where+ (Return a) >>= f = f a+ (Do eff k) >>= f = Do eff (k >=> f)++-- | Lift an effect into the freer monad.+toFreer :: f a -> Freer f a+toFreer eff = Do eff Return++-- | Interpret the effects in a freer monad in terms of another monad.+interpFreer :: Monad m => (forall a. f a -> m a) -> Freer f a -> m a+interpFreer _handler (Return a) = return a+interpFreer handler (Do eff k) = handler eff >>= interpFreer handler . k