After a few tutorials now creating my first "real" code in Haskell.
I have a system where I will be using a scoring function for pairs of entries which I prefer to represent as Int
's, so the function is essentially just a [[Int]]
until performance demands otherwise. This function is to be build dependent on the numbers in a text file specified as the command line arguments to the program.
All parts of my program worked correctly with an assigned hardcoded [[Int]]
(oops !) and I came to the stage where I had to read up on IO in Haskell and then I realised my blunder(?): The way I have done thi开发者_如何学Gongs I will have to take the [[Int]]
constructed from the input file and pass it around as an extra parameter in the functions needing to use the score of pairs. For now it seems to me that instead of having
ranking :: (Int, Int) -> Int
I will have to do
rankingBasedOnInput :: SomeType -> (Int, Int) -> Int
where SomeType
is the type that will be part of the righthand side of the signature of my function to handle the input file : IO SomeType
.
I am hoping someone could tell what I and my procedural mind are doing wrong and hint on a way of creating a better solution as I am still new to the functional way of thinking.
You probably do not want to know about [[Int]] in you main code but abstract it in the scoring function. Instead of using explicitly the "rankingBasedOnInput" function in the caller you can pass a ranking function ((Int, Int) -> Int).
someFunction :: a -> b
someFunction input = ...
where f x = rankingBasedOnInput input x
used with
someFunction a
becomes :
someFunction :: ((Int,Int) -> Int) -> a -> b
someFunction ranking x
used with :
someFunction ranking a
where ranking = rankingBasedOnInput input
Instead of passing around the input parameters, you pass around the scoring function.
You still have a parameter to pass explicitly. You can however use partial function application to avoid it as much as possible.
So your problem is that the ranking function depends on potentially changing data in a text file?
If ranking is pure function then yes, all the data you need to calculate ranking needs to be in the inputs to the function, so your type signature would need to be something like:
ranking :: SomeType -> (Int,Int) -> Int
This works just fine, with the only problem being that you have to thread the state explicitly through all the functions that use it.
Your other obvious option, if you have state (e.g. the rankings) which crops up in a large number of different places, is to use a State or Reader Monad for the top level functions. In my example below I use the State monad because I don't know if you going to need to modify your shared state a lot or not after first reading it in.
These effectively hide the fact that state is being threaded through everything, and would let you write something like:
-- import state monad
import Control.Monad.State
-- create type synonym to reduce type signature length
type MyMonad a = State SomeType a
-- dependence of ranking on state is via MyMonad rather than explicit input argument
ranking :: (Int,Int) -> MyMonad Int
ranking = do
rankingData <- get
calculate rank somehow with rankingData
return rank
-- define a function that does all the work
doCalculationsWithRanks :: unknown inputs -> MyMonad (unknown return type)
doCalculationsWithRanks = undefined
-- run the program and get the results
main = do
rankingData <- readRankingsFromFile
let myResults = evalState (doCalculationsWithRanks ...) rankingData
do something with the myResults
If you need both IO and state, you can create use a monad transformer to create a monad that lets you access IO but also carries around the state for you.
Also, as ysdx says, you can partially apply ranking to get rid of the extra argument:
ranking' = ranking rankingData
This means that you need to add a function argument to every function that needs to compute a ranking, so in that case you will still need to add extra arguments to some of your functions.
精彩评论