在F#中有一种方法可以用Clojure方式提取记录成员的值吗?

时间:2014-10-25 12:09:18

标签: clojure f#

在Clojure中我会写上面的代码:

user=> (def points [{:x 11 :y 12} {:x 21 :y 22}])
#'user/points
user=> (map :x r)
(11 21)

我可以这样做,因为:x可以用作函数。这是一个非常有用的功能

F#中的相同代码如下所示:

> type Point = {x : int; y: int};;
> let points = [{x=11;y=12}; {x=21; y=22}];;
> List.map (fun p -> p.x) points
val it : int list = [11; 21]

因为我讨厌写匿名函数,所以我发现自己写的是Point类型:

type Point =                 
    {                        
        x: int               
        y : int              
    }                        

    static member getX p = p.x;;

......这让我有可能做到:

> List.map Point.getX points

这仍然很混乱,因为我需要为我使用的每个记录成员编写一个getter。

相反,我想要的是这样的语法:

> List.map Point.x points

有没有办法在不必编写凌乱的匿名函数(fun p - > p.x)或静态getter的情况下执行此操作?

更新:

顺便说一句,Haskell也和Clojure一样(实际上是另一种方式):

Prelude> data Point = Point { x :: Int, y :: Int}
Prelude> let p = Point { x = 11, y=22}
Prelude> x p
11

更新2:

希望在没有帮助的情况下类型推断不起作用时,反对lambda的一个更明显的原因就是一个例子:

type Point2D = { x : int; y : int}
type Point3D = { x : int; y : int; z : int}

let get2dXes = List.map (fun (p:Point2D) -> p.x) 
let get2dXes' : Point2D list -> int list = List.map (fun p -> p.x)
let get2dXes'' (ps : Point2D list) = List.map (fun p -> p.x) ps

......这样的方式不像以下那样优雅:

let get2dXes = List.map Point2D.x

我不想开始关于哪种语法更好的火焰战争。我真诚地希望有一些优雅的方式来完成上述工作,因为我自己找不到任何东西。

显然,我能做的就是向F#的强大神灵祈祷,在未来的版本中包含类似这样的特征,在类型类旁边;)

更新3:

此功能已针对未来的语言版本提出。 https://fslang.uservoice.com/forums/245727-f-language/suggestions/5663326-syntax-for-turning-properties-into-functions谢谢JackP。!

3 个答案:

答案 0 :(得分:4)

我的推荐是这样的:

namespace Temp

    type Point = { x:int; y:int }

    [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
    module Point =
        let x: Point -> int = fun p -> p.x
        let y: Point -> int = fun p -> p.y


    type Circle =  { r: int; x: int; y: int }

    [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
    module Circle =
        let x: Circle -> int = fun c -> c.x
        let y: Circle -> int = fun c -> c.y
        let r: Circle -> int = fun c -> c.r


    module test =
        let p1 : Point = { x = 1; y = 2}

        Point.y p1 |> printf "%i"

如果您可以保证不重复使用记录字段名称,那么您可以使用AutoOpen这样的属性:

namespace Temp

    type Point = { x:int; y:int }

    [<AutoOpen>]
    module PointExt =
        let x: Point -> int = fun p -> p.x
        let y: Point -> int = fun p -> p.y


    module test =
        let points = [ { x = 1; y = 2}; { x=5;y=10} ]

        points |> List.map x |> List.iter (printf "%i")

但这种编码风格对我来说并不那么明显。我更喜欢保留模块名称。

答案 1 :(得分:1)

您通常会使用匿名函数来执行此操作,这被认为是惯用的。为记录字段定义静态“getter”似乎是多余的,并且会添加许多样板。

“乱七八糟”的匿名函数只是您的个人意见,您应该克服它。虽然我不得不承认我认为Clojure的语法是使用你自己的术语“混乱”,所以我可以看到你来自哪里;)

答案 2 :(得分:1)

这看起来怎么样?

type Point = { x: int, y : int }  

let l = [{x=1;y=2};{x=3;y=4};{x=5;y=6}]

l |> List.map(function | x->x.x)      // ie. based on a pattern match (see below)

虽然不多。

l |> List.map(fun x->x.x)

List.map(fun x->x.x) l

只是尝试匹配 List.map Point.x points

的目标

我倾向于考虑&#34;功能&#34;作为pattern matching的捷径。

Rich Hickey使用短语&#34;现在它已经归结为熟悉&#34;在他的一次会谈中。一旦你习惯了这些例子,这些例子看起来很优雅。

功能语言都是关于&#34;函数是值&#34;,或者函数应该像处理值一样容易处理和传递。 Anon起作用摇滚,但是如果你接受那个&#34;功能|&#34;是匹配表达式的代理,那么这可能是您问题的有效答案。


更新

目前没有办法以Clojure的方式做到这一点。这不是一个糟糕的建议。对我来说,更好的胜利就是制作&#34; List。&#34;部分可选。我们可以推断出我们正在处理一个列表。

// helpers
let map = Map.map
let map0 = List.map
let map1 = Seq.map
let map2 = Array.map

多态性在这里有所帮助,但是没有 - 类型推断的价格?

btw:在&#34;属性功能&#34;上,你可以分叉F#编译器并在你有时间/如此倾向时发出拉取请求。现在F#是开源的,这取决于我们。如果我们在回复中遗漏了某些内容,请务必将您的想法置于用户的声音中。