我努力想到这里有一个好头衔,但希望我的描述可以弥补它。
作为一个业余爱好项目,我试图为toy language移植一名翻译(你必须为这本书买单,只是链接以显示我来自哪里)从Go到F#
这一切都很好,直到我需要在函数字典中调用其他函数。
这是一个非常简单的例子,说明Go代码尝试用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
所以基本上,它试图将给定的输入值映射到不同的函数,并且在某些情况下,需要在这些函数中引用回映射(或者至少得到另一个映射函数)。
我如何在F#中做到这一点?
答案 0 :(得分:3)
F#中的编译顺序是一个功能,而不是一个bug。它有助于确保您的代码不是意大利面条,所有依赖关系都是好的和线性的。
像这样的“环回”场景通常通过参数化来解决。
因此,在这种特殊情况下,如果您希望特定函数以递归方式调用processCharacter
,只需将其传入:
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
但请注意,虽然这可以解决您的直接问题,但这可能(可能)不能一直工作,因为您没有跟踪输入消耗了哪些字符。因此,如果processC
决定再处理一个字符,则周围的代码将不知道它,并且从processC
返回时将再次处理相同的字符。我不确定这是否是你的意图(很难从代码中得知),如果是,请忽略这个警告。
解析像这样的输入流的通常方法是让每个处理函数返回一对 - 处理加上剩余输入尾部的结果,例如:
let processA chars =
1, (List.tail chars)
然后,周围的“驱动程序”函数会将列表的返回尾部线程化为下一个处理函数。这样,每个处理函数不一定消耗一个,而是消耗任意数量的输入 - 从零到全部。
这种方法也已在图书馆中实施。看看FParsec。
另一个注意事项:你的代码似乎非常不F#-y。您没有使用许多F#功能,使您的代码更长,更复杂。例如,不是访问.Tail
和.Head
,而是习惯于在列表上进行模式匹配:
let rec processInputRec current rest sum =
match rest with
| [] -> sum
| (next, rest') ->
let currentValue = processCharacter current rest
processInputRec next rest' (sum + currentValue)