What does GADT offer that cannot be done with OOP and generics? - haskell

Are GADTs in functional languages equivalent to traditional OOP + generics, or there is a scenario where there are correctness constrants easily enforced by GADT but hard or impossible to achieve using Java or C#?
For example, this "well-typed interpreter" Haskell program:
data Expr a where
N :: Int -> Expr Int
Suc :: Expr Int -> Expr Int
IsZero :: Expr Int -> Expr Bool
Or :: Expr Bool -> Expr Bool -> Expr Bool
eval :: Expr a -> a
eval (N n) = n
eval (Suc e) = 1 + eval e
eval (IsZero e) = 0 == eval e
eval (Or a b) = eval a || eval b
can be written equivalently in Java using generics and appropriate implementation of each subclass, though much more verbose:
interface Expr<T> {
public <T> T eval();
}
class N extends Expr<Integer> {
private Integer n;
public N(Integer m) {
n = m;
}
#Override public Integer eval() {
return n;
}
}
class Suc extends Expr<Integer> {
private Expr<Integer> prev;
public Suc(Expr<Integer> aprev) {
prev = aprev;
}
#Override public Integer eval() {
return 1 + prev.eval()
}
}
/** And so on ... */

OOP classes are open, GADTs are closed (like plain ADTs).
Here, "open" means you can always add more subclasses later, hence the compiler can not assume to have access to all the subclasses of a given class. (There are a few exceptions, e.g. Java's final which however prevents any subclassing, and Scala's sealed classes). Instead, ADTs are "closed" in the sense you can not add further constructors later on, and the compiler knows that (and can exploit it to check e.g. exhaustiveness). For more information, see the "expression problem".
Consider the following code:
data A a where
A1 :: Char -> A Char
A2 :: Int -> A Int
data B b where
B1 :: Char -> B Char
B2 :: String -> B String
foo :: A t -> B t -> Char
foo (A1 x) (B1 y) = max x y
The above code relies on Char being the only type t for which one can produce both A t and B t. GADTs, being closed, can ensure that. If we tried to mimick this using OOP classes we fail:
class A1 extends A<Char> ...
class A2 extends A<Int> ...
class B1 extends B<Char> ...
class B2 extends B<String> ...
<T> Char foo(A<T> a, B<T> b) {
// ??
}
Here I think we can not implement the same thing unless resorting to unsafe type operations like type casts. (Moreover, these in Java don't even consider the parameter T because of type erasure.) We might think of adding some generic method to A or B to allow this, but this would force us to implement said method for Int and/or String as well.
In this specific case, one might simply resort to a non generic function:
Char foo(A<Char> a, B<Char> b) // ...
or, equivalently, to adding a non generic method to those classes.
However, the types shared between A and B might be a larger set than the singleton Char. Worse, classes are open, so the set can get larger as soon as one adds a new subclass.
Also, even if you have a variable of type A<Char> you still do not know if that's a A1 or not, and because of that you can not access A1's fields except by using a type cast. The type cast here would be safe only because the programmer knows there's no other subclass of A<Char>. In the general case, this might be false, e.g.
data A a where
A1 :: Char -> A Char
A2 :: t -> t -> A t
Here A<Char> must be a superclass of both A1 and A2<Char>.
#gsg asks in a comment about equality witnesses. Consider
data Teq a b where
Teq :: Teq t t
foo :: Teq a b -> a -> b
foo Teq x = x
trans :: Teq a b -> Teq b c -> Teq a c
trans Teq Teq = Teq
This can be translated as
interface Teq<A,B> {
public B foo(A x);
public <C> Teq<A,C> trans(Teq<B,C> x);
}
class Teq1<A> implements Teq<A,A> {
public A foo(A x) { return x; }
public <C> Teq<A,C> trans(Teq<A,C> x) { return x; }
}
The code above declares an interface for all the type pairs A,B, which is then implemented only in the case A=B (implements Teq<A,A>) by the class Teq1.
The interface requires a conversion function foo from A to B, and a "transitivity proof" trans, which given this of type Teq<A,B> and
an x of type Teq<B,C> can produce an object Teq<A,C>. This is the Java analogous of the Haskell code using GADTs right above.
The class can not be safely implemented when A/=B, as far as I can see: it would require either returning nulls or cheating with non termination.

Generics do not provide type equality constraints. Without them you need to rely on downcasts, i.e., lose type safety. Moreover, certain dispatch – in particular, the visitor pattern – cannot be implemented safely and with the proper interface for generics resembling GADTs. See this paper, investigating the very question:
Generalized Algebraic Data Types and Object-Oriented Programming
Andrew Kennedy, Claudio Russo. OOPSLA 2005.

Related

How to EmptyCase at the type level

Using EmptyCase, it is possible to implement the following function:
{-# LANGUAGE EmptyCase, EmptyDataDecls #-}
data Void
absurd :: Void -> a
absurd v = case v of
With DataKinds, data types can be promoted to the kind level (their constructors are promoted to type constructors). This works for uninhabited data types like Void as well.
The question here is whether there is a way to write the equivalent of absurd for an uninhabited kind:
tabsurd :: Proxy (_ :: Void) -> a
tabsurd = _
This would effectively be a form of "EmptyCase at the type level". Within reason, feel free to substitute Proxy with some other suitable type (e.g. TypeRep).
NB: I understand that I can just resort to error or similar unsafe techniques here, but I want to see if there's a way to do this that would not work if the type wasn't uninhabited. So for whatever technique we come up with, it should not be possible to use the same technique to inhabit the following function:
data Unit = Unit
notsoabsurd :: Proxy (_ :: Unit) -> a
notsoabsurd = _
The type-level equivalent of pattern-matching is type classes (well, also type families, but they don't apply here, since you want a term-level result).
So you could conceivably make tabsurd a member of a class that has an associated type of kind Void:
class TAbsurd a where
type TAbsurdVoid a :: Void
tabsurd :: a
Here, tabsurd will have type signature tabsurd :: TAbsurd a => a, but if you insist on a Proxy, you can obviously easily convert one to the other:
pabsurd :: TAbsurd a => Proxy a -> a
pabsurd _ = tabsurd
So calling such function or using it in any other way would presumably be impossible, because you can't implement class TAbsurd a for any a, because you can't provide the type TAbsurdVoid a.
According to your requirement, the same approach does work fine for Unit:
data Unit = Unit
class V a where
type VU a :: Unit
uabsurd :: a
instance V Int where
type VU Int = 'Unit
uabsurd = 42
Keep in mind however that in Haskell, any kind (including Void) is potentially inhabited by a non-terminating type family. For example, this works:
type family F a :: x
instance TAbsurd Int where
type TAbsurdVoid Int = F String
tabsurd = 42
However, this limitation is akin to any type (including Void) being inhabited by the value undefined at term level, so that you can actually call absurd like this:
x = absurd undefined
The difference with type level is that you can actually call function tabsurd (given the instance above) and it will return 42:
print (tabsurd :: Int)
This can be fixed by having tabsurd return not a, but a Proxy (TAbsurdVoid a):
class TAbsurd a where
type TAbsurdVoid a :: Void
tabsurd :: Proxy (TAbsurdVoid a)

Typesafe StablePtrs

I spent a lot of time encoding invariants in my data types and now I am working on exposing my library to C via the FFI. Rather than marshal data structures across the language barrier I simply use opaque pointers to allow C to build up an AST and then upon eval Haskell only needs to marshal a string over to C.
Here is some code to be more illuminating.
-- excerpt from Query.hs
data Sz = Selection | Reduction deriving Show
-- Column Datatype
data Column (a :: Sz) where
Column :: String -> Column Selection
BinExpr :: BinOp -> Column a -> Column b -> Column (OpSz a b)
AggExpr :: AggOp -> Column Selection -> Column Reduction
type family OpSz (a :: Sz) (b :: Sz) where
OpSz Selection Selection = Selection
OpSz Selection Reduction = Selection
OpSz Reduction Selection = Selection
OpSz Reduction Reduction = Reduction
data Query (a :: Sz) where
... etc
-- excerpt from Export.hs
foreign export ccall "selection"
select :: StablePtr [Column a] -> StablePtr (Query b) -> IO (StablePtr (Query Selection))
foreign export ccall
add :: StablePtr (Column a) -> StablePtr (Column b) -> IO (StablePtr (Column (OpSz a b)))
foreign export ccall
mul :: StablePtr (Column a) -> StablePtr (Column b) -> IO (StablePtr (Column (OpSz a b)))
foreign export ccall
eval :: StablePtr (Query Selection) -> IO CString
As far as I can tell however, is that this seems to throw type safety out the window. Essentially whatever C hands off to Haskell is going to be assumed to be of that type completely negating the reason I wrote the dsl in Haskell. Is there some way I can get the benefits of using StablePtr's and retain type safe? The last thing I want is to re implement the invariants in C.
The C counterpart to StablePtr a is a typedef for void * -- losing type safety at the FFI boundary.
The problem is that there are infinitely many possibilities for a :: *, hence for StablePtr a. Encoding these types in C, which has a limited type system (no parametric types!) can not be done unless resorting to very unidiomatic C types (see below).
In your specific case, a, b :: Sz so we only have finitely many cases, and some FFI tool could help in encoding those cases. Still, this can cause a combinatorial explosion of cases:
typedef struct HsStablePtr_Selection_ { void *p; } HsStablePtr_Selection;
typedef struct HsStablePtr_Reduction_ { void *p; } HsStablePtr_Reduction;
HsStablePtr_Selection
add_Selection_Selection(HsStablePtr_Selection a, HsStablePtr_Selection b);
HsStablePtr_Selection
add_Selection_Reduction(HsStablePtr_Selection a, HsStablePtr_Reduction b);
HsStablePtr_Selection
add_Reduction_Selection(HsStablePtr_Reduction a, HsStablePtr_Selection b);
HsStablePtr_Reduction
add_Reduction_Reduction(HsStablePtr_Reduction a, HsStablePtr_Reduction b);
In C11 one could reduce this mess using type-generic expressions,
which could add the "right" type casts without combinatorial explosion.
Still, no one wrote a FFI tool exploiting that. For instance:
void *add_void(void *x, void *y);
#define add(x,y) \
_Generic((x) , \
HsStablePtr_Selection: _Generic((y) , \
HsStablePtr_Selection: (HsStablePtr_Selection) add_void(x,y), \
HsStablePtr_Reduction: (HsStablePtr_Selection) add_void(x,y) \
) \
HsStablePtr_Reduction: _Generic((y) , \
HsStablePtr_Selection: (HsStablePtr_Selection) add_void(x,y), \
HsStablePtr_Reduction: (HsStablePtr_Reduction) add_void(x,y) \
) \
)
(The casts above are from pointer to struct, so they don't work and we should use struct literals instead, but let's ignore that.)
In C++ we would have richer types to exploit, but the FFI is meant to use C as a common lingua franca for binding to other languages.
A possible encoding of Haskell (monomorphic!) parametric types could be achieved, theoretically, exploiting the only type constructors c has: pointers, arrays, function pointers, const, volatile, ....
For instance, the stable pointer to type T = Either Char (Int, Bool) could be represented as follows:
typedef struct HsBool_ { void *p } HsBool;
typedef struct HsInt_ { void *p } HsInt;
typedef struct HsChar_ { void *p } HsChar;
typedef struct HsEither_ HsEither; // incomplete type
typedef struct HsPair_ HsPair; // incomplete type
typedef void (**T)(HsEither x1, HsChar x2
void (**)(HsPair x3, HsInt x4, HsBool x5));
Of course, from the C point of view, the type T is a blatant lie!! a value of type T would actually be void * pointing to some Haskell-side representation of type StablePtr T and surely not a pointer-to-pointer to C function! Still, passing T around would preserve type safety.
Note that the above one can only be called as an "encoding" in a very weak sense, namely it is an injective mapping from monomorphic Haskell types to C types, totally disregarding the semantics of C types. This is only done to ensure that, if such stable pointers are passed back to Haskell, there is some type checking at the C side.
I used C incomplete types so that one can never call these functions in C. I used pointers-to-pointers since (IIRC) pointers to functions can not be cast to void * safely.
Note that such a sophisticated encoding could be used in C, but could be hard to integrate with other languages. For instance, Java and Haskell could be made to interact using JNI + FFI, but I'm not sure the JNI part can cope with such a complex encoding. Perhaps, void * is more practical, albeit unsafe.
Safely encoding polymorphic functions, GADTs, type classes ... is left for future work :-P
TL;DR: the FFI could try harder to encode static types to C, but this is tricky and there is no large demand for that at this moment. Maybe in the future this could change.

Type class definition with functions depending on an additional type

Still new to Haskell, I have hit a wall with the following:
I am trying to define some type classes to generalize a bunch of functions that use gaussian elimination to solve linear systems of equations.
Given a linear system
M x = k
the type a of the elements m(i,j) \elem M can be different from the type b of x and k. To be able to solve the system, a should be an instance of Num and b should have multiplication/addition operators with b, like in the following:
class MixedRing b where
(.+.) :: b -> b -> b
(.*.) :: (Num a) => b -> a -> b
(./.) :: (Num a) => b -> a -> b
Now, even in the most trivial implementation of these operators, I'll get Could not deduce a ~ Int. a is a rigid type variable errors (Let's forget about ./. which requires Fractional)
data Wrap = W { get :: Int }
instance MixedRing Wrap where
(.+.) w1 w2 = W $ (get w1) + (get w2)
(.*.) w s = W $ ((get w) * s)
I have read several tutorials on type classes but I can find no pointer to what actually goes wrong.
Let us have a look at the type of the implementation that you would have to provide for (.*.) to make Wrap an instance of MixedRing. Substituting Wrap for b in the type of the method yields
(.*.) :: Num a => Wrap -> a -> Wrap
As Wrap is isomorphic to Int and to not have to think about wrapping and unwrapping with Wrap and get, let us reduce our goal to finding an implementation of
(.*.) :: Num a => Int -> a -> Int
(You see that this doesn't make the challenge any easier or harder, don't you?)
Now, observe that such an implementation will need to be able to operate on all types a that happen to be in the type class Num. (This is what a type variable in such a type denotes: universal quantification.) Note: this is not the same (actually, it's the opposite) of saying that your implementation can itself choose what a to operate on); yet that is what you seem to suggest in your question: that your implementation should be allowed to pick Int as a choice for a.
Now, as you want to implement this particular (.*.) in terms of the (*) for values of type Int, we need something of the form
n .*. s = n * f s
with
f :: Num a => a -> Int
I cannot think of a function that converts from an arbitary Num-type a to Int in a meaningful way. I'd therefore say that there is no meaningful way to make Int (and, hence, Wrap) an instance of MixedRing; that is, not such that the instance behaves as you would probably expect it to do.
How about something like:
class (Num a) => MixedRing a b where
(.+.) :: b -> b -> b
(.*.) :: b -> a -> b
(./.) :: b -> a -> b
You'll need the MultiParamTypeClasses extension.
By the way, it seems to me that the mathematical structure you're trying to model is really module, not a ring. With the type variables given above, one says that b is an a-module.
Your implementation is not polymorphic enough.
The rule is, if you write a in the class definition, you can't use a concrete type in the instance. Because the instance must conform to the class and the class promised to accept any a that is Num.
To put it differently: Exactly the class variable is it that must be instantiated with a concrete type in an instance definition.
Have you tried:
data Wrap a = W { get :: a }
Note that once Wrap a is an instance, you can still use it with functions that accept only Wrap Int.

Can you pattern match constructors on a type class constrained parameter?

See code example below. It won't compile. I had thought that maybe it's because it has to have a single type for the first parameter in the test function. But that doesn't make sense because if I don't pattern match on it so it will compile, I can call it with both MyObj11 5 and MyObj21 5 which are two different types.
So what is it that restricts so you can't pattern match on constructors with a type class constrained parameter? Or is there some mechanism by which you can?
class SomeClass a where toString :: a -> String
instance SomeClass MyType1 where toString v = "MyType1"
instance SomeClass MyType2 where toString v = "MyType2"
data MyType1 = MyObj11 Int | MyObj12 Int Int
data MyType2 = MyObj21 Int | MyObj22 Int Int
test :: SomeClass a => a -> String
test (MyObj11 x) = "11"
test (MyObj12 x y) = "12" -- Error here if remove 3rd line: rigid type bound error
test (MyObj22 x y) = "22" -- Error here about not match MyType1.
what is it that restricts so you can't pattern match on constructors with a type class constrained parameter?
When you pattern match on an explicit constructor, you commit to a specific data type representation. This data type is not shared among all instances of the class, and so it is simply not possible to write a function that works for all instances in this way.
Instead, you need to associate the different behaviors your want with each instance, like so:
class C a where
toString :: a -> String
draw :: a -> String
instance C MyType1 where
toString v = "MyType1"
draw (MyObj11 x) = "11"
draw (MyObj12 x y) = "12"
instance C MyType2 where
toString v = "MyType2"
draw (MyObj22 x y) = "22"
data MyType1 = MyObj11 Int | MyObj12 Int Int
data MyType2 = MyObj21 Int | MyObj22 Int Int
test :: C a => a -> String
test x = draw x
The branches of your original test function are now distributed amongst the instances.
Some alternative tricks involve using class-associated data types (where you prove to the compiler that a data type is shared amongst all instances), or view patterns (which let you generalize pattern matching).
View patterns
We can use view patterns to clean up the connection between pattern matching and type class instances, a little, allowing us to approximate pattern matching across instances by pattern matching on a shared type.
Here's an example, where we write one function, with two cases, that lets us pattern match against anything in the class.
{-# LANGUAGE ViewPatterns #-}
class C a where
view :: a -> View
data View = One Int
| Two Int Int
data MyType1 = MyObj11 Int | MyObj12 Int Int
instance C MyType1 where
view (MyObj11 n) = One n
view (MyObj12 n m) = Two n m
data MyType2 = MyObj21 Int | MyObj22 Int Int
instance C MyType2 where
view (MyObj21 n) = One n
view (MyObj22 n m) = Two n m
test :: C a => a -> String
test (view -> One n) = "One " ++ show n
test (view -> Two n m) = "Two " ++ show n ++ show m
Note how the -> syntax lets us call back to the right view function in each instance, looking up a custom data type encoding per-type, in order to pattern match on it.
The design challenge is to come up with a view type that captures all the behavior variants you're interested in.
In your original question, you wanted every constructor to have a different behavior, so there's actually no reason to use a view type (dispatching directly to that behavior in each instance already works well enough).

Is there a Haskell equivalent of OOP's abstract classes, using algebraic data types or polymorphism?

In Haskell, is it possible to write a function with a signature that can accept two different (although similar) data types, and operate differently depending on what type is passed in?
An example might make my question clearer. If I have a function named myFunction, and two types named MyTypeA and MyTypeB, can I define myFunction so that it can only accept data of type MyTypeA or MyTypeB as its first parameter?
type MyTypeA = (Int, Int, Char, Char)
type MyTypeB = ([Int], [Char])
myFunction :: MyTypeA_or_MyTypeB -> Char
myFunction constrainedToTypeA = something
myFunction constrainedToTypeB = somethingElse
In an OOP language, you could write what I'm trying to achieve like so:
public abstract class ConstrainedType {
}
public class MyTypeA extends ConstrainedType {
...various members...
}
public class MyTypeB extends ConstrainedType {
...various members...
}
...
public Char myFunction(ConstrainedType a) {
if (a TypeOf MyTypeA) {
return doStuffA();
}
else if (a TypeOf MyTypeB) {
return doStuffB();
}
}
I've been reading about algebraic data types and I think I need to define a Haskell type, but I'm not sure how to go about defining it so that it can store one type or another, and also how I use it in my own functions.
Yes, you are correct, you are looking for algebraic data types. There is a great tutorial on them at Learn You a Haskell.
For the record, the concept of an abstract class from OOP actually has three different translations into Haskell, and ADTs are just one. Here is a quick overview of the techniques.
Algebraic Data Types
Algebraic data types encode the pattern of an abstract class whose subclasses are known, and where functions check which particular instance the object is a member of by down-casting.
abstract class IntBox { }
class Empty : IntBox { }
class Full : IntBox {
int inside;
Full(int inside) { this.inside = inside; }
}
int Get(IntBox a) {
if (a is Empty) { return 0; }
if (a is Full) { return ((Full)a).inside; }
error("IntBox not of expected type");
}
Translates into:
data IntBox = Empty | Full Int
get :: IntBox -> Int
get Empty = 0
get (Full x) = x
Record of functions
This style does not allow down-casting, so the Get function above would not be expressible in this style. So here is something completely different.
abstract class Animal {
abstract string CatchPhrase();
virtual void Speak() { print(CatchPhrase()); }
}
class Cat : Animal {
override string CatchPhrase() { return "Meow"; }
}
class Dog : Animal {
override string CatchPhrase() { return "Woof"; }
override void Speak() { print("Rowwrlrw"); }
}
Its translation in Haskell doesn't map types into types. Animal is the only type, and Dog and Cat are squashed away into their constructor functions:
data Animal = Animal {
catchPhrase :: String,
speak :: IO ()
}
protoAnimal :: Animal
protoAnimal = Animal {
speak = putStrLn (catchPhrase protoAnimal)
}
cat :: Animal
cat = protoAnimal { catchPhrase = "Meow" }
dog :: Animal
dog = protoAnimal { catchPhrase = "Woof", speak = putStrLn "Rowwrlrw" }
There are a few different permutations of this basic concept. The invariant is that the abstract type is a record type where the methods are the fields of the record.
EDIT: There is a good discussion in the comments on some of the subtleties of this approach, including a bug in the above code.
Typeclasses
This is my least favorite encoding of OO ideas. It is comfortable to OO programmers because it uses familiar words and maps types to types. But the record of functions approach above tends to be easier to work with when things get complicated.
I'll encode the Animal example again:
class Animal a where
catchPhrase :: a -> String
speak :: a -> IO ()
speak a = putStrLn (catchPhrase a)
data Cat = Cat
instance Animal Cat where
catchPhrase Cat = "Meow"
data Dog = Dog
instance Animal Dog where
catchPhrase Dog = "Woof"
speak Dog = putStrLn "Rowwrlrw"
This looks nice, doesn't it? The difficulty comes when you realize that even though it looks like OO, it doesn't really work like OO. You might want to have a list of Animals, but the best you can do right now is Animal a => [a], a list of homogeneous animals, eg. a list of only Cats or only Dogs. Then you need to make this wrapper type:
{-# LANGUAGE ExistentialQuantification #-}
data AnyAnimal = forall a. Animal a => AnyAnimal a
instance Animal AnyAnimal where
catchPhrase (AnyAnimal a) = catchPhrase a
speak (AnyAnimal a) = speak a
And then [AnyAnimal] is what you want for your list of animals. However, it turns out that AnyAnimal exposes exactly the same information about itself as the Animal record in the second example, we've just gone about it in a roundabout way. Thus why I don't consider typeclasses to be a very good encoding of OO.
And thus concludes this week's edition of Way Too Much Information!
It sounds like you might want to read up on typeclasses.
Consider this example using TypeClasses.
We define a c++-like "abstract class" MVC based on three types (note MultiParamTypeClasses): tState tAction tReaction in order to
define a key function tState -> tAction -> (tState, tReaction) (when an action is applied to the state, you get a new state and a reaction.
The typeclass has
three "c++ abstract" functions, and some more defined on the "abstract" ones. The "abstract" functions will be defined when and instance MVC is needed.
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, NoMonomorphismRestriction #-}
-- -------------------------------------------------------------------------------
class MVC tState tAction tReaction | tState -> tAction tReaction where
changeState :: tState -> tAction -> tState -- get a new state given the current state and an action ("abstract")
whatReaction :: tState -> tReaction -- get the reaction given a new state ("abstract")
view :: (tState, tReaction) -> IO () -- show a state and reaction pair ("abstract")
-- get a new state and a reaction given an state and an action (defined using previous functions)
runModel :: tState -> tAction -> (tState, tReaction)
runModel s a = let
ns = (changeState s a)
r = (whatReaction ns)
in (ns, r)
-- get a new state given the current state and an action, calling 'view' in the middle (defined using previous functions)
run :: tState -> tAction -> IO tState
run s a = do
let (s', r) = runModel s a
view (s', r)
return s'
-- get a new state given the current state and a function 'getAction' that provides actions from "the user" (defined using previous functions)
control :: tState -> IO (Maybe tAction) -> IO tState
control s getAction = do
ma <- getAction
case ma of
Nothing -> return s
Just a -> do
ns <- run s a
control ns getAction
-- -------------------------------------------------------------------------------
-- concrete instance for MVC, where
-- tState=Int tAction=Char ('u' 'd') tReaction=Char ('z' 'p' 'n')
-- Define here the "abstract" functions
instance MVC Int Char Char where
changeState i c
| c == 'u' = i+1 -- up: add 1 to state
| c == 'd' = i-1 -- down: add -1 to state
| otherwise = i -- no change in state
whatReaction i
| i == 0 = 'z' -- reaction is zero if state is 0
| i < 0 = 'n' -- reaction is negative if state < 0
| otherwise = 'p' -- reaction is positive if state > 0
view (s, r) = do
putStrLn $ "view: state=" ++ (show s) ++ " reaction=" ++ (show r) ++ "\n"
--
-- define here the function "asking the user"
getAChar :: IO (Maybe Char) -- return (Just a char) or Nothing when 'x' (exit) is typed
getAChar = do
putStrLn "?"
str <- getLine
putStrLn ""
let c = str !! 0
case c of
'x' -> return Nothing
_ -> return (Just c)
-- --------------------------------------------------------------------------------------------
-- --------------------------------------------------------------------------------------------
-- call 'control' giving the initial state and the "input from the user" function
finalState = control 0 getAChar :: IO Int
--
main = do
s <- finalState
print s

Resources