Difference between revisions of "Output/Input"

From HaskellWiki
Jump to: navigation, search
(Minor changes)
(Added missing definition for `echo_stat` >_<)
Line 173: Line 173:
putchar_cont c (\_ -> echo_cont k)
putchar_cont c (\_ -> echo_cont k)
echo_stat :: IOState -> ((), IOState)
echo_stat s = case getchar_stat s of
(c, s') ->
if c == '\n' then
((), s')
case putchar_stat c s' of
echo_wfth :: IO ()
echo_wfth :: IO ()

Revision as of 02:43, 23 August 2020


Alright then - have a look at this:

data OI                         --  abstract, primitive

getchar :: OI -> Char           --  primitives 


instance Partible OI where ...

class Partible a where
    part  :: a -> (a, a)
    parts :: a -> [a]




 -- for GHC 8.6.5
{-# LANGUAGE BangPatterns #-}
module ClassicIO where
import Prelude(Char, String)
import Prelude(($), (.))
import Data.List(map, foldr, zipWith)
import OutputInput
import Partible

 -- simple text --


runMain_text main       =  \u -> let !(u1, u2) = part u in
                                 putchars (main (getchars u1)) u2

getchars                :: OI -> String
getchars                =  map getchar . parts

putchars                :: String -> OI -> ()

-- dialogues --

{-  main                :: Dialogue  -}

runMain_dial            :: Dialogue -> OI -> ()

type Dialogue           =  [Response] -> [Request]

data Request            =  Getq | Putq Char
data Response           =  Getp Char | Putp

yet                     :: (a -> a) -> a
yet f                   =  f (yet f)
respond                 :: Request -> OI -> Response

-- continuations --


runMain_cont main       =  call (main done)

newtype IOResult        =  R (OI -> ())

call                    :: IOResult -> OI -> ()
call (R a)              =  a

done                    :: () -> IOResult

getchar_cont            :: (Char -> IOResult) -> IOResult
getchar_cont k          =  R $ \u -> let !(u1, u2) = part u in
                                     let !c = getchar u1 in
                                     call (k c) u2

putchar_cont c k        =  R $ \u -> let !(u1, u2) = part u in
                                     seq (putchar c u) (call (k ()) u2)

 -- state-passing --



newtype IOState         =  S OI

ini_st                  :: OI -> IOState
ini_st                  =  S

getchar_stat            :: IOState -> (Char, IOState)
getchar_stat (S u)      =  let !(u1, u2) = part u in
                           let !c = getchar u1 in
                           (c, S u2)

putchar_stat c (S u)    =  let !(u1, u2) = part u in

 -- and those weird, fickle things ;-)

{-  main                :: IO ()  -}

runMain_wfth main       =  main

type IO a               =  OI -> a

getchar_wfth            :: IO Char
getchar_wfth            =  getchar

putchar_wfth            :: Char -> IO ()
putchar_wfth            =  putchar

unit                    :: a -> IO a

bind m k                =  \u -> case part u of

Here are examples for each of those approaches:

module Echoes where
import Prelude(($))
import ClassicIO
import OutputInput(runOI)

echo_text             :: String -> String

echo_dial             :: Dialogue
echo_dial p           =  Getq :
                         case p of
                           Getp c : p' ->
                             if c == '\n' then
                               Putq c :
                               case p' of
                                 Putp : p'' -> echo_dial p''

echo_cont k           =  getchar_cont $ \c ->
                         if c == '\n' then
                           k ()
                           putchar_cont c (\_ -> echo_cont k)

echo_stat             :: IOState -> ((), IOState)
echo_stat s           =  case getchar_stat s of
                           (c, s') ->
                             if c == '\n' then
                               ((), s')
                               case putchar_stat c s' of

echo_wfth             :: IO ()
echo_wfth             =  getchar_wfth     `bind` \c ->
                         if c == '\n' then
                           unit ()
                           putchar_wfth c `bind` \_ -> echo_wfth


This should work as expected[1][2]:

 -- for GHC 8.6.5
{-# LANGUAGE MagicHash, UnboxedTuples #-}
module Sequential(seq) where
import GHC.Base(seq#, realWorld#)

infixr 0 `seq`
seq :: a -> b -> b
x `seq` y = case seq# x realWorld# of
              (# s, _ #) -> case seq# y s of
                              (# _, t #) -> t

It didn't work? Try this instead:

 -- for GHC 8.6.5
#define during seq
module Sequential(seq) where
import qualified Prelude(during)
import GHC.Base(lazy)

infixr 0 `seq`
seq :: a -> b -> b
seq x y = Prelude.during x (lazy y)

As for those extensions - they stay with each definition.

That still didn't work? Well, give this a try:

yet                     :: (a -> a) -> a
yet f                   =  y where y = f y

Now that we're firmly on the topic of implementation details, did you notice how easy it was to define that allegedly warm, fuzzy[3] IO type using this curious new OI type, and those primitives?

Sometimes that can be a hint that doing the opposite will be difficult or even impossible to do while staying within standard Haskell 2010. As it happens, this is one of those cases...

To define OI, partOI, getchar and putchar will require:

  • modifying your preferred Haskell implementation - lots of work;

For now, I'll just use the extensions - they're ugly, but at least they're contained, just like those alternate definitions of seq. But who knows - if this approach to I/O proves useful enough, it might make its way into a future Haskell standard...that's how IO happened[4].

In the meantime, take a deep breath:

 -- for GHC 8.6.5
{-# LANGUAGE BangPatterns, MagicHash, UnboxedTuples #-}
module OutputInput(OI, runOI, seq, getchar, putchar) where
import Prelude(Char, String)
import Prelude(($), (++), putChar, getChar, error)
import Partible
import Sequential
import GHC.Base(seq#, realWorld#, newMutVar#, atomicModifyMutVar#)

data OI                 =  OI OI#

instance Partible OI where
    part =  partOI

partOI                  :: OI -> (OI, OI)
partOI (OI r)           =  case expire# "partOI" r realWorld# of
                             s -> case newMutVar# () s of
                                    (# s', r1 #) ->
                                      case newMutVar# () s' of
                                        (# _, r2 #) -> (OI r1, OI r2)

runOI g                 =  IO $ \s -> case newMutVar# () s of
                                        (# s', r #) -> seq# (g (OI r)) s'

getchar                 :: OI -> Char
getchar (OI r)          =  case expire# "getchar" r realWorld# of
                             s -> case undo# getChar s of
                                    (# _, c #) -> c

putchar                 :: Char -> OI -> ()
putchar c (OI r)        =  case expire# "putchar" r realWorld# of
                             s -> case undo# (putChar c) s of
                                    (# _, x #) -> x

 -- Local definitions
type OI#                =  MutVar# RealWorld ()

expire#                 :: String -> MutVar# s () -> State# s -> State# s
expire# name r s        =  case atomicModifyMutVar# r flick s of
                             (# s', _ #) -> s'
                               flick      :: () -> (a, ())
                               flick x@() =  (error nowUsed, x)

                               nowUsed    =  name ++ ": argument already used"

undo#                   :: IO a -> State# RealWorld -> (# State# RealWorld, a #)
undo# (IO a)            =  a

Now you can start breathing again :-)

module Partible where

class Partible a where
    part :: a -> (a, a)
    parts :: a -> [a]

     -- Minimal complete definition: part or parts



testme n = n^2 + n^2



This definition:


would likewise be rewritten, with the result being:


but, as noted by Philip Wadler[5]:

becomes invalid.


What - just treat I/O-centric definitions as some special case by modifying GHC? Haskell implementations like GHC are complicated enough as is!

The problem is being caused by the code being treated as though it's pure, so let's modify the code instead. In this case, the easiest solution is to make all calls to I/O-centric definitions unique:

main u = let !(u1, u2) = part u in

But what about:

oops g h u = g u `seq` h u


Will the laugh be on us, again?


In the prototype implementation, the maintenance of this all-important single-use property is performed by expire#.

Now for the much-maligned[7] seq...you could be tempted into avoiding its use by using a new data type:

newtype Result a = Is a

getchar' :: OI -> Result Char

and case-expressions:

respond'                :: Request -> OI -> Response

But before you succumb:

unit_Result             :: a -> Result a
unit_Result             =  Is

bind_Result (Is x) k    =  k x

Oh look - Result is one of those types...

The bang-pattern extension? So you can instead write:

respond''               :: Request -> OI -> Response

As you can see, z isn't used anywhere - there is no need for it. This being Haskell, if it isn't needed, it isn't evaluated - that allows implementations like GHC to rewrite a definition like respond as:

respond''               :: Request -> OI -> Response

You could try all manner of ways to avoid using seq - you might even find one that you like; all well and good...but others might not. For me, the simplest way I've found to make this approach to I/O work is with seq - one that's actually sequential.


If that's you, the corresponding language proposal[8] has a list of other articles and research papers I've found which describe or refer to other approaches - perhaps one (or more) of them will be more acceptable.

As noted by Owen Stephens[9]:

iteratees being a case in point.


P.S: Why the name OI? Many years ago I was tinkering with arrows for performing I/O, labelling them OI a b out of expediency. More recently, I discovered a set of slides[10] describing another approach to I/O which used values of type OI a in a similar fashion to what I've been describing here. I've reused the name because of that similarity.


[1] Sequential ordering of evaluation; Haskell Wiki.


[3] Wearing the hair shirt: a retrospective on Haskell; Simon Peyton Jones.


[5] How to Declare an Imperative; Philip Wadler.

[6] The Clean homepage; Radboud University, Nijmegen, The Netherlands.

[7] Thread: State monads don't respect the monad laws in Haskell; Haskell mail archive.

[8] Partibles for composing monads; Haskell Wiki.

[9] Approaches to Functional I/O; Owen Stephens.

[10] Non-Imperative Functional Programming; Nobuo Yamashita.

Atravers 03:05, 20 August 2020 (UTC)