简体   繁体   中英

Is there a way in F# to extract the value of a record member in a Clojure fashion?

In Clojure I would write the above code:

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

I can do this because :x can be used as a function. This is a very useful feature

The same code in F# would look like this:

> 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]

Because I hate writing the anonymous function all the time I find myself writing the type Point like this:

type Point =                 
    {                        
        x: int               
        y : int              
    }                        

    static member getX p = p.x;;

...which gives me the possibility of doing:

> List.map Point.getX points

This is still messy because I need to write a getter for each record member that I use.

Instead what I would like is a syntax like this:

> List.map Point.x points

Is there a way to do this without having to write the messy anonymous function (fun p -> px) or the static getter?

UPDATE:

By the way, Haskell also does it the same as Clojure (actually is the other way around):

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

UPDATE 2:

Hopefully a more obvious reason against the lambda is an example when the type inference doesn't work without help:

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

...which are way less elegant than something like:

let get2dXes = List.map Point2D.x

I don't want to start flame wars about which syntax is better. I was just sincerely hoping that there is some elegant way to do the above since I haven't found any myself.

Apparently all I can do is pray to the mighty gods of F# to include a feature like this in a future version, next to the type classes ;)

UPDATE 3:

This feature is already proposed for a future language version. https://fslang.uservoice.com/forums/245727-f-language/suggestions/5663326-syntax-for-turning-properties-into-functions Thanks JackP.!

My recommendation would be something like this:

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"

If you could guarantee no reuse of record field names, then you could use the AutoOpen attribute like this:

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")

This style of coding feels less obvious to me, though. I'd prefer to keep module names.

You would normally use an anonymous function to do it and it is considered idiomatic. Defining a static "getter" for a record field seems superfluous and would add lots of boilerplate.

Anonymous functions being "messy" is only your personal opinion and you should get over it. Though I have to admit that I consider Clojure's syntax for them to be, using your own term, "messy", so I can see where you're coming from ;)

how does this look?

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)

although not much in it.

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

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

Just trying to match your goal of List.map Point.x points

I tend to think of "function" as a short cut to pattern matching .

Rich Hickey uses the phrase, "now it is down to familiarity" in one of his talks. The examples look kind of elegant once you are used to them.

Functional languages are all about a "function is value", or functions should be as easy to handle and pass around as a value. Anon functions rock, but if you'll accept that "function |" is a proxy to a match expression, then maybe this is a valid answer to your question.


Update

no there is not a way to do this in a Clojure fashion currently. It's not a bad suggestion. For me the nicer win would be to make the "List." portion optional. We can infer that we are dealing with a list.

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

Polymorphism would help out here, but there isn't any -- the price of type inference?

btw: on the "property feature", you could fork the F# compiler and make a pull request if you have the time / are so inclined. It's up to us now that F# is open source. Definitely put your thoughts into user voice if there's something that we've missed in our replies.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM