Reputation: 5636
I struggled to think of a good title here, but hopefully my description makes up for it.
As a hobby side-project, I'm attempting to port an interpreter for a toy language (you have to pay for the book, just linking to show where I am coming from) from Go to F#.
This has all gone fine until the point where I am needing to call other functions in a dictionary of functions.
Here's a very simplified example of what the Go code tries to do written in F#:
let processA a remainingCharacters =
1
let processB b remainingCharacters =
2
let processC c remainingCharacters =
// this doesn't work, obviously, as funcMap is declared below
let problem = funcMap.[remainingCharacters.Head]
3 + problem
// i assume there is a better way of doing this, I'm just not sure what it is
let funcMap = dict[('a', processA); ('b', processB); ('c', processC)]
let processCharacter currentCharacter remainingCharacters =
let processFunc = funcMap.[currentCharacter]
processFunc currentCharacter remainingCharacters
let input = ['a'; 'b'; 'a'; 'c']
let processInput() =
let rec processInputRec currentCharacter (remainingCharacters: char list) sum =
if remainingCharacters.IsEmpty then
sum
else
let currentValue = processCharacter currentCharacter remainingCharacters
processInputRec remainingCharacters.Head remainingCharacters.Tail (sum + currentValue)
processInputRec input.Head input.Tail 0
let result = processInput()
sprintf "%i" result |> ignore
So basically, it is trying to map given input values to different functions and in certain cases, needing to refer back to that mapping (or at least getting at another one of those mapped functions) inside those functions.
How would I go about doing that in F#?
Upvotes: 1
Views: 84
Reputation: 80805
The order of compilation in F# is a feature, not a bug. It helps to make sure that your code is not spaghetti, that all dependencies are nice and linear.
The "loopback" scenarios like this one are usually solved via parametrization.
So in this particular case, if you want the specific functions to call processCharacter
recursively, just pass it in:
let processA a remainingCharacters _ = // an extra unused parameter here
1
let processB b remainingCharacters _ = // and here
2
let processC c remainingCharacters procChar = // here is where the extra parameter is used
let problem = procChar (List.head remainingCharacters) (List.tail remainingCharacters)
3 + problem
...
let rec processCharacter currentCharacter remainingCharacters =
let processFunc = funcMap.[currentCharacter]
processFunc currentCharacter remainingCharacters processCharacter
Note, however, that, although this will solve your immediate problem, this will (probably) not work all the way, because you're not keeping track of which characters got consumed from the input. So that, if processC
decides to process one more character, the surrounding code won't know about it, and upon return from processC
will process the same character again. I'm not sure if this was your intent (hard to tell from the code), and if it was, please disregard this warning.
The usual approach to parsing a stream of inputs like this is to have each processing function return a pair - the result of the processing plus the tail of remaining inputs, e.g.:
let processA chars =
1, (List.tail chars)
Then the surrounding "driver" function would thread the returned tail of the list to the next processing function. This way, each processing function can consume not necessarily one, but any number of inputs - from zero to all of them.
This approach has been implemented in libraries, too. Take a look at FParsec.
Another note: your code seems very un-F#-y. You're not using many F# features, making your code longer and more complicated than it needs to be. For example, instead of accessing .Tail
and .Head
, it is customary to pattern-match on the list:
let rec processInputRec current rest sum =
match rest with
| [] -> sum
| (next, rest') ->
let currentValue = processCharacter current rest
processInputRec next rest' (sum + currentValue)
Upvotes: 3