Hacker News new | past | comments | ask | show | jobs | submit login
C# functional language extensions and Erlang-like concurrency system (github.com/louthy)
99 points by networked on Jan 29, 2017 | hide | past | favorite | 48 comments

C# is probably my recommended language for anyone looking to get started with functional programming and event handling.

Its familiar Java/C like syntax, classic use of OOP, enterprise vibes will mean it'll be very familiar and easy for most to pick up. And as you go deeper, it'll introduce you to anonymous delegates, lambdas, true generics, higher order functions, true closures, list comprehensions, anonymous types, type inference, lazy evaluation and even a sprinkle of monads.

With the language extensions, you can get even further.

This is a great way to start learning about FP concepts without even knowing it and it'll make your switch to a more thorough FP language much easier, such as MLs, Lisps, Erlangs, and most proof languages.

I think mixed-paradigm languages make it harder to get started with functional programming, since they provide a crutch to other paradigms, which people tend to fall back on. I'd recommend Scheme, or maybe Elm (if you're doing web programming), as good starting points to learn FP, since both languages are relatively simple to learn and won't let you implement OOP, for instance.

I got started with functional programming with F#. I agree that there were times when I used its hybrid nature as a crutch, but I think there's a value in using a hybrid language if your goal is to learn. F# let me write out code in an imperative style that did what I wanted it to do, and then from there I could refine the code and take advantage of the functional features as I learned them. It's a nice way to learn which features from the different paradigms are equivalent, and it really lets you appreciate what you gain from using FP.

Scheme will let you implement an object system on top of it, using mutable closures, and perhaps macros for syntactic sugar.

The most popular Scheme (Racket) comes with an object system, that I almost instantly fell on when I came to it from C#. I don't think there's any substitute to just explaining the problems with OOP.

That depends on how you think about problems, I think. I understand OOP, and have written OOP code for two decades, using languages like Java, C#, Ruby, C++, etc. However, OOP never felt like a natural way to think to me. The first time I worked with a functional programming was an amazing experience, since FP works in exactly the same way I think. I'm not knocking OOP. I think it is great. It just doesn't fit as well with the way I puzzle through problems. Good thing we have options, I suppose.

Tuples and some pattern matching coming this spring as well.

If the pattern matching isn't exhaustive, it will greatly reduce their usefulness.

It's not, unfortunately. Myself and another have started on an exhaustive pattern matching library that utilizes implicit conversion operators, type inference, lambdas, and a well designed fluent interface to provide support for it in C#. Check out, https://github.com/Jagged/OneOf Distributed as DiscU on NuGet.

> true generics, type inference

This has absolutely nothing to do with functional programming.

> delegates, lambdas, list comprehensions, anonymous types

Syntactic sugar.

> higher-order functions, true closures

Java has them too. They're called “objects”.

> a sprinkle of monads

Every higher-order language with strict evaluation (which includes every object-oriented language) already has at least one monad, whose use is pervasive and inescapable.

So, unless you have user-definable monads, where is the improvement?

> So, unless you have user-definable monads, where is the improvement?

C# does have user-definable monads. There are many in this project (Option, Either, Try, TryOption, Task, Reader, Writer, State, Parser, ...)

No, you only have user-definable type constructors that happen to be monads if you jump to a metalanguage. (Whatever formal or informal system you use to reason about C# code.) Inside C#, you don't even have the means to assert that they are monads, let alone take advantage of the fact that they are monads.

> you don't even have the means to assert that they are monads, let alone take advantage of the fact that they are monads.

Yes, you can.

Here's an example from version 2.0 of lang-ext (which is WIP and will be released soon) [1]:

[1] https://github.com/louthy/language-ext/blob/type-classes/Lan...

That just shows that the result of specializing the monad laws to one specific type constructor, holds. But the monad laws themselves are inexpressible in a general form.

If you can't even postulate a theory (the monad signature and its equational laws), it doesn't make sense to talk about what models of the theory exist (concrete instances).

Tuples will be native first class citizen on C# 7 (https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new...)

Author here. When I started the project C#6 was just arriving. It's awesome to see the ValueTuples coming through for C#7. I have been adding some super-powers to them for lang-ext 2.0 (which is work in progress):

Example of extensions for ValueTuple<A,B,C> (there are extensions up to ValueTuple<A,B,C,D,E,F,G> and Tuple<A,B,C,D,E,F,G>)


It allows cool stuff like this:

    var abc = ('a', 'b').Add('c');                                           // ('a', 'b', 'c')
    var abcd = ('a', 'b').Add('c').Add('d');                                 // ('a', 'b', 'c', 'd')
    var abcd5 = ('a', 'b').Add('c').Add('d').Add(5);                         // ('a', 'b', 'c', 'd', 5)

    var sumA = (1, 2, 3).Sum<TInt, int>();                                   // 6
    var sumB = (2, 4, 8).Product<TInt, int>();                               // 64
    var flag = ("one", "two", "three").Contains<TString, string>("one");     // true
    var str = ("Hello", " ", "World").Concat<TString, string>();             // "Hello World"
    var list = (List(1, 2, 3), List(4, 5, 6)).Concat<TLst<int>, Lst<int>>(); // [1,2,3,4,5,6]

I've been using them quite heavily already. Lovely stuff. Though disappointing that record types didn't make it in.

Doing Haskell nowadays, been years since touching C# --- maybe my memory is fooling me but didn't have C# "structs" (or whatever they were called) from the very start? What else exactly are "record types" if not those?

Or do you simply mean some leaner syntactic sugar for their on-the-fly instantiation?

Structs are mutable value types. Record types would let you define objects with public getters only and constructor that assigns them and a fancy syntax for creating a new object based on an existing record value + new property values, I think something like

    Point a = b with {X = 0} 
this will make using immutable record types in C# much less tedious. Will also implement other stuff like value equality override, etc. I wish this was in 7.0, this is a great way to do FP in C# when you can't use F#.

Proposal link : https://github.com/dotnet/roslyn/blob/features/records/docs/...

Yip syntactic sugar for immutable property only class instantiation.

Structs differ from classes in that they are guaranteed to be contiguously placed laid out in memory; they're also non nullable and can't be a part of the inheritance hierarchy (though they can implement interfaces, which have some perf tradeoffs).

I'm curious how you made that transition,and what problem domain you're in. I predominantly write web applications. Lots of React, but some traditional MVC apps,too. Haskell is a language I want to love, but haven't ever managed to find it productive in my problem space.

Haven't done it all that long, after C# I first went Go for a few years, and spent much time with GL shaders and such too.

Haven't done such "real-world" stuff (with a proper server side, client side, data side etc) in Haskell either --- yet. It's not immediately productivity-inducing at all. I think over the very long haul, prolonged intensive exposure makes many a developer of "mainstream/web/crud/mvp/etc extractions" a much more rigorous, precise, exacting practitioner, which will yield a hard-to-quantify, harder-to-capitalize-on, but still very real and more substantive/deeper/sustained "productivity boost". It also teaches one to think of more robust patterns of abstraction and generalization which can translate back into programming-in-other-languages.

Nothing more tangible than that to report. Also there's a lot of curious-researcher-type "playfulness" in the wider ecosystem that's very tricky to assess what this will actually buy the "let's get coding" "brogrammer" (such as I)!

One facet that makes it quite worthwhile is that the language/compiler/ecosystem has incredibly bright people contributing not just by devising yet another funky undecipherable combinator for saving 10 lines of code in once-a-decade scenarios, but in fact actual hardcore pedal-to-the-metal under-the-hood optimizations, code check/test/validation assistence tools, keeping it all somewhat performant in server/parallel/concurrent scenarios and so on.

As someone new to Erlang, I'm curious what the main benefits of this are? It's obviously pretty popular on GitHub. To my understanding, you can use F# with C# in .NET projects. Also from what I've read, it seems like the biggest benefit of Erlang is BEAM. Would anyone be willing to explain the benefits of this? Is the main benefit just to allow more functional programming in C#?

As a long-time C# dev, now full-time Erlang/Elixir dev, I've written things like this before myself when attempting to bring some of the FP tools I loved back to C# for my own benefit - so this looks really familiar in that sense. This is by no means an actual competitor to Erlang's actors - for one thing it lacks any form of supervision, which is really the most important aspect of Erlang's actor model (i.e. the "let it crash" philosophy). There are other things missing as well (such as the OTP behaviours, e.g. gen_server, provided by Erlang).

That said, this looks like something I'd definitely use, or at least experiment with in C# if I was still doing work in the language - though for the actor bits I would probably lean on Orleans.

> This is by no means an actual competitor to Erlang's actors - for one thing it lacks any form of supervision

Author here. That is simply not correct. It has a full supervision hierarchy as well as a compositional strategy system for creating policies that handle failures. The strategies can also be scripted as part of the config, to detach the failure handling entirely from the actor itself.

Hi Matt, I'm the author of the project. The reason I wrote it is because I have a 12 year old C# project that's creaking at the seams from the issues of OO development. I believe OO becomes cognitively very difficult when a project reaches a certain scale. Functional programming allows my brain to manage complexity much better.

I couldn't just jump ship and use F#, Haskell, or Erlang; so I decided to bring what I'd learned from those languages into C#, and create a library that will help me write better C# code.

My mission is to create a library that reduces the cognitive burden of writing C#. It isn't a functional manifesto for C#, it just looks like it because I've found that's the best way to achieve my goals.

The Process system (Erlang style actors) is there to deal with the 'edges' of functional code. It's not possible to be pure like in Haskell, and so you will have stateful code - with all the problems that come with it, i.e. shared memory, locks, mutability, etc. Actors allow a certain amount of control that you don't get from C# classes: Single-threaded and no-shared state. They can be distributed anywhere and the mechanism for interaction is the same. You can build in routing or proxying to do load-balancing, etc.

That makes perfect sense. Thank you for taking the time to reply.

The thought of trying to fix up an old code base did not even cross my mind. Probably because that is what my nightmares are made of.

Why should I use this instead of Akka.net or Orleans for actors in C#?

I personally use both Akka and LanguageExt together. The higher order type features, monadic operators and types such as Optional and Either are useful for any project

Reading through the docs, this looks really nice. Forget the Erlang in the title, and just think of this as a nice functional C# hack. I'm thinking of using C# on a side-project because the tooling around F# in .NET Core on Mac is sub par right now. This library might be just the ticket.

It's not clear to me from a cursory look through the docs how the scheduling system works. "Erlang-like" concurrency works so well predominantly because the runtime uses preemptive scheduling instead of cooperative (which is what basically everything else uses, unfortunately).

I think it works similar to how Akka actors work. The actors are purely reactive and their message processing functions are called whenever a new message arrived. You shouldn't block them (e.g. to wait for a response from another actor), because that blocks the actor from doing other things as well as an OS thread. Instead you should return and wait for being called again with a response message. This makes some things a little bit harder to write, since you will need some kind of state machine and store the current state of actors in a more explicit fashion. However you can also achieve a higher reactivity with this kind approach, so it's a trade off. In C# you could basically get some kind of best-of-both-worlds by allowing async handlers (that return Task<T>), which means no thread would be blocked if the message handler would do a long running async operation. I think that was discussed for Akka.Net, but I don't know if it's now supported or not.

That's incorrect, most scheduling systems, including OS threads are preemptive. Erlang's scheduler itself is actually cooperative - though it can still preempt the "processes" it schedules.

Erlang's message passing system is probably more important to the success of its concurrency model than the specifics of the scheduler. As proven perhaps by the success of Akka and other actor-based systems built on a different threading model.

I was talking about scheduling in actor-pattern frameworks and non-OS runtime environments (JVM, C++, Python, etc. etc. etc.).

Erlang's scheduler is preemptive. Erlang processes can be preempted by the scheduler in the middle of execution and moved to the back of the scheduler queue or to another scheduler thread. There is no requirement for a process itself to yield time back to the scheduler to make sure that other processes receive some execution time. This is one component of how it achieves "soft-realtime" semantics. Eight processes, on a system with 8 scheduler threads, running in a tight loop cannot cause all other processes in the Erlang VM to be blocked. Other processes will be given time.

The only thing that violates this guarantee is long-running or blocking code executing on the native side of the NIF boundary.

Akka, goroutines, Cloud Haskell, etc. are exactly the kinds of things I'm talking about when I say I don't understand the point of actor frameworks running in environments that can't preempt actor execution. You had better hope all your actor implementations are well-behaved or that the implementations you're going to import from someone else are all well behaved... otherwise you're going to lockup your scheduler's thread pool.

I think what he meant is that from a programmer's point of view, Erlang appears to be preemptive (there is no explicit yielding of control, ala golang or lua, the two I can think of off the top of my head), even if the scheduler itself is cooperative. The place where this most obviously reveals itself is with NIFs, where the scheduler can no longer preempt the process, and is blocked until the NIF finishes executing. At least that's my understanding.

Right, that's exactly what I said above. But OS threads are preemptive in exactly the same way. It is not unique to Erlang as claimed, in fact it's the norm.

I think you're confusing the issue; OS processes are preemptive, threads within a process are not. Preemptive threads are not the norm on Unix / Linux / Windows systems. The BEAM VM implements its own threads which allows for them to be preemptively scheduled.

> That's incorrect, most scheduling systems, including OS threads are preemptive. Erlang's scheduler itself is actually cooperative - though it can still preempt the "processes" it schedules.

OS processes are preemptive, however process threads are not. I'm not sure what you are trying to say about Erlang's scheduler itself being cooperative. It is a preemptive scheduler.

> Erlang's message passing system is probably more important to the success of its concurrency model than the specifics of the scheduler.

I disagree, preemptive concurrency is a fundamental piece of BEAM's success. Preemption gives Erlang its fault tolerance and low latency guarantees. If by message passing you mean immutability, then perhaps they are of equal importance.

> That's incorrect, most scheduling systems, including OS threads are preemptive. Erlang's scheduler itself is actually cooperative

From what I know Erlang's scheduler preempts processes it executes. Can you explain what you mean by it being "cooperative". It precisely useful because it is not cooperative. And processes don't have to "yield to" the scheduler.

Not quite, as a sibling comment states. Everything today's operating systems provide in scheduling (processes and OS-level threads) is preemptive. Erlang's (BEAM's) scheduling is cooperative, but Erlang compiler inserts yield points in appropriate places, so it actually looks and feels like preemptive scheduling.

Erlang processes can be interrupted by the scheduler. Saying that Erlang scheduling is "cooperative" because it uses reduction counts as one (among many) hints about how to decide what to schedule is like saying that preemptive OS kernel schedulers are "cooperative" because they use mechanisms like time-slices, etc. to attempt to balance execution resources.

The Erlang VM can intercede into a running process, and also does this with some of it's BIFs implemented in C.

> Erlang processes can be interrupted by the scheduler.

From what I know, they cannot, not at arbitrary places, and that's why the scheduler is a cooperative one.

> "Scheduling is pre-emptive. Regardless of priority, a process is pre-empted when it has consumed more than a certain number of reductions since the last time it was selected for execution."

From the Erlang docs on process priority.

The BEAM VM can kill, halt, or suspend a running process at anytime from outside the process. It does not need to wait around for the process to exceed its reduction count limit.

Some of this capability is even exposed to user-land applications. The exit/2 function builds on top of this capability when sending in th atom 'kill' as the second argument. As do the suspend_process/1, suspend_process/2, and resume_process/1 functions.

The reduction count system is just there as a kind of fair-scheduling determinism semantic. Exhausting reduction allocations is not the only way the VM has to interact with running processes.

>> "Scheduling is pre-emptive. Regardless of priority, a process is pre-empted when it has consumed more than a certain number of reductions since the last time it was selected for execution."

> From the Erlang docs on process priority.

Well, apparently you're right. I must take back what I said about Erlang scheduler.

Author here. "Erlang-like" was mostly used for programmers that aren't aware of actor systems, or the current de facto standard language for actors. It's not a statement on its competitiveness with Erlang.

The system simply uses the TPL for scheduling (which is actually very good).

I haven't programmed in C# for 14 years, but it is nice to see projects embracing functional programming and the actor model. It solves all kinds of concurrency headaches. I'd still opt for Erlang or Elixir over this due to OTP and BEAM, but it is still great to see things moving in this direction.

I didn't realise I'd hit the front page again! I'm the author of this project. Feel free to AMA.

I'm currently working on V2 of this project to bring ad-hoc polymorphism to C# (almost type-classes), and splitting out the Core and Process (actors) libraries to separate repos.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact