Is F# not also a solid and complementary source of design ideas given the goals of Julia?

1,203 views
Skip to first unread message

Sam Isaacson

unread,
Jun 11, 2012, 8:17:44 AM6/11/12
to juli...@googlegroups.com

Reading through the main site, the manual and the forum there is a strong focus on R, Python and Matlab as benchmarks for Julia in various ways (not just performance). I have spent more time researching feasible/potential data analysis environments than is healthy and I would like to add one "benchmark" to your list, namely F#. The most interesting thing about F# in this instance is that it takes a different approach to R, Python and Matlab (all quite similar to one another as dynamic scripting language that drop into C for performance with vectorization the main code metaphor). My intention is not to start a discussion on the pros and cons of Julia versus F# versus xxx, but rather to draw the core team's attention to some really useful features of the F# language design that could perhaps be incorporated into Julia:


  • Pipeline Operator: This is a highly intuitive way to code on a high-level. It requires higher order functions (Julia has) and partial functions(?) and currying(?)
  • Pattern Matching: Incredibly useful with recursive data structures like Discrimated Unions
  • Option Types: these wrap a value that could potentially be empty/null in a consistent manner. Could this somehow relate to allowing for missing values in a manner as natural as R does (per some of the comparisons of Julia and R)? 


At the heart of the above lies the question: can Julia support coding in the style of a full functional language given that functions are without doubt first class citizens of the language? 


Jeffrey Sarnoff

unread,
Jun 11, 2012, 11:09:24 AM6/11/12
to juli...@googlegroups.com
Hi Sam,

I prefer your experience to my first read of online docs -- would you elaborate a bit?  What is it that this Pipeline Operator, those Option Types, and that important way of using Pattern Matching let occur easily where other paradigms are challenged?  Also, please share some perspective on the power/elegance each offers data analysis.

Krzysztof Kamieniecki

unread,
Jun 11, 2012, 11:41:42 AM6/11/12
to juli...@googlegroups.com
Pattern matching has been discussed (I'm not sure where that discussion went), it is what I miss most from ML style languages in Julia. I would love to see it added to Julia, but I would not want to sacrifice aggressive JIT compiling.

Julia's Union types match with what my understanding is of F# Option Types. http://julialang.org/manual/types/ however without method call (or other cases) pattern matching, you don't get the same expressiveness.

Sam Isaacson

unread,
Jun 11, 2012, 11:45:53 AM6/11/12
to juli...@googlegroups.com
Hi Jeff

Just to be clear, none of the things mentioned in my post are present in Julia or the online docs at present...hence my post :-)

You ask for an explanation of the functional programming concepts I mentioned above. It took me reading a whole book on F# to really "get" it so I hope I can do the concepts justice in the short space below!

Pipelines

Pipelines of operations/functions reverse the standard order of imperative calling of functions where a functions acts on certain inputs. On the other hand, pipelines follow the metaphor of pushing data through functions to get a result. You then end up with code that looks like:

let result = 
input_data -> function1
                    -> function2
                    -> function3

In words: you apply input_data to function1, then the result of this operation gets applied to function2 and the result of that to function3. Finally, the answer is bound to the result symbol (for completeness).

Compare this to:
let result = function3(function2(function1(input_data))))

Both get the job done, but they have a very different feel.

A classic example from a data analysis workflow would be:

let result = 
input_data -> filter
                    -> remove_outliers
                    -> build_model


Pattern Matching

This is a sophisticated control flow statement that is doing more than just checking boolean expressions to direct traffic. An example of the built-in sophistication is that it is very natural to direct the flow according to the contents and/or type of a variable. This leads to code that looks like:

match (object):
   something -> function(something)
   nothing -> null

This pre-supposes that the object above has been wrapped in an Option type and hence is optionally *nothing*. In words, if the object contains a *something* then it automagically binds the something variable for you and allows you to call a function on the something.


I hope that sheds some light on F# and functional programming.

Krzysztof Kamieniecki

unread,
Jun 11, 2012, 11:52:47 AM6/11/12
to juli...@googlegroups.com
Here is the thread on pattern matching for method calls


On Monday, June 11, 2012 8:17:44 AM UTC-4, Sam Isaacson wrote:

david tweed

unread,
Jun 11, 2012, 12:18:43 PM6/11/12
to juli...@googlegroups.com
So it looks like pipelines are an another way of doing the composition HOF o (in ML) or . (in Haskell). The only difference is the order: I suppose if you haven't been "trained" by years of writing maths by hand the left to right might be easier to intuit thant he right to left. What's most interesting is if the formulation of all these things can be made transparent enough that optimisations like deforestation (aka removal of temporaries) can be applied automatically.

Note that there's also nothing inherently functional about pattern matching: it's been made available in Scala. It'd be nice, but I'd prefer it if the overhead of argument passing/function dispatch was such that there was absolutely no pattern matching cost in the common case pattern matching isn't being used.

Jason Nielsen

unread,
Jun 11, 2012, 8:42:43 PM6/11/12
to juli...@googlegroups.com
The ML languages (SML, Ocaml, F#) are great for writing compilers!  Pattern matching ala ML is the key to why this is so (type inference, gc, etc. but I digress since julia already has these).  If you look at the syntax of pattern matching in these languages and yacc/bison syntax you might see some similarities that I'd argue supports my previous statement.  If julia had ML style pattern matching getting it to be truly self hosting (which has been stated as a desirable thing in a few threads on this list) would certainly be much easier to implement and maintain.

Jason

John Cowan

unread,
Jun 11, 2012, 9:52:32 PM6/11/12
to juli...@googlegroups.com
On Mon, Jun 11, 2012 at 8:42 PM, Jason Nielsen <drjdn...@gmail.com> wrote:

> The ML languages (SML, Ocaml, F#) are great for writing compilers!  Pattern
> matching ala ML is the key to why this is so (type inference, gc, etc. but I
> digress since julia already has these).

I looked through Andrew Appel's famous 1992 "Critique of Standard ML"
to see the good points of ML and whether Julia has them.

Safety: Check.

Garbage collection: Check.

Compile-time type checking: Check.

Module system: Not as good as ML's, certainly.

Immutable values; On the to-do list.

Mutable objects: Check.

Polymorphism: Check.

Type inference: Not needed due to dynamic typing.

Higher-order functions: Check.

Efficiency: Check.

Note that pattern matching is not even mentioned! It's just syntactic
sugar in a dynamically typed language anyway.

And here are what he reports other people consider to be the bad points:

Lack of dynamic types: Julia has them.

Restrictive type system: Julia's is not.

Mutation: Julia has it.

Lack of direct access to machine/C types: Julia has that.

Lack of separate compilation: Julia doesn't have that either.

Bizarre syntax: A matter of taste.

--
GMail doesn't have rotating .sigs, but you can see mine at
http://www.ccil.org/~cowan/signatures

Jason Nielsen

unread,
Jun 12, 2012, 11:53:38 AM6/12/12
to juli...@googlegroups.com
On Mon, Jun 11, 2012 at 9:52 PM, John Cowan <johnw...@gmail.com> wrote:

Note that pattern matching is not even mentioned!  It's just syntactic
sugar in a dynamically typed language anyway.

It may be syntactic sugar in a dynamically typed language but definitely very useful when having to do any serious manipulation of an AST.  I think the julia dev's would agree (see match.scm in the source tree). 

John Cowan

unread,
Jun 12, 2012, 12:41:19 PM6/12/12
to juli...@googlegroups.com
On Tue, Jun 12, 2012 at 11:53 AM, Jason Nielsen <drjdn...@gmail.com> wrote:

>> Note that pattern matching is not even mentioned!  It's just syntactic
>> sugar in a dynamically typed language anyway.
>
>
> It may be syntactic sugar in a dynamically typed language but definitely
> very useful when having to do any serious manipulation of an AST.

Oh yes. Convenience features are there for convenience, after all.

Also, I didn't mean to suggest that pattern matching isn't syntactic
sugar in statically typed languages. Haskell pattern matching reduces
to case statements in Core Haskell. It's just that it has an effect
on the Hindley-Milner type system (as does let) that isn't relevant to
dynamic languages.

Tom Short

unread,
Jun 13, 2012, 8:50:34 AM6/13/12
to juli...@googlegroups.com
On Mon, Jun 11, 2012 at 11:45 AM, Sam Isaacson <cogno...@gmail.com> wrote:
> Pipelines
>
> Pipelines of operations/functions reverse the standard order of imperative
> calling of functions where a functions acts on certain inputs. On the other
> hand, pipelines follow the metaphor of pushing data through functions to get
> a result. You then end up with code that looks like:
>
> let result =
> input_data -> function1
>                     -> function2
>                     -> function3


It's a one-liner to add piping capability to an existing operator using multiple
dispatch:

(|)(x, f::Function) = f(x)

Here's an example of piping data through a series of transforms:

julia> mydata = [1:5] / (2pi)
5-element Float64 Array:
0.159155
0.31831
0.477465
0.63662
0.795775

julia> mydata | cos | atan | cumsum | sort
5-element Float64 Array:
0.779039
1.53868
2.26492
2.94216
3.5527

This is equivalent to sort(cumsum(atan(cos(mydata)))).

You can also use anonymous functions:

julia> mydata | cos | atan |
(x -> x.^2) | (x -> x[x .< 0.6]) | sort
4-element Float64 Array:
0.372766
0.458657
0.52742
0.577052

The parentheses are required around the anonymous function
definitions. I looked for an operator that has a low enough precedence
to allow removal of these parens. There are some available that would
fit the bill (like =>), but they are not activated. I like | better anyway.

Likewise, it's easy to add function composition:

(&)(f::Function, x) = f(x)
(&)(f::Function, g::Function) = x -> f(g(x))


julia> cos & atan & cumsum & sort & mydata
5-element Float64 Array:
0.98757
0.902414
0.723217
0.532018
0.386353

You can also use function composition with higher-order functions:

julia> map(atan & cos, mydata)
5-element Float64 Array:
0.779039
0.75964
0.726237
0.677242
0.610546

In this case, that's equivalent to atan(cos(mydata)), since these are
both already vectorized.

A limitation of Julia for function composition is that function
arguments are not automatically curried (automatic currying conflicts
with multiple dispatch). You can manually curry a function, but that
may not fit with other function definitions.

Stefan Karpinski

unread,
Jun 13, 2012, 9:08:27 AM6/13/12
to juli...@googlegroups.com
Oo. I rather like this. I've thought about using => for data pipelining like this before. I've already defined * for function composition:

https://github.com/JuliaLang/julia/blob/master/base/operators.jl#L134

That let's you also have f^n for iterated function application, which can be handy. I've also thought about having the => do automatic currying essentially by rewriting

v => cos => filter(f) => map(g)

as `map(g,filter(f,cos(v)))`. But that's a lot of not-strictly-necessary syntax shenanigans, which I'm always pretty hesitant to get into.

Jeffrey Sarnoff

unread,
Jun 14, 2012, 2:39:27 AM6/14/12
to juli...@googlegroups.com
While formal expressiveness in a computer language is about what can be expressed, there is much to be said for a more experiential quality: ease and flexibility of expression.  Including an automatic currying operator gets a high score on the latter scale.


      v => cos => filter(f) => map(g)

allows people to want to write

        v 
        => cos
        => filter(f)
        => map(g)

do it inside parens, would it also work using begin..end instead of parens?

The automatic currying operator is a greater tool with fileop("file.jl"), a
'file namespaced' anonymous function call: 

        # filter.jl  has one or more anonymous functions with locally unique signatures
        # map.jl  has one or more anonymous functions with locally unique signatures
        #
        # fileop("file.jl") is something I don't know how to do .. it pulls in a file
        #       and maybe @gensym identifies anonymous functions in it
        #       then applies the signature matched anonymous function 
  
        v => cos => fileop("filter.jl")(f) => fileop("map.jl")(g) 

Sam Isaacson

unread,
Jun 14, 2012, 5:42:18 AM6/14/12
to juli...@googlegroups.com
I agree with Jeffrey on the experiential quality point.

The F# team consistently used the expression that functional programming (and F#) allowed code to be created in a more declarative way. There explanation for what declarative meant in this context is that it allowed you to express yourself in a manner closer to the problem domain and not bound to the machine representation of your instruction. I would imagine that (in the absence of losing out from a performance point of view) this would be highly desirable for Julia as a language.


Tom Short

unread,
Jun 14, 2012, 9:03:15 AM6/14/12
to juli...@googlegroups.com
On Thu, Jun 14, 2012 at 2:39 AM, Jeffrey Sarnoff
<jeffrey...@gmail.com> wrote:
> While formal expressiveness in a computer language is about what can be
> expressed, there is much to be said for a more experiential quality: ease
> and flexibility of expression.  Including an automatic currying operator
> gets a high score on the latter scale.

With multiple dispatch, it's pretty easy to add curried versions of
functions, and you
only have to do it once. Here's an example for map() with the pipeline
operator I
defined previously in this thread:

julia> map(f::Function) = x -> map(f, x)

julia> [1,2,3] | map(x -> x^2)
3-element Int64 Array:
1
4
9

>         v => cos => fileop("filter.jl")(f) => fileop("map.jl")(g)

I'm not sure what you're after on this part.

Jeffrey Sarnoff

unread,
Jun 14, 2012, 9:31:01 AM6/14/12
to juli...@googlegroups.com
fileop() is intended to be a way to make it easy and safe for different people to contribute data transformation functions and let me tie them together in whatever sequence helps the task at hand.  Instead of load()ing each one then using whatever naming occurs in their file -- and those sorts of names often do overlap (i run into that often using multiple R libraries) let fileop() take away the problem.  It is like, but better than systems where the name of the file is the name of the function in the file and using that name calls the function.   To have files of data related actions, each contributed by someone well-suited to write it, and then be able to use that body of work without needing to disentangle something great from inside of a different program -- well that would worthwhile.

fileop() is a vehicle for specialists to contribute data transformations in a way that makes them most available for the user community.  it just would be more healthy than reliance on library packages alone.

Stefan Karpinski

unread,
Jun 14, 2012, 3:15:02 PM6/14/12
to juli...@googlegroups.com
Ah. This is even better. So we just got pipeline goodness with zero language changes. Multiple dispatch for the win! I think I will add these definitions soon, using | for this. I think I'm going to stick with * for composition, however. Hopefully the precedence doesn't cause problems...

Stefan Karpinski

unread,
Jun 15, 2012, 5:10:50 PM6/15/12
to juli...@googlegroups.com
On Mon, Jun 11, 2012 at 8:17 AM, Sam Isaacson <cogno...@gmail.com> wrote:

Reading through the main site, the manual and the forum there is a strong focus on R, Python and Matlab as benchmarks for Julia in various ways (not just performance). I have spent more time researching feasible/potential data analysis environments than is healthy and I would like to add one "benchmark" to your list, namely F#.

If someone (maybe you?) wants to write F# benchmarks, that would be cool. However, there are a few issues. Primarily, since it's not an open source language, I'm not terribly interested in this comparison. Of course, Matlab isn't either, but that's a little different just because it's much closer to what Julia core competencies are. But, I'm not going to be able to benchmark Microsoft's F# implementation on my OS X machine and we're not going to be running our benchmarks on Windows any time soon. So even if someone were to write F# benchmark code, I really doubt the comparison would go on our website in any case.

More generally, the .NET languages, like JVM languages (and all managed runtime languages, really), are not, imo, a great choice for numerical computing. While you don't need as much control for numerical computing as you do for systems programming, you still need more than any of the managed runtime platforms provide. Not to mention issues like 32-bit pointer limits, which, afaik, both the JVM and CLR are saddled with.

The most interesting thing about F# in this instance is that it takes a different approach to R, Python and Matlab (all quite similar to one another as dynamic scripting language that drop into C for performance with vectorization the main code metaphor). My intention is not to start a discussion on the pros and cons of Julia versus F# versus xxx, but rather to draw the core team's attention to some really useful features of the F# language design that could perhaps be incorporated into Julia:

  • Pipeline Operator: This is a highly intuitive way to code on a high-level. It requires higher order functions (Julia has) and partial functions(?) and currying(?).
Thanks to Tom Short's observations and three method definitions, we now support this style of programming.
  • Pattern Matching: Incredibly useful with recursive data structures like Discrimated Unions
Pattern matching would be cool. I opened an issue for this a long time ago, inspired by the Magpie language, which makes really nice, consistent use of pattern matching for a lot of things and is a dynamically typed language (unlike the ML family). The big problem here is that you don't just want a pattern matching operator, you'd want to be able to use all the pattern matching operators for method dispatch too. And that's *the* central feature of the language that has to work really fast and well. So adding complicated features to it is both non-trivial and risky work. Even the design would be quite difficult. It could certainly happen, but I suspect it's going to be a post-1.0 feature.
  • Option Types: these wrap a value that could potentially be empty/null in a consistent manner. Could this somehow relate to allowing for missing values in a manner as natural as R does (per some of the comparisons of Julia and R)? 

You can certainly do this in Julia:

typealias Maybe{T} Union(Nothing,T)

There, it's done. Of course, you don't even have to do this. You can just write functions that either return a value of some type or `nothing`. You don't have to explicitly type these functions at all.

Let's be honest though — option types suck. All reference types in Java are essentially option types and it's pretty much the worst thing in the world. It leads to having to check everywhere if everything is or is not null. I implicitly used an option type the regex matching API because I couldn't figure out a better interface: the match function either returns `nothing` or a `RegexMatch` object; you call it and then have to check which. But this design is terrible. I hate that I had to write it. I hate using it. I still want an else-block syntax in addition to the do-block syntax so that I never have to use it again.

At the heart of the above lies the question: can Julia support coding in the style of a full functional language given that functions are without doubt first class citizens of the language?

I dunno? Does this thread answer the question in the affirmative? I hope so. I feel like all we need at this points are monads and some more category theory ;-)

Patrick O'Leary

unread,
Jun 16, 2012, 1:11:04 PM6/16/12
to juli...@googlegroups.com
On Friday, June 15, 2012 4:10:50 PM UTC-5, Stefan Karpinski wrote:
You can certainly do this in Julia:

typealias Maybe{T} Union(Nothing,T)

That doesn't actually work. Should it?
 
julia> typealias Maybe{T} Union(Nothing, T)
type error: typealias: expected Type{T<:Top}, got TypeVar
 in anonymous at no file

Let's be honest though — option types suck.
...
I dunno? Does this thread answer the question in the affirmative? I hope so. I feel like all we need at this points are monads and some more category theory ;-)

Of course, having Maybe as a monad makes option types suck less, because you can make nullity checks implicit. Without going through the trouble of figuring out how to make a Maybe monad instance, though:

julia> |(x, f::Function) = !isa(x, Nothing) ? f(x) : Nothing()

julia> match(r"(ab)(ab)", "abab") | x -> x.captures | map(x -> x^2)
("abab","abab")

julia> match(r"(ab)(ab)", "bbab") | x -> x.captures | map(x -> x^2)

julia>

You could sweeten this further, though I don't know of a good operator choice. I'm going with ">" here. You could also add another dispatch to "|". Not sure which one I prefer.

julia> (>)(x::Union(RegexMatch, Nothing), f::Function) = !isa(x, Nothing) ? f(x.captures) : Nothing()

julia> match(r"(ab)(ab)", "abab") > map(x -> x^2)("abab","abab")

julia> match(r"(ab)(ab)", "bbab") > map(x -> x^2)

julia>

Jeff Bezanson

unread,
Jun 17, 2012, 12:39:59 PM6/17/12
to juli...@googlegroups.com

I would not recommend that. Parsing code at run time is not really practical.

Stefan Karpinski

unread,
Jun 17, 2012, 2:22:06 PM6/17/12
to juli...@googlegroups.com
Heh. Fair point. The way Haskell and your code here handle option types is far less annoying. I'm not sure we want to encourage this style of coding in Julia though. It's far less efficient than just writing a simple nested function application, and the currying of functions like map and filter has to be done manually, which is a bit questionable. Jeff and I discussed some approach that would make this style of programming actually do stream fusion and avoid temporaries, which would make it really beneficial, but didn't really get anywhere with that train of thought.

Stefan Karpinski

unread,
Dec 23, 2012, 1:39:40 PM12/23/12
to Julia Dev
On Sun, Dec 23, 2012 at 12:49 PM, nrolland <nicolas...@gmail.com> wrote:

F# is an open source langage released under Apache licence.
You can run it on your Mac as much as you want, and it is part of standard release of Mono, the open source dotnet.

That's true – thanks for the correction. It has slipped my mind that Microsoft open sourced F# back in 2010.
 
Let's be honest though — option types suck.  
All reference types in Java are essentially option types and it's pretty much the worst thing in the world.

Option types refers to a type system existence of null as a type by itself, while Java's null reference is pretty much the opposite of this. It is actually very useful to catch those errors at compile time, you should try it 

Also a good point. Since Julia is dynamically typed, however, having Null be it's own type (e.g. our Nothing type) is just as bad as nullable types in Java except that we can dispatch on the Nothing type, which is only a slight improvement.
Reply all
Reply to author
Forward
0 new messages