Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a function in Haskell?

Tags:

haskell

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 }
like image 904
Jonathan Avatar asked Jun 03 '26 22:06

Jonathan


2 Answers

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.

like image 132
leftaroundabout Avatar answered Jun 06 '26 04:06

leftaroundabout


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.

like image 43
Carcigenicate Avatar answered Jun 06 '26 04:06

Carcigenicate



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!