问题描述
在 F# 中是否有 "proc" notation 的 arrows 实现?在 Haskell 中,它看起来像这样:
mean2 :: Fractional a => Circuit a a
mean2 = proc value -> do
t <- total -< value
n <- total -< 1
returnA -< t / n
注意 proc
关键字和 -<
符号。
理想情况下,这可能会以某种方式使用计算表达式,但我也愿意接受其他方法。
解决方法
我还没有在 F# 中看到任何等效的符号。我认为原因是 F# 并没有试图变得纯粹,因此没有 proc
和 -<
正在解决的问题。
等效的 F# 将是:
let mean2 input =
let mutable total = 0.
let mutable count = 0.
seq {
for i in input do
total <- total + i
count <- count + 1.
yield total / count
}
也许我误解了 proc
关键字提供的功能比这个简单的例子多得多。
更新整个答案:
这不是 Proc 等效符号,但您可以用 F# 编写:
let mean =
let t = total << id
let n = total << List.map (constant 1)
listFork t (/) n
我将在下面解释这一点。
我更喜欢 J 的 fork 连词,它激发了下面的 fork
- 但请注意它并不完全相同 - 在 F# 中完成所有工作,据我所知,Arrows 会做,但这是有指导的通过上面提到的 F# 库中的 Arrow 实现,而不是查看 Haskell 的用法(这与 Kliesli Arrows 不同,我也使用它们,您为它们创建了一个中缀运算符)。无论如何在 1 行中:
let fork g h f a = h (g a) (f a)
- 根据需要与
id
、fst
、snd
、flip
、tuple2
、uncurry
等结合使用
无论如何,我会使用 fork
来创建问题中描述的平均值:
let average values = fork List.sum (/) List.length values
或
let average = fork List.sum (/) List.length
运行平均版本,此处使用列表。 (fork
可以提升到不同的集合,这里是 listFork
(如果扩展到列表集合则可以是 List.fork
)所以 F# 与 Haskell 不同,需要专门的 {{1} },seqFork
组合子等)
更新:在风格上尽可能与 Proc 相似:
arrayFork
这显然不是 Proc 符号。现在我有了正在运行的版本,我不知道这是否能回答你的“其他方法”的终点...
最终更新:
使用 let total: int list -> float list = List.scan (+) 0 >>List.tail >> List.map float
let constant i = fun _ -> i
let fork g h f a = h (g a) (f a)
let inline listFork g h f = fork g (List.map2 h) f
let mean =
let t = total << id
let n = total << List.map (constant 1)
listFork t (/) n
mean [0;10;7;8];;
>
val total : (int list -> float list)
val constant : i:'a -> 'b -> 'a
val fork : g:('a -> 'b) -> h:('b -> 'c -> 'd) -> f:('a -> 'c) -> a:'a -> 'd
val inline listFork :
g:('a -> 'b list) ->
h:('b -> 'c -> 'd) -> f:('a -> 'c list) -> ('a -> 'd list)
val mean : (int list -> float list)
val it : float list = [0.0; 5.0; 5.666666667; 6.25]
,您只需使用 1-liner 即可使这更无任何点:
listFork
在 OP 中链接的第一个示例中不清楚的是 let mean1 = listFork total (/) (List.map (fun _ -> 1) >> total)
本身是一个箭头,它可以完成折叠或扫描的繁重工作。这就是 Arrows 的真正力量,因此它们在 FRP 中很有用,但这已经(或曾经)超出了我使用 total
的范围——它更专注于无点、组合和管道。至少到现在为止,我会进一步考虑这一点,但不会在这里!
你确实可以使用一个参数化的计算表达式,它在箭头的方向上有一定的作用。让我们先看看用于此类问题的基本构建块(FRP 同样可用):
-
Seq.scan
用于在将文件夹应用于序列时捕获状态 -
Seq.zip
重新组合两个序列
下面的简单实现将迭代输入序列两次,因此可能需要考虑缓存它。
let inline total source =
Seq.scan (+) LanguagePrimitives.GenericZero source
|> Seq.skip 1
let inline mean source =
source
|> Seq.map (fun _ -> LanguagePrimitives.GenericOne)
|> total
|> Seq.zip (total source)
|> Seq.map ((<||) (/))
通过修改 total
函数以包含映射器,我们可以想出一个构建器,该构建器将输入序列迭代两次并将其重新组合以应用最终的除法运算符。
let inline mapTotal (f : 'a->'b) source : seq<'b> =
Seq.scan (fun s t -> f t + s) LanguagePrimitives.GenericZero source
|> Seq.skip 1
type ZipMap<'a>(xs : seq<'a>) =
member __.Bind(a,f) = f (a xs)
member __.Return(op,x,y) = Seq.zip x y |> Seq.map ((<||) op)
let inline mean2 source =
ZipMap<_>(source){
let! t = mapTotal id
let! n = mapTotal (fun _ -> LanguagePrimitives.GenericOne)
return (/),t,n }
mean2 [0.; 10.; 7.; 8.] // seq [0.0; 5.0; 5.666666667; 6.25]
mean2 [0; 10; 7; 8] // seq [0; 5; 5; 6]