Skip to content

Commit

Permalink
Doc cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyday567 committed Jul 18, 2023
1 parent 2117c60 commit 3dfc26b
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 14 deletions.
6 changes: 3 additions & 3 deletions box.cabal
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
cabal-version: 2.4
name: box
version: 0.9.2
synopsis: boxes
description: A profunctor effect
synopsis: A profunctor effect system.
description: This might be a profunctor effect system, but unlike all the others. See the project readme.org for usage and discussion: https://github.com/tonyday567/box#readme.org
category: project
homepage: https://github.com/tonyday567/box#readme
bug-reports: https://github.com/tonyday567/box/issues
Expand All @@ -13,7 +13,7 @@ license: BSD-3-Clause
license-file: LICENSE
build-type: Simple
tested-with:
GHC ==8.6.5 || ==8.8.4 || ==8.10.7 || ==9.2.8 || ==9.4.5 || ==9.6.2
GHC ==8.10.7 || ==9.2.8 || ==9.4.5 || ==9.6.2
extra-source-files: ChangeLog.md

source-repository head
Expand Down
154 changes: 151 additions & 3 deletions readme.org
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,113 @@

[[https://hackage.haskell.org/package/box][file:https://img.shields.io/hackage/v/box.svg]] [[https://github.com/tonyday567/box/actions?query=workflow%3Ahaskell-ci][file:https://github.com/tonyday567/box/workflows/haskell-ci/badge.svg]]

* A profunctor effect.
A profunctor effect system.

#+begin_quote
What is all this stuff around me; this stream of experiences that I seem to be having all the time? Throughout history there have been people who say it is all illusion. ~ S Blackmore
#+end_quote

* Usage

#+begin_src haskell
:set prompt "> "
:set -XOverloadedStrings
import Box
import Prelude
#+end_src

* Debugging echo example

#+begin_src haskell :results output
import Prelude
import Box
import Data.Function
import Data.Bool
stdC = Committer (\s -> putStrLn s >> pure True)
emitQuit = Emitter (getLine & fmap (\x -> bool (Just x) Nothing (x =="quit")))
glue stdC emitQuit

#+end_src


** basic narrative
Effects often come in pairs, in two ways, duals that use the same real-world effect system such as standard IO, and effects that bracket a computation. You read from a source (a file, a socket, the screen or an IORef), compute stuff, then write to a target (a file, a socket, the screen or an IORef).

Starting with the dual of reading and writing lines to standard IO:

#+begin_src haskell :results output :exports both
:t getLine
:t putStrLn
#+end_src

#+RESULTS:
: getLine :: IO String
: putStrLn :: String -> IO ()

The design of Haskell tends to encourage coinduction for the (effect) functionality either side of a computation. In practice, this means that it is useful to define effects according to their destructors: how do I run an effect until it is exhausted.

#+begin_src haskell
newtype Emitter m a = Emitter
{ emit :: m (Maybe a)
}
#+end_src

An emitter returns an 'a' on demand until it doesn't.

#+begin_src haskell
newtype Committer m a = Committer
{ commit :: a -> m Bool
}
#+end_src

You give a committer an 'a', and it tells you whether the consumption of the 'a' was successful or not.

A Box is just a product of an emitter and committer, so the standard IO box would be:

#+begin_src haskell
data Box m c e = Box
{ committer :: Committer m c,
emitter :: Emitter m e
}
#+end_src

#+begin_src haskell :results output
stdIO = Box (Committer (\s -> putStrLn s >> pure True)) (Emitter (Just <$> getLine))
#+end_src

#+RESULTS:
#+begin_src haskell :results output
echo = (\string -> bool (pure (Just ("echo: " <> string))) (pure Nothing) (string=="quit"))
#+end_src

#+RESULTS:

#+begin_src haskell :results output
:t echo
#+end_src

#+RESULTS:
: echo
: :: (Applicative f, Semigroup a, Data.String.IsString a, Eq a) =>
: a -
: f (Maybe a)

#+begin_src haskell :results output
:t fuse echo stdIO
#+end_src

#+RESULTS:
: fuse echo stdIO :: IO ()






One possible motivation for Haskell laziness



This library grew out of using pipes. I wanted to try out the streaming library but it didn't yet have a concurrency system, so I ripped out the ends management and queues of pipes-concurrency. After a while I realised I was using this functionality ~instead~ of streams. There was often no stream, just pure computes with management of queues and effects at either end. I've used streamly without looking under the hood and the API, at least, seems somewhat familial.

It's a bit of a hodge-podge, but there's a good idea or two in here somewhere I'm sure.
Expand All @@ -26,6 +127,8 @@ import Box
import Prelude
#+end_src

#+RESULTS:

#+begin_src haskell :results output
glue toStdout <$|> qList ["a","b","c"]
#+end_src
Expand Down Expand Up @@ -144,16 +247,55 @@ This is how we can use a profunctor to glue together two categories ~ Milewski

Non-fix version of glue:

#+begin_src haskell
#+begin_src haskell :results output
:{
import Control.Monad

glue' :: (Monad m) => Committer m a -> Emitter m a -> m ()
glue' c e = go
where
go = do
a <- emit e
c' <- maybe (pure False) (commit c) a
when c' go
:}
#+end_src

#+RESULTS:
: ghci| ghci| ghci| ghci| ghci| ghci| ghci| ghci| ghci| ghci|

#+begin_src haskell :results output
:{
glue'' :: (Show a) => Committer IO a -> Emitter IO a -> IO ()
glue'' c e = go
where
go = do
a <- emit e
putStrLn $ "emitted: " <> show a
c' <- maybe (pure False) (commit c) a
when c' (putStrLn "next go" >> go)
:}
#+end_src

#+RESULTS:
: ghci| ghci| ghci| ghci| ghci| ghci| ghci| ghci| ghci|


#+RESULTS:
#+begin_src haskell :results output
echo = (\string -> bool (pure (Just ("echo: " <> string))) (pure Nothing) (string=="quit"))
#+end_src

#+RESULTS:
: <interactive>:449:1: warning: [GHC-63397] [-Wname-shadowing]
: This binding for ‘echo’ shadows the existing binding
: defined at <interactive>:375:1

#+begin_src haskell :results output
glue'' (committer stdIO) (witherE echo (emitter stdIO))
#+end_src


** finiteness

Most committers and emitters will run forever until the glued or fused other-side returns.
Expand Down Expand Up @@ -241,7 +383,8 @@ glue (contramap (pack . show) toStdout) <$|> qList [1..3]
#+RESULTS:
: fuse :: Monad m => (a -> m (Maybe b)) -> Box m b a -> m ()

#+begin_src haskell
#+begin_src haskell :results output
import Data.Text (pack)
import Data.Bool
let box' = Box <$> pure toStdout <*> qList ((pack . show) <$> [1..3])
fuse (\a -> bool (pure $ Just $ "echo: " <> a) (pure Nothing) (a=="2")) <$|> box'
Expand All @@ -251,6 +394,7 @@ fuse (\a -> bool (pure $ Just $ "echo: " <> a) (pure Nothing) (a=="2")) <$|> box
: echo: 1
: echo: 3


A future direction of the library may be to add this in to a box.

* Continuation operators
Expand Down Expand Up @@ -509,6 +653,10 @@ nucleate ::
f (g ())
nucleate n c e = emit e & n & fmap (commit c)
#+end_src
** Programming with Monoidal Profunctors and Semarrows

This paper may be relevant and contain abstraction that could be usefully backported:
[[https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4496714][Programming with Monoidal Profunctors and Semiarrows by Alexandre Garcia de O...]]
* bugs

This compiles but is broken:
Expand Down
3 changes: 3 additions & 0 deletions src/Box/Functor.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
-- | Some higher-kinded Functor types that make do until we get FunctorOf
--
-- eg https://eevie.ro/posts/2019-05-12-functor-of.html
module Box.Functor
( FFunctor (..),
FoldableM (..),
Expand Down
15 changes: 7 additions & 8 deletions src/Box/IO.hs
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,7 @@ handleC action h = Committer $ \a -> do
action h a
pure True

-- | Commit lines of Text to a handle.
-- handleCBS = handleC Char8.hPutStrLn

-- | Emits lines of Text from a handle.
-- handleCText = handleC Text.hPutStrLn

-- | Emit lines of Text from a file.
-- | Emit from a file.
fileE :: FilePath -> BufferMode -> IOMode -> (Handle -> Emitter IO a) -> CoEmitter IO a
fileE fp b m action = Codensity $ \eio ->
withFile
Expand All @@ -152,13 +146,15 @@ fileE fp b m action = Codensity $ \eio ->
eio (action h)
)

-- | Emit lines of Text from a file.
fileEText :: FilePath -> BufferMode -> CoEmitter IO Text
fileEText fp b = fileE fp b ReadMode (handleE Text.hGetLine)

-- | Emit lines of ByteString from a file.
fileEBS :: FilePath -> BufferMode -> CoEmitter IO ByteString
fileEBS fp b = fileE fp b ReadMode (handleE Char8.hGetLine)

-- | Commit lines of Text to a file.
-- | Commit to a file.
fileC :: FilePath -> IOMode -> BufferMode -> (Handle -> Committer IO a) -> CoCommitter IO a
fileC fp m b action = Codensity $ \cio ->
withFile
Expand All @@ -169,9 +165,12 @@ fileC fp m b action = Codensity $ \cio ->
cio (action h)
)

-- | Commit Text to a file, as a line.
fileCText :: FilePath -> BufferMode -> IOMode -> CoCommitter IO Text
fileCText fp m b = fileC fp b m (handleC Text.hPutStrLn)


-- | Commit ByteString to a file, as a line.
fileCBS :: FilePath -> BufferMode -> IOMode -> CoCommitter IO ByteString
fileCBS fp m b = fileC fp b m (handleC Char8.hPutStrLn)

Expand Down
3 changes: 3 additions & 0 deletions src/Box/Time.hs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ stampE ::
Emitter IO (LocalTime, a)
stampE = witherE (fmap Just . stampNow)

-- | Usually represents seconds.
type Gap = Double

-- | Convert stamped emitter to gap between emits in seconds
Expand Down Expand Up @@ -118,6 +119,7 @@ gapEffect as =
(Just (s, a')) -> sleep s >> pure (Just a')
_ -> pure Nothing

-- | Using the Gap emitter, adjust the Gap for a (Gap, a) emitter
speedEffect ::
Emitter IO Gap ->
Emitter IO (Gap, a) ->
Expand Down Expand Up @@ -166,6 +168,7 @@ speedSkipEffect p e = evalEmitter 0 $ Emitter $ do
(Just (n, s), Just (g, a)) ->
lift $ sleep (bool (g / s) 0 (n >= count)) >> pure (Just a)

-- | Ignore the first n gaps and immediately emit them.
skip :: Int -> Emitter IO (Gap, a) -> CoEmitter IO (Gap, a)
skip sk e = evalEmitter (sk + 1) $ Emitter $ do
skip' <- get
Expand Down

0 comments on commit 3dfc26b

Please sign in to comment.