9 ms·
Show HN: C# 6.0 Functional Language Extensions
- steego 12y agoFunny I come across this today. Only yesterday did I start turning some of my helper functions and extension methods into a little NuGet library. It's amazing how much overlap there is between your library and mine, though I'm probably going to steal some of your tricks. :) While I do have an Option type with all the relevant LINQ operators defined, I've resorted to using Nullable<T> for all string parsing functions that return primitives as Nullable is a type that's actually supported databases. For any nullable, I can use a nullableVar.DefaultTo(defaultValue) to convert it to a non-nullable primitive type. Personally, I don't find Option types in C# all that compelling because the pattern matching mechanism doesn't compel you to handle all cases like F#. All in all, it looks like an interesting library. Thanks for posting!
- louthy 12y agoHeh, that's really interesting! Do you have a link to your work? > Personally, I don't find Option types in C# all that compelling because the pattern matching mechanism doesn't compel you to handle all cases like F#. In my library you are compelled. You must provide both a Some and None handler, even on the fluent variant. That was one aspect I absolutely wanted to nail after doing a reasonable amount of F# work recently. The only exception to that is you can ignore the Some branch, and just handle failure. The Some branch then just uses an identity function behind the scenes. So you're still compelled to deal with the failure case, which is where I feel most of the C# null carnage comes from.
- steego 12y ago> Heh, that's really interesting! Do you have a link to your work? I do and I don't. I actually wrote a lot of these things in VB.NET 4-5 years ago and I've recently returned to the .NET world, so I'm in the process of converting it to C#. There's a ton I haven't converted yet (Option types, lazy thunks that use LINQ, Parser monads, LINQ Trees, etc.), and there's a ton I'm going to let die out. :) Here's a work-in-progress: https://github.com/steego/Steego.NET https://github.com/steego/Steego.NET
- louthy 12y ago> There's a ton I haven't converted yet (Option types, lazy thunks that use LINQ, Parser monads, LINQ Trees, etc.), ahem https://github.com/louthy/csharp-monad https://github.com/louthy/csharp-monad ;-) > Here's a work-in-progress: Cool, I'll take a look.
- steego 12y ago> ahem https://github.com/louthy/csharp-monad https://github.com/louthy/csharp-monad ;-) Very nice! You may save me a bunch of work yet. We should get some of your stuff into NuGet packages so you can spread the love. I saw on Twitter that Miguel de Icaza approves of your library. :) BTW, I just pushed up my little Tree library to my repo. Feel free to incorporate and change it however if you like it. https://github.com/steego/Steego.NET/tree/master/Steego.Trees https://github.com/steego/Steego.NET/tree/master/Steego.Tree...
- louthy 12y ago> I saw on Twitter that Miguel de Icaza approves of your library. :) That's awesome! Thanks for sharing. I wouldn't even know how to get something onto NuGet. I guess my weekend project might have to become a bit more serious ;) Time to do some research. EDIT: research done... https://www.nuget.org/packages/LanguageExt/0.0.1-beta https://www.nuget.org/packages/LanguageExt/0.0.1-beta
- Too 12y agoIt's a good idea but i think in reality people will be lazy, not know what to do, and just throw their own NullPointerException inside the None-handler. And then we are full circle back on square one. It's like checked exceptions in java. Sounds good on paper but in practice this happens: You are in the middle of just bursting out code to quickly get a response from a local rest api and you hit compile to suddenly get a whole list of "NetworkException/JsonParseException/WhateverBloatException not handled". At the moment you couldn't care less and just convert everything into a RuntimeException and tada! you don't have checked exceptions anymore. I think it might also cause unnecessary bloat when mixing code that uses the Option type and not, I've seen it happen with code contracts, either everything or nothing must be annotated. Once you start mixing you will have to add assertions before or after every call to the unannotated library.
- jpatte 12y ago> For any nullable, I can use a nullableVar.DefaultTo(defaultValue) to convert it to a non-nullable primitive type. What would be the difference with "nullableVar ?? defaultValue" ?
- steego 12y agoGood point. I originally wrote it for VB.NET, though IF(nullableVar,defaultValue) would be better now...
- harveywi 12y agoIt's great that people see the value in having these kinds of functional tools! I also put together a library that looks very similar to this, and it sure has been handy. I used it as a foundation to build some solid algebraic APIs (typesafe parser combinators based on the Brzozowski derivative and pickler combinators). I like how the catamorphism is called "Match" rather than "Fold" (which is what Scala uses). Using named parameters whenever Match is called really does help with the readability, and they make the "case analysis" explicit.
- saosebastiao 12y agoBeautiful. I've been trying to pick up C# simply due to Xamarin, and this definitely looks like it will make me feel more at home. Has anyone had success with 6.0 on Xamarin? (Is it even out yet? I saw announcements, but they looked like previews).
- sandyarmstrong 12y agoMany C# 6 features work in Xamarin's compiler, but aren't necessarily understood by the IDE, for example. `using` static classes and the ?. operator both work.
- nbevans 12y agoJust use F# from Xamarin. It's the real deal. C# is just a stop-gap measure to suck in the enterprise devs.
- saosebastiao 12y agoExcept that all the docs are in C#, which means if I want to consult them, I have to either know C# or know how F# interops with C# (and therefore understand C#). Trust me, I would love to, but I don't have the time to learn two languages for one project.
- louthy 12y agoOut of interest what language are you coming from?
- saosebastiao 12y agoI'm all over the map. My day job requires tons of quick and dirty data analysis or concept prototyping which tends to get thrown away, which moves me towards declarative langugages (lots of SQL and Prolog) as well as pragmatic dynamic languages (Clojure,R,Ruby). But if I'm actually building anything that is meant to last more than a month, I tend much more strongly to an ML philosophy. Currently that is mostly Scala and some Rust, but I've been wanting to play with F#/OCaml for a long while.
- bcbrown 12y agoCool stuff. Isn't there still a problem with Option, where when a method takes Option<T> as a parameter, invocations can pass null for that Option<T>? So there's still the possibility of null exceptions.
- infogulch 12y agoFrom the article: > Option<T> works in a very similar way to Nullable<T> except it works with all types rather than just value types. It's a struct and therefore can't be null.
- louthy 12y agoExactly. Option<T> has a implicit conversion operator that converts a type of T to Option<T>. So passing T to the function constructs a new Option<T>, and the constructor checks for null. It's also likely (though not guaranteed) that the Option<T> will be allocated on the stack, and should therefore not pose too much of an overhead.
- bcbrown 12y agoAwesome.
- tommyd 12y agoImpressive looking stuff - I'm currently learning Scala and getting deeper into functional programming, and a lot of the concepts I come across (e.g. Option types) make a lot of sense, so it's cool to see them being implemented in other languages - hopefully with the recent MS announcement, I'll be able to get back into C# development in the future as it's a pretty great language all in all. As an aside, I recognised your name in the example code on there - I used to occasionally hang around on 4four :)
- louthy 12y ago> I used to occasionally hang around on 4four Nice. 4four's still going, but it's very quiet these days. Killed by the Facebook behemoth. That's life, it was fun while it lasted :)
- martijn_himself 12y agoThis is a really interesting experiment, although personally I'd find using: var ab = tuple("a","b"); awful. It's very hard to read what is going on here- someone else reading your code would be utterly confused. The method name starts with a lowercase character, and I'm not sure I like the possibility to omit class names for static classes (granted this is not your invention but a new C# feature right?); to me it would seem it is missing the 'new' keyword. I'd rather use Tuple.Create<T1,T2>(T1 first, T2 second) until they add proper support for: var ab = ("a","b");
- louthy 12y agoThat's fair enough. I knew implementing it this way would ruffle a few feathers, and I totally understand why. I too would love to see ("a","b"), and proper pattern matching... and .. This was a weekend project that kind of grew over the past few days. Initially I wasn't going to make everything lowercase, but I started thinking about the fact that a lot of these functions are now essentially global and the potential for clashes in real code. So I felt having a core set of useful functions that look more like the lowercase keywords of C# rather than library methods would work better. It definitely won't be everybody's cup of tea though.
- martijn_himself 12y agoI realise my comment is probably a bit too negative- I really like your ideas (especially the Option type). Things like Tuple<T1,T2,T3> etc. make me wonder if we would not all be better off though if we just started using F#... although I still like C#.
- louthy 12y ago> I realise my comment is probably a bit too negative Not at all, you're entitled to your opinion. Whatever works for you :) > we would not all be better off though if we just started using F#... although I still like C# That would be nice. Unfortunately it's not always quite as easy as that. At least in my corner of the world!
- 12y ago
- dstone16321 12y agoFeel free to pilfer some ideas from my own take on a similar lib for C#. https://github.com/danstone/lambit https://github.com/danstone/lambit - includes some basic pattern matching support for example. This sort of library is kind of necessary I feel for things like poor tuple support at least. I'm not entirely sure about the casing conventions. I mostly code in clojure/haskell/f# but 'when in rome'. Its likely the sort of thing that will turn off a lot of stubborn developers.
- louthy 12y agoYeah I've implemented a similar pattern matching library before (not public though). I always felt the syntax to be a bit 'bulky' because there were always two lambdas, one for the predicate and one for the handler. I like your take on list matching though, I think that's excellent and much more usable. Not 100% sure how it could be adapted in a general way to the C# Tuple though; because Tuple<A,B> is a different type to Tuple<A,B,C>. So you could only ever match on one type (if you don't take the bulky predicate route that is).
- dstone16321 12y agoList matching tends to be fairly useful even its very limited form in my lib. Its basically arity match + de-structure. It doesn't nest like full fat pattern matching. I don't see how tuple matching is as useful (over your 'With' fn or my 'Apply') unless it can nest, especially with matches on union types. e.g (Maybe a, b) | (Nothing, b) Basically I feel like such a thing is going to be very hard in C# to achieve with any sort of usable api. I personally took the route of the api being more important than whether any sort of decent abstraction is actually there behind the scenes - I didn't mind writing 100's lines of boilerplate to get the result and behaviour I wanted. Its great to see more functional support for C# from the community and I look forward to using your lib in the future!
- danbruc 12y agoI hope this does not come across to negative, definitely a nice project, but is this really useful in practice? If you want F# or Haskell syntax, why not just use F# or Haskell? Why bent C# until it looks like something that already exists? If you work together with (pure) C# developers, you will just confuse them. If you work together with Haskell or F# developers, you can just use that languages. If this is just a fun project, scratch what I said, I love building useless stuff myself, but if it is intended to be used seriously I don't get the point.
- BigChiefSmokem 12y agoThe importance lies in that C# is becoming one of the first truly, for lack of a better word, "programmable" programming languages. The One Language to rule them all? Maybe not, but definitely making incredible progress.
- bbcbasic 12y agoI wonder what the impact is on performance of routinely wrapping your types in Option<T>? I am assuming it is minimal, because the struct would remain on the stack, and the object on the heap. There is just an unwrapping and wrapping cost but no more than Nullable? However this may be a naive view. I like the syntax though. Where I work we use Code Contracts. This reduces bugs due to nulls but sometimes 25% of the code is Contract.XXX(...) which is annoying to read. And more typing too.
- louthy 12y agoAs you say the overhead should be similar to Nullable. And the struct should be allocated on the stack, but the CLR doesn't guarantee that, so it can be on the heap too. There is however an 'if' in the constructor of Option<T>, so there's a little extra potential branching overhead. For most scenarios I suspect this is all moot, but if you're looking for the absolute maximum performance then you would probably want to do some profiling before committing too heavily.
- MichaelGG 12y agoThe CLR has suboptimal codegen for structs, so there is likely an impact.
- rukugu 12y agoI was thinking of writing something like this myself. This looks great!
- louthy 12y agoDue to popular demand, there's now a NuGet package. My first attempt at a NuGet package, so let me know if I've messed something up: https://www.nuget.org/packages/LanguageExt/0.0.1-beta https://www.nuget.org/packages/LanguageExt/0.0.1-beta
- louthy 12y agoThis is the general link, rather than directly to a version. https://www.nuget.org/packages/LanguageExt https://www.nuget.org/packages/LanguageExt
- MichaelGG 12y agoCoercing Some (null) to None seems like a bug. Null is a valid value in the type and should be allowed (F# allows it too). Otherwise, a function might accidentally return None and cause a bug. For instance, a function that looks up s user and returns the name. The name might have null as a valid option. Some(null) indicates user found with no name. None indicates user not found.
- louthy 12y agoI hear what you're saying, but I felt that the reason for using Option in C# would be explicitly to deny nulls. If you are OK with accepting null, don't use Option and return null. I think that's slightly different with F# where you mostly deal with value types that definitely have values. It's rare that you need to pull null out of the bag (unless interop'ing with .NET libs). I understand why you might call it a bug, but I think I make the reasoning clear in the documentation. So I hope it's unlikely to cause confusion.
- hjnilsson 12y agoI feel like throwing a NullReferenceException would be the more appropriate way to deal with it. Either treat Some(null) as a valid value, or treat it as an error. Silently coercing it will lead to problems down the line, like with the Some(Some(null)) case mentioned.
- louthy 12y agoI'm definitely starting to lean that way. Thanks for the feedback.
- lomnakkus 12y agoThe proper way to do this is to have something like in Scala where you say Option(foo) if you want "foo" coerced to either Some(...) or None based on its nullity. It doesn't prevent you from using Some(null) if that's what you really mean. (Though I think it might be a reasonable idea to actually throw an error in such a case. However, there should be a way to construct a Some(null), perhaps with a name that suggests that it's not recommended. Perhaps something with "unsafe" or "unguarded" in the name.)
- Strilanc 12y agoA comment on the option type: I think transforming Some(null) into None is wrong. - There is an x for which Some(x) is not a Some. This violates useful invariants. - Some((int?)1) is a Some, but Some((object)(int?)1) is a None. - optionValue.Cast<object>().Cast<T>() is not the identity function. - I can't use your option type in existing code without doing careful null analysis. - As a rule of thumb, generic code you should treat null as just another instance of T with no special treatment (beyond the acrobatics to make the code not throw an exception). That way both users enforcing a non-null constraint and users allowing nulls can use your type.
- louthy 12y agoJust replied to MichaelGG on the same subject [1]. I understand what you're getting at. I'm not particularly looking to create an algebraically perfect type. The explicit reason for this is to kill null as a possible output from a method, so that the consuming code only has Some or None to deal with. If you want to return null, don't use Option<T>. [1] https://news.ycombinator.com/item?id=8634015 https://news.ycombinator.com/item?id=8634015
- MichaelGG 12y agoIt only has Some or None to deal with. Some<T> or None. Also, do you take it all the way? By the current logic, Some(Some(null)) should also return None.
- dragonwriter 12y agoIf Some(null)is None, then shouldn't Some(Some(null)) be Some(None)? But I'd agree that Some(null) should just be Some(null) and not magically become None. Now, there should be a a generic function something like NullableToOptional<T> that returns an Option<T> by mapping any non-null value v to Some(v) and null to None, but it shouldn't be the core behavior, because you should be able to wrap a nullable value in an option.
- louthy 12y ago
- TazeTSchnitzel 12y agoThat definition of cons() angers me. It's only useful for constructing lists... not for constructing tuples and trees :(
- CmonDev 12y agoThis is a really cool exploration project, but hopefully I will not see this in any production code. Way too un-idiomatic.
- NKCSS 12y agoI didn't realy get the tuple part, if you want named properties, you can always just do new { Name = "John", Age = 29 }; and you're there; no need for tuples and wrapping, etc.
- louthy 12y agoYou can't return anonymous types from functions.