如何翻译函数词典

时间:2018-01-16 14:52:33

标签: f# functional-programming

我努力想到这里有一个好头衔,但希望我的描述可以弥补它。

作为一个业余爱好项目,我试图为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#中做到这一点?

1 个答案:

答案 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)