Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

clojure

f#

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 -> p.x) 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.!

like image 597
vidi Avatar asked Oct 25 '14 12:10

vidi


People also ask

What chord can I play instead of F?

An F chord is formed with the notes F, A and C. We can play a simple triad shape across strings 4, 3 and 2. If I replace the A note on string 3 with the open G I create what is called an Fsus2. Adding an open string 1 to these shapes creates an Fmaj7 (commonly used by beginners to replace an F chord) or Fmaj7sus2.

Why is an F chord so hard?

One of the reasons the F chord is difficult to play is because it's positioned on the 1st fret of your guitar. A good rule of thumb to remember is as follows: the lower the fret, the higher the string tension. It takes tremendous finger strength to barre across the first fret.


2 Answers

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.

like image 131
Christopher Stevenson Avatar answered Nov 15 '22 08:11

Christopher Stevenson


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.

like image 26
sgtz Avatar answered Nov 15 '22 07:11

sgtz