问题 用f#报废你的锅炉


我用过了 报废你的锅炉 和Haskell编程语言中的Uniplate库,我会发现通过有区别的联合的泛型编程形式非常有用。 f#编程语言中是否有等效的库?


6787
2017-08-29 21:40


起源



答案:


从来没听说过;如果没有内置于语言/编译器的支持,我希望唯一的替代方案是基于反射的版本。 (我不知道怎么做 Uniplate中 实施 - 你呢?)

这是基于原始演示文稿示例的基于反射的版本的代码。我没有深入考虑它的局限性,但这比我想象的要简单得多。

type Company = C of Dept list
and Dept = D of Name * Manager * SubUnit list
and SubUnit = | PU of Employee | DU of Dept
and Employee = E of Person * Salary
and Person = P of Name * Address
and Salary = S of float
and Manager = Employee
and Name = string
and Address = string

let data = C [D("Research",E(P("Fred","123 Rose"),S 10.0),
                  [PU(E(P("Bill","15 Oak"),S 5.0))])]
printfn "%A" data

open Microsoft.FSharp.Reflection 
let everywhere<'a,'b>(f:'a->'a, src:'b) =   // '
    let ft = typeof<'a>             // '
    let rec traverse (o:obj) =
        let ot = o.GetType()
        if ft = ot then
            f (o :?> 'a) |> box    // '
        elif FSharpType.IsUnion(ot) then
            let info,vals = FSharpValue.GetUnionFields(o, ot)
            FSharpValue.MakeUnion(info, vals |> Array.map traverse)
        else 
            o
    traverse src :?> 'b       // '

let incS (S x) = S(x+1.0) 

let newData = everywhere(incS, data)
printfn "%A" newData

everywhere 函数遍历任意DU的整个结构并应用该函数 f 到每个节点类型 f 继续工作,让所有其他节点保持原样。


11
2017-08-29 21:52



这是一个很好的建议,我将不得不考虑这种方法的性能影响。无论如何,它对我的​​特定用例来说可能无关紧要。 - Richard Warburton
re:关于如何实现Uniplate的问题,来源可用 community.haskell.org/~ndm/darcs/uniplate 。 - Richard Warburton
我已经接受了你的回答,因为我认为这是一个很好的方法 - 但我做了一个改变,以使其正常工作:我用'ot.IsSubclassOf(ft)'替换'ft = ot' - 否则它无法匹配当参数类型为f时,即'a'比通过的特定参数更通用。 - Richard Warburton
首先,Uniplate是用魔法实现的。 :]那就是说,Uniplate,SYB和相关的库大部分都是作为反射所做的结构化的显式实现而存在的。 Haskell不需要特殊的支持(实例的自动生成只是一种便利),但我不知道F#中是否存在所使用的技术,无论哪种方式,我都希望在构建的基础上更加明智。现有的反思设施,如你的例子所示。 - C. A. McCann
也可以看看 social.msdn.microsoft.com/Forums/en-US/fsharpgeneral/thread/... - Brian


答案:


从来没听说过;如果没有内置于语言/编译器的支持,我希望唯一的替代方案是基于反射的版本。 (我不知道怎么做 Uniplate中 实施 - 你呢?)

这是基于原始演示文稿示例的基于反射的版本的代码。我没有深入考虑它的局限性,但这比我想象的要简单得多。

type Company = C of Dept list
and Dept = D of Name * Manager * SubUnit list
and SubUnit = | PU of Employee | DU of Dept
and Employee = E of Person * Salary
and Person = P of Name * Address
and Salary = S of float
and Manager = Employee
and Name = string
and Address = string

let data = C [D("Research",E(P("Fred","123 Rose"),S 10.0),
                  [PU(E(P("Bill","15 Oak"),S 5.0))])]
printfn "%A" data

open Microsoft.FSharp.Reflection 
let everywhere<'a,'b>(f:'a->'a, src:'b) =   // '
    let ft = typeof<'a>             // '
    let rec traverse (o:obj) =
        let ot = o.GetType()
        if ft = ot then
            f (o :?> 'a) |> box    // '
        elif FSharpType.IsUnion(ot) then
            let info,vals = FSharpValue.GetUnionFields(o, ot)
            FSharpValue.MakeUnion(info, vals |> Array.map traverse)
        else 
            o
    traverse src :?> 'b       // '

let incS (S x) = S(x+1.0) 

let newData = everywhere(incS, data)
printfn "%A" newData

everywhere 函数遍历任意DU的整个结构并应用该函数 f 到每个节点类型 f 继续工作,让所有其他节点保持原样。


11
2017-08-29 21:52



这是一个很好的建议,我将不得不考虑这种方法的性能影响。无论如何,它对我的​​特定用例来说可能无关紧要。 - Richard Warburton
re:关于如何实现Uniplate的问题,来源可用 community.haskell.org/~ndm/darcs/uniplate 。 - Richard Warburton
我已经接受了你的回答,因为我认为这是一个很好的方法 - 但我做了一个改变,以使其正常工作:我用'ot.IsSubclassOf(ft)'替换'ft = ot' - 否则它无法匹配当参数类型为f时,即'a'比通过的特定参数更通用。 - Richard Warburton
首先,Uniplate是用魔法实现的。 :]那就是说,Uniplate,SYB和相关的库大部分都是作为反射所做的结构化的显式实现而存在的。 Haskell不需要特殊的支持(实例的自动生成只是一种便利),但我不知道F#中是否存在所使用的技术,无论哪种方式,我都希望在构建的基础上更加明智。现有的反思设施,如你的例子所示。 - C. A. McCann
也可以看看 social.msdn.microsoft.com/Forums/en-US/fsharpgeneral/thread/... - Brian