Ok, so this is really dumb, but I don't understand how to write functions in Haskell that are outside of the main do area. I have this script that reads a file, finds the locations of a word in that file, and then bins the results into bins of 10:
{-# LANGUAGE OverloadedStrings #-}
import Data.Text as T
import Data.Text.Internal.Search
import qualified Data.Vector as V
import qualified Data.Text.IO as TIO
import Statistics.Sample.Histogram
main :: IO ()
main = do
rawText <- TIO.readFile "jane-eyre.asciidoc"
let locations = indices "morning" rawText
vec = V.fromList $ Prelude.map fromIntegral locations
(_, sizes) = histogram 10 vec
histSizes = V.toList sizes
print histSizes
This works fine. But now when I try to abstract some things into functions I can't. I try to put the first rawText into its own function, for instance:
{-# LANGUAGE OverloadedStrings #-}
import Data.Text as T
import Data.Text.Internal.Search
import qualified Data.Vector as V
import qualified Data.Text.IO as TIO
import Statistics.Sample.Histogram
rawText = TIO.readFile "jane-eyre.asciidoc"
main :: IO ()
main = do
let locations = indices "morning" rawText
vec = V.fromList $ Prelude.map fromIntegral locations
(_, sizes) = histogram 10 vec
histSizes = V.toList sizes
print histSizes
And I get:
• Couldn't match expected type ‘Text’ with actual type ‘IO Text’
• In the second argument of ‘indices’, namely ‘rawText’
Here's another example of something I tried:
{-# LANGUAGE OverloadedStrings #-}
import Data.Text as T
import Data.Text.Internal.Search
import qualified Data.Vector as V
import qualified Data.Text.IO as TIO
import Statistics.Sample.Histogram
hist text = V.toList sizes where
locations = indices "morning" text
vec = V.fromList $ Prelude.map fromIntegral locations
(_, sizes) = histogram 10 vec
main :: IO ()
main = do
rawText <- TIO.readFile "jane-eyre.asciidoc"
histSizes <- hist rawText
print histSizes
But I get:
• Couldn't match type ‘[]’ with ‘IO’
Expected type: IO Integer
Actual type: [Integer]
• In a stmt of a 'do' block: histSizes <- hist rawText
In the expression:
do { rawText <- TIO.readFile "jane-eyre.asciidoc";
histSizes <- hist rawText;
print histSizes }
In an equation for ‘main’:
main
= do { rawText <- TIO.readFile "jane-eyre.asciidoc";
histSizes <- hist rawText;
print histSizes }
To answer the title question “how to write a function”: well – you just put it in your file.
...but that's clearly not what you're trying to ask here. Thing is, rawText is not at all a function. In your working program, it's just a text value. And, as it depends on external data, it's not really a constant, it's actually a variable. Global variables are a bad idea (in all programming languages), so I do not see why you want to put that in global scope.
What you could do, however, is put the action that's used to retrieve the text in the module scope:
fetchRawText :: IO Text
fetchRawText = TIO.readFile "jane-eyre.asciidoc"
main :: IO ()
main = do
rawText <- fetchRawText
let locations = indices "morning" rawText
vec = V.fromList $ Prelude.map fromIntegral locations
(_, sizes) = histogram 10 vec
histSizes = V.toList sizes
print histSizes
fetchRawText is now what other languages might call a “function with no arguments”, like
def fetchRawText():
return open("jane-eyre.asciidoc").read()
...except Haskell doesn't have functions without arguments (those are just values), it instead expresses the fact that there's a side-effect involved in fetching the value by having it in the IO monad.
A probably more sensible factoring of the program would however be this:
locationHistogramSizes :: Text -> [Double]
locationHistogramSizes rawText = V.toList sizes
where locations = indices "morning" rawText
vec = V.fromList $ Prelude.map fromIntegral locations
(_, sizes) = histogram 10 vec
main :: IO ()
main = do
rawText <- TIO.readFile "jane-eyre.asciidoc"
print $ locationHistogramSizes rawText
Here, I've completely seperated all of the interesting logic from the IO part. That, amongst other advantages, makes unit testing much easier.
Even though you've pulled it out of the main, rawText is still an impure value, yet you're trying to use it in a pure context. You'll still need to get it out of the Monad before you can use it:
main = do
raw <- rawValue
let locations = indices "morning" raw
This may have been more obvious if you had tried giving it a signature (giving things explicit signatures is almost always a good idea). Had you written:
rawText :: Text
rawText = TIO.readFile "jane-eyre.asciidoc"
It would have told you that the signature was incorrect, which would have narrowed the problem down.
Because the code is so simple, taking it out doesn't lead to much in the way of gains. You'd be better off taking what all you have in the main right now, and making all of that its own function; if it makes conceptual sense.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With