Make variable from input accessible from the whole code - haskell

Is this even possible? I have a main that saves an Int (width) given by the user as a variable, but I need that variable in a bunch of other functions... Is there a way to do this other than adding 'width' as an argument to every function?

I'll show a simple example of reader monad usage.
This code:
area :: Double -> Double -> Double
area height width = height * width
main = do
width <- fmap read getLine
let result = area 42 width
print result
Becomes:
import Control.Monad.Reader
area :: MonadReader Double m => Double -> m Double
area height = do
width <- ask
return (width * height)
main :: IO ()
main = do
width <- fmap read getLine
let result = runReader (area 42) width
print result
This seems more complicated, but it's actually quite nice when you have a lot of "configuration" parameters to pass around.
import Control.Monad.Reader
data Config = Config { width :: Double, color :: String, etc :: String }
area :: MonadReader Config m => Double -> m Double
area height = do
w <- asks width
return (w * height)
main :: IO ()
main = do
w <- fmap read getLine
c <- undefined -- todo get color param
e <- undefined -- todo get etc param
let result = runReader (area 42) (Config w c e)
print result

Related

RGB Terminal Colors with Haskell and Brick

I know that the Brick and the VTY hackage do not support escape sequences. VTY only supports 240 colors.
Is there any workaround to use true RGB colors and not mess up the layout?
This is an example I made, but I can't get the border right:
module BrickTest where
import Brick (simpleMain, Widget, str)
import Brick.Widgets.Border (border)
import Text.Printf (printf)
main :: IO ()
main = simpleMain $ colorWidget (255, 0, 0)
type RGB = (Int, Int, Int)
colorWidget :: RGB -> Widget ()
colorWidget (r, g, b) = border $ str (prefix ++ "a" ++ postfix)
where
prefix = printf "\ESC[38;2;%d;%d;%dm" r g b
postfix = "\ESC[0m"
output:
┌──────────────────┐
│a│
└──────────────────┘
I found a workaround. I managed to implement a function zeroWidthStr that can print any string, and Brick handles it as if it has width 0. But I can't really explain how this is working internally, and it might have some other side effects.
module BrickTest where
import Brick (Widget, raw, simpleMain, str,
(<+>))
import Brick.Widgets.Border (border)
import Data.List (intercalate)
import Data.Text.Lazy (pack)
import Graphics.Vty (defAttr)
import Graphics.Vty.Image.Internal (Image (HorizText))
import Text.Printf (printf)
main :: IO ()
main = simpleMain $ colorWidget (255, 0, 0)
type RGB = (Int, Int, Int)
colorWidget :: RGB -> Widget ()
colorWidget (r, g, b) = border $ prefix <+> str "a" <+> postfix
where
prefix = zeroWidthStr $ printf "\ESC[38;2;%d;%d;%dm" r g b
postfix = zeroWidthStr $ "\ESC[0m"
zeroWidthStr :: String -> Widget ()
-- | workaround to print any string in terminal, and hackage Brick (vty) handles it as if it has width 0
zeroWidthStr str = raw image
where
image = HorizText defAttr (pack modStr) 0 0
modStr = str ++ repeatN "\ESC\ESCa" (length str)
repeatN :: String -> Int -> String
repeatN str n = intercalate "" $ take n $ repeat str
output:

Generate a random list of custom data type, where values provided to data constructor are somehow bounded within a range

I have defined a Point data type, with a single value constructor like so:
data Point = Point {
x :: Int,
y :: Int,
color :: Color
} deriving (Show, Eq)
data Color = None
| Black
| Red
| Green
| Blue
deriving (Show, Eq, Enum, Bounded)
I have found an example of making a Bounded Enum an instance of the Random class and have made Color an instance of it like so:
instance Random Color where
random g = case randomR (1, 4) g of
(r, g') -> (toEnum r, g')
randomR (a, b) g = case randomR (fromEnum a, fromEnum b) g of
(r, g') -> (toEnum r, g')
I was then able to find out how to make Point an instance of the Random class also:
instance Random Point where
randomR (Point xl yl cl, Point xr yr cr) g =
let (x, g1) = randomR (xl, xr) g
(y, g2) = randomR (yl, yr) g1
(c, g3) = randomR (cl, cr) g2
in (Point x y c, g3)
random g =
let (x, g1) = random g
(y, g2) = random g1
(c, g3) = random g2
in (Point x y c, g3)
So, this let's me make random point values. But, what I'd like to do is be able to create a list of random Point values, where the x and the y properties are bounded within some range whilst leaving the color property to be an unbounded random value. Is this possible with the way I am currently modelling the code, or do I need to rethink how I construct Point values? For instance, instead of making Point an instance of the Random class, should I just create a random list of Int in the IO monad and then have a pure function that creates n Points, using values from the random list to construct each Point value?
Edit, I think I have found out how to do it:
Without changing the above code, in the IO monad I can do the following:
solved :: IO ()
solved = do
randGen <- getStdGen
let x = 2
let randomPoints = take x $ randomRs (Point 0 0 None, Point 200 200 Blue) randGen
putStrLn $ "Random points: " ++ show randomPoints
This seems to work, randomRs appears to let me specify a range...
Presumably because my Point data type derives Eq?
Or
Is it because my x and y properties are Int (guessing here, but may be "bounded" by default) and I have Color derive bounded?
It works because of the properties of the Int and Color types, not because of the properties of Point. If one suppresses the Eq clause of Point, it still works.
Your code is overall quite good, however I would mention a few minor caveats.
In the Random instance for Point, you are chaining the generator states manually; this is a bit error prone, and monadic do notation is supposed to make it unnecessary. The Color instance could be simplified.
You are using IO where it is not really required. IO is just one instance of the MonadRandom class. If g is an instance of RandomGen, any Rand g is an instance of MonadRandom.
The random values you're getting are not reproducible from a program execution to the next one; this is because getStdGen implicitly uses the launch time as a random number generation seed. It may do that because it is IO-hosted. In many situations, this is a problem, as one might want to vary the choice of random sequence and the system parameters independently of each other.
Using monadic style, the basics of your code could be rewritten for example like this:
import System.Random
import System.Random.TF -- Threefish random number generator
import Control.Monad.Random
data Point = Point {
x :: Int,
y :: Int,
color :: Color
} deriving (Show, Eq)
data Color = None
| Black
| Red
| Green
| Blue
deriving (Show, Eq, Enum, Bounded)
instance Random Color where
randomR (a, b) g = let (r,g') = randomR (fromEnum a, fromEnum b) g
in (toEnum r, g')
random g = randomR (minBound::Color, maxBound::Color) g
singleRandomPoint :: -- monadic action for just one random point
MonadRandom mr => Int -> Int -> Color -> Int -> Int -> Color -> mr Point
singleRandomPoint xmin ymin cmin xmax ymax cmax =
do
-- avoid manual chaining of generator states:
x <- getRandomR (xmin, xmax)
y <- getRandomR (ymin, ymax)
c <- getRandomR (cmin, cmax)
return (Point x y c)
And then we can derive an expression returning an unlimited list of random points:
-- monadic action for an unlimited list of random points:
seqRandomPoints :: MonadRandom mr =>
Int -> Int -> Color -> Int -> Int -> Color -> mr [Point]
seqRandomPoints xmin ymin cmin xmax ymax cmax =
sequence (repeat (singleRandomPoint xmin ymin cmin xmax ymax cmax))
-- returns an unlimited list of random points:
randomPoints :: Int -> Int -> Int -> Color -> Int -> Int -> Color -> [Point]
randomPoints seed xmin ymin cmin xmax ymax cmax =
let
-- get random number generator:
-- using Threefish algorithm (TF) for better statistical properties
randGen = mkTFGen seed
action = seqRandomPoints xmin ymin cmin xmax ymax cmax
in
evalRand action randGen
Finally we can print the first few random points on stdout:
-- Small printing utility:
printListAsLines :: Show t => [t] -> IO()
printListAsLines xs = mapM_ (putStrLn . show) xs
solved01 :: IO ()
solved01 = do
let
seed = 42 -- for random number generator setup
-- unlimited list of random points:
allRandomPoints = randomPoints seed 0 0 None 200 200 Blue
count = 5
someRandomPoints = take count allRandomPoints
-- IO not used at all so far
putStrLn $ "Random points: "
printListAsLines someRandomPoints
main = solved01
Program execution (reproducible with constant seed):
$ randomPoints
Random points:
Point {x = 187, y = 56, color = Green}
Point {x = 131, y = 28, color = Black}
Point {x = 89, y = 135, color = Blue}
Point {x = 183, y = 190, color = Red}
Point {x = 27, y = 161, color = Green}
$
Should you prefer to just get a finite number of points and also get back the updated state of your random number generator, you would have to use replicate n instead of repeat, and runRand instead of evalRand.
Bit more details about the monadic approach here.

How can you use the result of a function as a variable in another function in Haskell?

I'm trying to learn how to use Haskell and now I have to make a program that takes a integer n and a string k and every letter of that string will be moved n places to the right in the alphabet. At this moment I've got the next code:
import Data.Char
main = do
x <- read getLine :: Int
y <- getLine
caesar x y
result :: String
rotate :: Int -> Char -> [Char]
rotate a b = [chr ((a + ord b) `mod` ord 'z' + ord 'a')]
caesar :: Int -> String -> ()
caesar moving text= do
rotatespecific moving text 0
putStrLn result
rotatespecific :: Int -> String -> Int -> ()
rotatespecific moving text place = do
if place < length text
then
result ++ rotate (moving (text !! place))
rotatespecific (moving text (place + 1))
else
if place == length text
then
result ++ rotate (moving (text !! place))
But I can't compile it because it still gives me the same error message:
parse error (possibly incorrect indentation or mismatched brackets)
|
28 | result ++ rotate (moving (text !! place))
| ^
But I can't see what's wrong with my syntax. I first thought it had something to do with using a Char as parameter for my function but I was wrong because text !! place should give a char and not a [char]. So what's wrong with what I'm doing?
After some edit I got this, but it still doesn't work:
import Data.Char
main = do
xr <- getLine
let x = read xr :: Int
y <- getLine
putStrLn (rotatespecific (x y 0))
rotate :: Int -> Char -> [Char]
rotate a b = [chr ((a + ord b) `mod` ord 'z' + ord 'a')]
rotatespecific :: Int -> String -> Int -> String
rotatespecific moving text place = do
if place < length text
then do
help <- text !! place
h <- rotate (moving help)
a <- rotatespecific (moving text (place + 1))
b <- h ++ a
return b
else
if place == length text
then do
return rotate (moving (text !! place))
else
return ()
The immediate problem is that every if must have an else. You got a parse error at the end because the parser is expecting more, namely an else for that if place == length text.
When you fix this you will have more problems, because you are treating Haskell like an imperative language, and that's not how she likes to be treated. It seems like you think
result ++ newstuff
will mutate result, adding newstuff to the end of it. But Haskell doesn't mutate. Instead, this expression result ++ newstuff is the list that results when you concatenate result and newstuff, but result itself remains unchanged.
ghci> let result = [1,2,3]
ghci> result ++ [4,5,6]
[1,2,3,4,5,6]
ghci> result
[1,2,3]
rotatespecific must return the rotated string, rather than trying to mutate it into existence. The only way functions may communicate is by returning results computed from their arguments -– they may not manipulate any "global" state like result. A function that returns () is guaranteed to be useless.
rotatespecific :: Int -> String -> Int -> String
Delete the result "global variable" (which does not mean what you think it means) and focus on defining rotatespecific in a way that it returns the rotated string.
I would also recommend commenting out main and caesar for now until you have rotatespecific compiling and working when you test it in ghci.
I feel like this is an appropriate time to just show an example, because there are a lot of little problems. I'm not going to fix logic bugs, but I've fixed your syntax. Hopefully this gets you unstuck.
rotatespecific :: Int -> String -> Int -> String
rotatespecific moving text place =
if place < length text then
-- use let .. in instead of do/bind (<-) in pure functions.
let help = text !! place
-- multiple arguments are given after the function, no parentheses
h = rotate moving help
-- use parentheses around an argument if it is a complex expression
-- (anything more than a variable name)
a = rotatespecific moving text (place+1)
b = h ++ a
in b
else
if place == length text then
rotate moving (text !! place)
else
undefined -- you must decide what String to return in this case.
After you have this function working as intended, and only then, open this sealed envelope. ♥️
rotatespecific :: Int -> String -> String
rotatespecific moving text = concatMap (rotate moving) text

Simple Graph Generator [Haste]

Could someone please help me with this? I'm kinda stuck and don't know why i get this error message :
not in scope type constructor or class 'Point'
--import Haste hiding (eval)
--import Haste.Graphics.Canvas
import Data.Maybe
import Expr
-- calculates all points of the graph in pixels
points :: Expr -> Double -> (Int,Int) -> [Point]
points exp sca (w,h) = [(x,realToPix(eval exp(pixToReal x))) | x<- [0..w]]
where
pixToReal :: Int -> Double
pixToReal x = sca*((fromIntegral x)-(fromIntegral w)/2)
realToPix :: Double -> Int
realToPix x = round ((x/sca) + ((fromIntegral w)/2))
-- calculates the lines that are going to be drawn between the points
linez :: Expr -> Double -> (Int,Int) -> [(Point,Point)]
linez exp sca (w,h) = zip (points exp sca (w,h)) (drop 1 (points exp sca (w,h)))
-- width and height of the window
sizeX, sizeY :: Int
sizeX = 300
sizeY = 300
--main :: IO ()
--main = do
--Just can <- getCanvasById "canvas"
--Just canElem <- elemById "canvas"
--Just func <- elemById "formula"
--Just d <- elemById "draw"
--onEvent d OnClick $ \_ (x,y) -> do
--f <- getProp func "value"
--w <- getProp canElem "width"
--h <- getProp canElem "height"
--render can (stroke (path (points (fromJust (readExpr f)) 0.04 (read w,read h))))
--return()
You have to import "Haste.Graphics.Canvas" which defines type alias for "Point".

check if element exists on the sub-list

I can have many Figures on my list. Each Figure can have many Rectangles on its list. I have a problem with my function checkNewRectangleId - this function should ask user about new rectangle id until he write really new id and then it should return this id - but I have an error: couldn't match expected type IO t against inferred type Maybe figureType line (Figure id width height rectangles) <- findFigure idFigure x in my function - could you help ?
import IO
import Char
import System.Exit
import Maybe
import Data.Time.Calendar
import System.Time
checkNewRectangleId :: Int -> [FigureType] -> IO Int
checkNewRectangleId idFigure x = do
idRectangle <- getInt "Give me new rectangle id: "
(Figure id width height rectangles) <- findFigure idFigure x
if isJust (findRectangle idRectangle rectangles) then do
putStrLn ("We have yet rectangle with id " ++ show idRectangle)
checkNewRectangleId idFigure x
else return idRectangle
data FigureType = Figure Int Int Int [RectangleType] deriving(Show, Read)
data RectangleType = Rectangle Int CalendarTime deriving(Show, Read)
findFigure :: Int -> [FigureType] -> Maybe FigureType
findFigure _ [] = Nothing
findFigure n ((Figure id width height rectangles) : xs) =
if n == id then Just (Figure id width height rectangles)
else findFigure n xs
findRectangle :: Int -> [RectangleType] -> Maybe RectangleType
findRectangle _ [] = Nothing
findRectangle n ((Rectangle id date) : xs) =
if n == id then Just (Rectangle id date)
else findRectangle n xs
isInt i = not (null i) && all isDigit i
getInt :: String -> IO Int
getInt q = do
putStr q;
i <- getLine
if isInt i == False then do
putStrLn "Bad number"
getInt q
else return (read i)
Since you say idFigure is guaranteed to exist, you can use fromJust in the Data.Maybe module to convert a Maybe FigureType into a FigureType:
let (Figure id width height rectangles) = fromJust $ findFigure idFigure x
findFigure operates in the Maybe monad, but checkNewRectangleId operates in the IO monad. Haskell will not automatically translate failures (or successes) in one monad into failures (or successes) in another, because the types don't match. So, you have to ask yourself the question, what do you want to happen if findFigure fails to find anything?

Resources