在f#

时间:2016-10-18 15:23:11

标签: f#

我有以下问题 - 一些函数和两种类型,其中每个函数至少需要2个参数(每个定义类型中的一个)。
现在这些类型是参数化的,只有在它们的泛型类型符合的情况下才能与这些函数一起使用。

type Record<'a, 'b> = { first: 'a; second: 'b }
type Lens<'a, 'b> = Lens of 'a * 'b

let someFn lens rec : Record<'a, 'b> = ....
let anotherFn lens rec : Record<'a, 'b> = ...
let aThirdFn lens rec : Record<'a, 'b> = ...
//and potentially a dozen functions more that might or might not return a record

使用场景就是这样的

let workflow1 () = 
    let intIntLens = Lens (1, 1)
    let intIntRec = { first = 10; second = 100}

    intIntRec
    |> someFn intIntLens
    |> anotherFn intIntLens
    |> aThirdFn intIntLens

let workflow2 () = 
    let strIntLens = Lens ("foo", 1)
    let strIntRec = { first = "bar"; second = 100}

    strIntRec
    |> someFn strIntLens
    |> someFn strIntLens
    |> aThirdFn strIntLens
    |> anotherFn strIntLens
    |> someFn strIntLens

现在,对于任何给定的工作流程,镜头类型保持不变,但记录正以某种方式处理。 但是我必须将镜头插入每个功能中,这些功能在某种程度上是无聊的。 一个明显的选择是拥有一个可变的模块级变量。我认为,面对并发代码,我认为它不会长时间工作。

那么摆脱镜片参数的最佳方法是什么?

1 个答案:

答案 0 :(得分:2)

如果你的函数总是采用并返回相同的类型(意味着它们本身都是相同的类型),你可以只列出它们并按顺序应用:

let ap lens fns rec = Seq.fold (fun r f -> f lens r) rec fns

let workflow2 () = 
    let strIntLens = Lens ("foo", 1)
    let strIntRec = { first = "bar"; second = 100}

    ap strIntLens [someFn; someFn; aThirdFn; anotherFn; someFn] strIntRec

我无法看到这些功能可能是不同类型的,但也许这只是一个玩具示例,而您真正的问题确实涉及不同类型的功能。
如果是这种情况,这里的解决方案稍微不那么优雅:在本地定义一个管道以关闭其中的镜头。

let workflow2 () = 
    let strIntLens = Lens ("foo", 1)
    let strIntRec = { first = "bar"; second = 100}
    let (|*>) r f = f strIntLens r

    strIntLens 
    |*> someFn
    |*> someFn
    |*> aThirdFn
    |*> anotherFn
    |*> someFn

(请注意,我没有重复使用标准管道名称|>。虽然技术上可行,但这会使代码的可读性降低)

最后,您可以全力解决问题,并将输入与镜头组合在一个数据结构中,然后创建一个自定义管道来处理这种结构,这将应用该功能,但隧道镜头:

type RecAndLens<'a, 'b> = { rec: Record<'a, 'b>; lens: Lens<'a, 'b> }
let (|*>) rl f = { rec = f rl.lens rl.rec; lens = rl.lens }

let workflow2 () = 
    ...    
    { rec = strIntRec; lens = strIntLens }
    |*> someFn 
    |*> someFn 
    |*> aThirdFn 
    |*> anotherFn
    |*> someFn

这种最终方法将是一个长期的&#34;如果你正在建立一个高度可重复使用的图书馆或其他什么的基础。

从技术上讲,对于快速而肮脏的概念证明,您可以使用元组而不是RecAndLens记录:

let (|*>) (rec,lens) f = f lens rec, lens

let workflow2 () = 
    ...
    (strIntRec, strIntLens)
    |*> someFn 
    |*> someFn 
    |*> aThirdFn 
    |*> anotherFn
    |*> someFn

但这听起来不那么容易,更容易出错(闻起来有点原始的痴迷)。