F#中是否有箭头的"proc"表示法的实现?在 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 Arrow 是分开的, 我也使用这些,您为它们创建一个中缀运算符)。无论如何在 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
),以便与Haskell不同,F#通用,需要专用seqFork
,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]
它显然不是Proc符号。现在我有了运行版本,我不知道这是否回答了您的"其他方法"完成点......
最终更新:
有了listFork
,您只需 1 行即可使其更加无点:
let mean1 = listFork total (/) (List.map (fun _ -> 1) >> total)
在OP中链接的第一个示例中不清楚的是,total
本身就是一个箭头,可以进行折叠或扫描的繁重提升。这就是 Arrows 的真正力量,因此它们在 FRP 中很有用,但这超出了我对fork
的使用范围 - 它更侧重于无点、组合和管道。至少到现在为止,我会进一步考虑这个问题,但不是在这里!
您确实可以使用参数化的计算表达式,该表达式在箭头方向上有所作为。让我们首先看一下用于此类问题的基本构建块(同样可用于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]