+ not virtual by default (so base classes can be changed more easily without breaking/recompiling downstream clients that the base class writer doesn't know about; specifying something as "virtual" should be a deliberate conscious decision by the class author)
+ value types (for speed, because J Gosling's idea that "_everything_ is an object is 'simpler' for programmers" has a cost -- the boxing & unboxing, and inefficient cache-unfriendly pointer-chasing containers)
+ no checked exceptions (checked exceptions have benefits in theory but real-world practice shows that it forces programmers to copy-paste mindless boilerplate to satisfy the checked-constraint)
+ unsigned types (very handy for P/Invoke API boundaries because legacy Win32 has unsigned params everywhere; yes, yes, Gosling said that unsigned types are confusing and dangerous but nevertheless, they are still very useful)
+ many other examples
This doesn't mean that Anders Hejlsberg's C# language team was smarter than J Gosling's Java team. They simply had 7 years to observe how Java programmers (mis)used the language and therefore, could correct some design mistakes.
Nevertheless, C# still made some dubious decisions such as renaming "finalizers" to "destructors". Java had the better name and C# should have kept the original terminology.
As languages like Rust or Swift demonstrate, the issue is less the checked exceptions and more the abject lack of support for them in the language and type system, which ends up making them essentially unusable.
> Gosling said that unsigned types are confusing and dangerous
He is right of course, but he forgets that so are signed types if they're bounded.
If Java had unbounded signed integers (à la Python or Erlang) that'd be one thing, but Java does not, and neither does it have Pascal-style restrictions/user-defined integral bounds, which means it's no less confusing or dangerous, it's just more limited.
The kind of exceptions that unwind the stack until some part of the code up the stack catches the exception.
They both handle errors by returning error values, kind of like Go.
Rust has a try! macro, which might make you think it's try/catch equivalent, but it's not. It's just a syntactic sugar over error values.
Similarly in Swift try/catch/throw is just syntactic sugar for handling error values.
https://doc.rust-lang.org/book/error-handling.html
https://developer.apple.com/library/content/documentation/Sw...
Having to think more about edge-cases makes the code more dangerous:
for(uint i = arr.len()-1; i >= 0; i--) {...}The lack of unsigned types in Java is a constant pain when doing cross platform development, or when trying to consume (or write out!) a binary wire format.
It can be worked around, but it is just needlessly stupid.
David Bacon's Kava's approach to lightweight objects remains the correct answer and is sorely missed.
The only case for unchecked exception is mitigating terrible design choices. Like using Spring or Hibernate/JPA. When the client code can't do anything with a caught exception, it shouldn't even be thrown. Meaning, they're doing it wrong.
The omission of signed integers has caused me unnecessary pain a few times. Original bindings for OpenGL, a DICOM parser, etc.
--
Properties are the one C# feature sorely missed in Java. JavaBeans is silly.
Syntactic sugar for reflection would be nice, maybe something like 'Method m = myClass#myMethod( String )'.
Java has done plenty of terrible things, crimes against productivity and design rationality. Chiefly annotations, lambdas, and the silly Optional. C#'s equivalent quixotic fever dream is probably LINQ.
I have a laundry list for Java, JDK (especially), and JVM. Who doesn't? Mostly undoing bad ideas and culling deprecated stuff, plus some sugar to reduce the verbosity.
--
Unfortunately, Java's language is hard to separate from its culture. The taint of all the enterprise minded stuff (XML XSD etc, J2EE, Spring, excess indirection caused by design pattern newbies) has been hard to shake off.
What? LINQ is terrific.
Maybe the weird query-style syntax.
If you use it properly, it's no different than map, fold, filter, etc that are ubiquitous functional higher-order functions., just with T-SQL-ly names.
Linq is so much better than doing all of what it does by hand, with for loops and temporaries galore, the way we once did.
Maybe guys like Gosling and Pike should take note. A touch more complexity in the right places can make a huge difference.
(1) that's an exaggeration; MFC used comments to identify sections that it owned in the source, but in my (limited) experience, there typically were zillions of ways to make changes to your code, but if you didn't use the one Microsoft picked as _the_ way (and which they didn't push into your face in the IDE), you were in for heaps of problems.
It's not just language features that made C# an improvement over Java, either. CIL is a fair bit more elegant than JVM bytecode. JVM bytecode has type-specific operations to speed up bytecode interpreters, which turns out to be irrelevant since nobody cares about bytecode performance these days.
This might be my own confirmation bias, but this is my takeaway: point of view and the constraints that you see or believe are there are the main determinant of choices, and not whether some particular pattern or feature of a language is intrinsically good.
The older I get and the more code I write, the more I find this to be true. I change my own mind about things I used to argue fiercely over and things I thought were tautologically true.
I think (hope) this is making me more open minded as I go, more willing to listen to opposing points of view, and more able to ask questions about what someone else's constraints are rather than debating about the results. But, who knows, I might be wrong.
Pointing back at yourself over 10 years is pointing to a different place, sure, but it is also a different person.
Static typing is useful, especially in large projects, if it provides the right guarantees.
OTOH, whether it does that depends on the type system. Go, Java, Pony, Rust, and Haskell are all static, but their type systems offer very different capacities. If you have a type system that has a lot of ceremony, and fails to provide the guarantees that are actually needed in your project, it's a pure burden. If it's low-ceremony and provides guarantees important to your project, it's a clear, undiluted benefit. Reality often falls somewhere in between.
If you are writing a small one time use script to accomplish a task clearly this that kind of protection is of low value.
If you are trying to write or maintain a system intended to last 20 years and keeps bugs out of 100 millions lines of code, every kind of check that can be automated has extremely high value.
Most projects are somewhere between these two extremes. The nature of the cutoff point where strong static typing helps or does not is what we should be debating, not its inherent value as Dahart suggested.
While you may know a lot about what the language is, how it works, and accepted ways of using it, your opinions on how to do things will always be evolving (hopefully).
Sometimes all the experts who use a language will be behind the times. For a long time experts championed strong OO design. Now all the experts champion hybrid OO/FP style things (witness Java 8!).
This too shall pass, and we should have the humility to realize that no-one knows for certain what will be the next evolution of software development.
Because classes aren't real objects and therefore not necessarily also instances of other classes (their metaclasses) as they would be in Smalltalk, there is no class-side equivalent of "self/this," nor of "super." In effect, you cannot write static (class) methods that call other static methods without explicitly referencing the classes on which those other methods are defined, completely breaking class-side inheritance and rendering class behavior (and instance creation in particular) needlessly brittle.
I believe the explosion of factories, abstract factories, and just generally over-engineered object construction and initialization schemes in Java and C# would have been side-stepped if both languages had always had a proper metaclass hierarchy paralleling the regular class hierarchy, as well as some form of local type inference.
http://insightfullogic.com/2014/May/12/fast-and-megamorphic-...
‡ I've used non-OO languages, but never an OO language without virtual by default.
At the end of the day, the fastest code is one that doesn't have to run.
HotSpot is an impressive technology but the optimizations it has to do to overcome Java's design really only pay for themselves in most frequently executed code paths and only after some time to gather necessary info to perform the optimizations.
It's ok for long-running server code but not good for, say, short-lived command-line program.
Or to put it differently: a language that has perf-friendly design, like Go, matches Java's speed with 10% of engineering time and resources spent on the compiler and optimizations. Perf friendly design means it has to do 10% of the work to achieve the same end result.
This, more than anything, has dramatically improved the quality of my designs... and made coding fun again.
Immutability and Lambda functions have also had a tremendous impact on my designs.
Is the term Object-Oriented-Programming relevant anymore?
The GoF patterns either need updating or perhaps we're on the verge of calling this hybrid environment something completely different (?)
I picked up on this too; object-oriented design patterns have lost a lot of mindshare over the last ten to fifteen years. There was a time when it seemed like design patterns were taking over the world. We're still living with some of the monstrosities spawned during that era. I wonder if design patterns can ever be rehabilitated.
I like the Java convention, for one thing, because it is one less decision for programmers to make. I've seen many C# programmers who are oblivious to what virtual means.
On top of that arguing that non-virtual by default is worse than virtual by default is completely superfluous. Just add the damn keyword everywhere and you have virtual everywhere. Same for final.
But Java has everything non-final and virtual by default, which sucks badass because both require great care when implementing the method.
Extending code that was not designed to be extended is very common in Java, because you can. Adding final can easily be forgotten. Removing final, which is required in C#, will only be done IF you intended to make that method extendable, same for virtual.
Yes a great gain. Now I need to argue for each final I add to Java classes and methods, because you know, it seems wasteful to add it, while in fact it is crucial, since maybe just 1% of any code I write was meant to be replaceable by a third-party. Mostly, you want to use other mechanism for extension, like decoration & composition.
If it took ten years to learn that falsehood (non-virtual is worse than virtual by default), then we talk about one hell of a regression huh.
If you aggressively use all the syntactic sugar from the latest version of C# and compare that to similarly up-to-date Java code, they'll be worlds apart. The C# will look terse, and to more conservative programmers, rather weird.
[1] https://github.com/dotnet/roslyn/issues/118
[2] https://github.com/dotnet/corefxlab/blob/master/docs/specs/s...
Also, doesn't C# have the ability to limit (or pretty much disable) GC?
Just be a good citizen and don't trash like crazy and the GC will never block in the action phase.
Also, don't use the default mono GC, that one is terrible.
That's a bit harsh. "Factory" is a term that became prominent in Java as a result of the language design decision not to include first-class functions, so any time you see "Factory" just think "function that returns an object", and any time you see "AbstractFactory", think, "type of function that returns an object". In C# you can just use delegates and the explosion of factories isn't really there.
I'd say your opinion of this explosion might change if you work in a good codebase which makes sensible use of techniques like IoC. Yes, it feels a bit silly to have a component in your project which does nothing more than instantiate objects, but you end up with classes that are much more cleanly defined in terms of the interfaces they expose and consume, and you can write unit tests that don't make you feel like you're damaging your code base to get the unit test to work.
At least, when it goes well.
My experience with metaclass programming (a fair bit of Python metaclass programming) is that it can often be replaced by generics, reflection, or various code generation tricks in C#, and I don't end up missing metaclass programming that much. Metaclass programming isn't a silver bullet, it's a tool that complements other tools in the right toolbox (Python, Smalltalk) but would just get in the way in other toolboxes (C#, Go).
There's a narrative here that we're somehow "neglecting" the lessons we learned with old systems like Smalltalk, Lisp, etc. when we make languages. It's a seductive narrative but I think it's mostly papering over the sentiment that language X isn't like my favorite language, Y, and therefore it's bad. I welcome the proliferation of different programming paradigms, and besides a few obvious features (control structures, algebraic notation for math) there are few features that make sense in every language. That especially includes metaprogramming, generics, reflection, macros, and templates.
The client just knows that it has an EncryptionFactory which makes some kind of stream and some kind of key, which are compatible.
AbstractFactory doesn't specifically address indirect construction or indirect use of a class, but it does solve a problem that can also be addressed with metaclasses. If we can just hold a tuple of classes, and ask each one to make an instance, then that kind of makes AbstractFactory go away.
The thing is that in a language like Java, these factories have rigid methods with rigid type signatures. The MakeKey of an EncryptionFactory will typically take the same parameters for all key types. The client doesn't know which kind of stream and key it is using, and uses the factory to make them all in the same way, using the same constructor parameters (which are tailored to the domain through the EncryptionFactory base/interface).
If we have a class as a first class object (such as an instance of a metaclass), that usually goes hand in hand with having a generic construction mechanism. For instance, in Common Lisp, constructor parameters are represented as keyword arguments (a de facto property list). That bootstraps from dynamic typing. All object construction is done with the same generic function in Common Lisp, the generic function make-instance. Thus all constructors effectively have the same type signature.
Without solving the problem of how to nicely have generic constructors, simply adding metaclasses to Java would be pointless. This is possibly a big part of the reason why the feature is absent.
If this was in fact avoidable, it is a sad fact.
Class constructors tend to exhibit a variety of type signature. Even when objects are derived from the same base type and substituted for each other, the way they are constructed can be quite different. In a dynamic language, we can handle all construction with the same kind of function: something that takes a "property list". Because of that, we can have a "virtual constructor". That can be a method on a meta-class to create an instance, or just something built in: some make-new-object function which takes a type and a list of generic constructor arguments that any type can handle. This is very easy to indirect upon.
Adding meta-classes in Java wouldn't solve the problem of how to make construction generic.
Source: https://blogs.msdn.microsoft.com/ericgu/2008/07/02/why-does-...
So you do agree that signed bounded types are dangerous, and it's only a matter of degrees between them and unsigned. Thank you.
> If we can just hold a tuple of classes, and ask each one to make an instance, then that kind of makes AbstractFactory go away.
That seems like just one particular way to solve things. I guess I don't see what the fuss is about, if we are talking about metaclasses in particular, because we could also solve this problem with generics, and the factory solution doesn't seem that bad to begin with.
> Thus all constructors effectively have the same type signature.
Or turned around, the type system is not expressive enough to assign different types to different constructors, and is incapable of distinguishing them. This matches with my general experience, that metaclasses are useful on the dynamic typing side (Python, Lisp, Smalltalk, JavaScript) but annoying on the static typing side (C++, Haskell, C#).
But of course that makes sense. In a system without static types, the only way to pass a class to a function is through its parameters, so you have to pass the class by value. In systems with static typing, you have the additional option of passing a class through a type parameter, which has the advantage of giving you access to compile-time type checking. Furthermore, there are real theoretical problems with constructing type systems which allow you to use metaclasses involving whether the type checker is sound and whether it will terminate.
"C# heap allocations aren't expensive and are faster than C/C++. They are even faster than stack allocations (if the stack alloc is too large to be a register) as the stack needs to zero out the memory first; whereas the heap has done this ahead of time. However, they do come at a cost, which is deallocation.
C/C++ have both an allocation and deallocation cost. The main advantage is you know when you are paying that deallocation cost; the main disadvantage is you often do it at the wrong time or don't do it at all which leads to memory leaks, using unallocated memory or worse trashing something else's memory (as it has been reallocated).
The GC isn't the bad guy; its the liver and blaming it is like blaming a hangover on the liver.
However; the free drinks the clr allocation bar gives you are very tempting, and does mean there is a tendency to over indulge so the hangovers can be quite bad... (GC pauses)."
[1] https://github.com/dotnet/roslyn/issues/10378#issuecomment-2...
Source: https://www.fstar-lang.org
Btw, i kinda like java's Anonymous Classes. Do we know any reason for C# not to adopt this as well?
Because delegates are a much easier way of dealing with things? Especially with lambda functions. Unless you're talking about the java pattern of passing an entire anonymous class for, let's say, a formatter or something. The reason for C# not to adopt that seems to be that:
- It's so terribly awful, who in his right mind is happy to implement yet another anonymous class? - API design is different and .NET manages to avoid the need of those quite gracefully.
They pretty much never phase anything out because they're serious about backwards compatibility (which frankly I consider refreshing in this world). I mean they've even said at some point like "yeah, the delegates are unfortunate because they're just a more awkward syntax for what the lambda functions do but we're not going to get rid of them because people have used them."
The crux of my argument was the larger and more complex the work the more important it is to find errors early. It seems obvious to me that languages like Java, C++ and Rust do much more to catch errors early than languages like Ruby, Python and Javascript which are easier to get started with and make a minimum viable product. Put those two things together and it seems like strong heuristic to use when starting a project.
Maybe a few type errors will still slip by, but you'll have found and fixed so many other kinds of errors much earlier. Kinds of errors that benefit by being caught immediately instead of festering because they passed a type checker. (I've never really found a type error to be a catastrophic-oh-I-wished-we-found-this-sooner type of bug. You fix it and move on. It's not dissimilar to fixing various null pointer exceptions that plague lots of corporate Java code.)
To me your obvious claim is not obvious at all, because the tradeoff space is so much richer than what mere type systems allow. We're not even touching on what you can do with specs and other processes that happen before you code in any language, nor other language tradeoffs like immutability, everything-is-a-value, various language features (recalling Java only recently got streams and lambdas), expressiveness (when your code is basically pseudocode without much ceremony (or even better when you can make a DSL) there's a lot fewer places for bugs to hide)... Typing just doesn't tell that much of a story.
Players notice when the framerate drops. Presuming a pretty typical 60 Frames Per Second you have a tight 16.6ms time budget to do all of the work for the entire frame. All of the physics, all of the sound processing, all of the AI and everything else needs to be sliced up into little bits that can be distributed across the time the game is played.
There are many good ways to achieve this, and many ways that just appear good until someone else plays your game. If you allocate dynamically even occasionally you need a strategy to allocated about as much as you deallocate each frame or otherwise mitigate the costs.
C++ has this problem completely solved with strong deterministic semantics between destructors, smart pointers and allocators. This can be handled in C# a few ways as well, but sometimes a bunch of unallocated memory builds up and is cleaned between level loads or during downtime. When the Frame rate drops because the garbage collector consumes 1 of the 2 hardware threads in the middle of a firefight players get mad. If you only ever tested on your nice shiny i7 with 8 hardware threads you might never notice until a bug report lands in your inbox. That presumes it wasn't one of the stop the world collections and you couldn't use that last hardware thread better than the GC, both of which negate GC altogether.
Done right deterministic resource allocation costs almost nothing. You can get to zero runtime cost and nearly zero extra dev time. In practice a little runtime cost is fine, and a little time spent learning is OK, but a bug report in the final hour before shipping that the frame rate drops on some supported hardware setups but not others is really scary.
In practice, most games don't have entirely reliable performance, particularly on low-end hardware.
Drop a frame in a competitive First Person Shooter and be ready for death threats.
Drop a frame in an angry birds clone and be ready for 5 stars in a review just because you made your first game.
I suspect you could could get away with quite a bit of GC in most games. But by the time you learn whether or not you could get away with you have fully committed to language for several months. Unless you fully committed to D you are stuck with your memory management strategy. In order to be risk averse game devs dodge GC languages entirely because the benefit is small compared to the potential gain. Combine this with how everyone wants to make the next super great-<insert genre here> MMO that will blow everyone away, they think that they must squeeze every drop of perf out of the machine and sometimes they are right.
Lua is hugely popular for scripting in games. World of Warcraft used it to script the UI. Its garbage collector can be invoked in steps. You can tell it to get all the garbage or just to get N units of garbage. If you tell it to get 1 unit of garbage each frame while frugally allocating I expect you could easily meet the demands of many casual games.
Then there are games like Kerbal Space Program. All C# and all crazy inconsistent with performance. It will pause for no apparent reason right as you try to extend your lander legs and cause you to wreck your only engine on a faraway planet. I cannot say with certainty it is GC, but that cannot be helping.
Historically, many game engines have a "never allocate" policy: Everything is done with static buffers and arenas of temporary data. The broad strokes of this policy are mostly achievable in a GC language that allows value type collections(fewer managed references = less to trace = cheaper garbage). The problem typically comes in little bits of algorithmic code that need their own allocation: Because the language is garbage collected, all your algorithms are using the GC by default. And if you want to reclaim it, you have to fight a very uphill battle.
IME, though, most of the problem is resolvable if you have enough introspection into GC configuration. If a game can tell the collector that it needs a cheap scratch buffer to process an update loop and then get thrown out, that covers a lot of the problem. The last bit of it is fine detail over memory size and alignment, which some GC languages do give you introspection into already.
Edit: Also, I should note that the relative value of GC changes a lot when your process is long-lived and unbounded in scale(servers) or involves a lot of "compiler-like" behaviors(transformations over a large, complex data graph). The advantage of doing without it in a game engine has a lot to do with the game being able to be tuned around simple processing of previously authored data, with limited bounds in all directions.
While you could use the support for them to split human-managed code across separate source files, that would be a horrible practice that I've never encountered in the wild, even in .NET shops with otherwise-atrocious practices and code quality.
To continue that type of advice, some say "#region/#endregion" is another language feature that's intended for code generators so that the IDE can collapse specific lines of code and hide it from view. Programmers should not be hand-coding "#region" themselves. That said, there is debate on that: https://softwareengineering.stackexchange.com/questions/5308...
Like others have said, it makes working with generated code easier.
lol
The namespace keyword is used to declare a scope that contains a set of related objects. You can use a namespace to organize code elements and to create globally unique types.
And about partial classes:
There are several situations when splitting a class definition is desirable: When working on large projects, spreading a class over separate files enables multiple programmers to work on it at the same time. When working with automatically generated source, code can be added to the class without having to recreate the source file. Visual Studio uses this approach when it creates Windows Forms, Web service wrapper code, and so on. You can create code that uses these classes without having to modify the file created by Visual Studio.
As you can see partial classes are not the right tool to organise code.
Also, "regardless of the use case" ? I barely know how to respond to that. Have a little imagination.
The comparison to Go is very misleading. Neither Swift nor Rust require you to return a value on error like Go does, nor do they allow you to simply ignore errors. The semantics, not the implementation, is what matters.
Swift's error handling is not "syntax sugar." try/catch in Swift are not macros that desugar into normal Swift. Errors are not returned via the normal return path, but via a dedicated register. Just like stack-unwinding exceptions, Swift errors are part of the core ABI.
https://github.com/apple/swift/blob/master/docs/ABIStability...
But also just to display traces on asserts/panics, even if not "unwound" per se.
The point is that both of them have generic error types which "infect" every caller (transitively) in much the way checked exceptions do.
That Rust and Swift require explicitly bubbling error values should be a point in favour of checked exceptions.
I thought the explicit bubbling was the nicest thing about error handling in Rust. It's usually just a single character ('?') that the editor can easily highlight, and nicely indicates where the operations are that might fail.
I'd add that checked exceptions don't play nicely with general functional/stream operations like "map" which is why Java went with unchecked exceptions for their streams api in Java 8. Rust on the other hand can handle interior failures in such functions nicely, promoting them to a single overall failure easily via collect(), using the blanket FromIterator<Result<A, E>> implementation for Result<V, E>.
And Swift has a `rethrows` marker to transitively do whatever a callback does, it's equivalent to "throws" if the callback throws, and to nothing if it does not. So e.g. `map(_ fn: (A) throws -> B) rethrows` will throw if the callback it is provided throws, and not throw if the callback doesn't throw.
1. The C problem of forgetting to check error returns. Yes, exceptions (checked or not) also avoid this.
2. The C++/Java/C# problem of exceptions being more expensive than you'd like for common error situations. .NET has sprouted alternatives like `bool tryParse(String, out int)` as a workaround, but on balance I prefer the unified mechanism.
What I don't like is how innocuous `unwrap` looks, or how often it appears in example code.
Exceptions should not be used for common error situations! This is the prime mistake of Java which pretty much required exceptions when it should (and checked exceptions to boot).
> .NET has sprouted alternatives like `bool tryParse(String, out int)` as a workaround
I don't consider that a workaround. There is a deep semantic difference between TryParse and Parse. If you see TryParse then you know the data is expected to be invalid. If you see Parse than you know it's expected to be always valid. A good C# program should have very very few try/catch blocks (ideally just one).
In languages with exceptions, the program will crash as soon as you try to use that value (rather than when you try to unwrap it), e.g. the infamous null pointer exception. Copying bad sample code in this case might result in code that is difficult to debug because a null value might be handed off several times before something tries to dereference it.
In languages that expect but do not enforce that you check the validity of the value (like C) you'll just get undefined behaviour that will hopefully cause your program to segfault when you try to use the value, but who knows what will actually happen? Copying bad sample code in this case will cause a security vulnerability.
Copying "bad" sample rust code (using unwrap) will cause a safe crash with maximum locality, for simpler debugging.
Rust panics result in unwinding the stack:
https://doc.rust-lang.org/nomicon/unwinding.html
try!/? is preferred for handling errors, but if you "know" that a Result or an Option has a value, you can unwrap() or expect() it, and you'll get a panic if you're wrong.
In both languages panic() is for "this shouldn't happen" fatal errors, not for signaling errors to the caller, the way Java or C# use exceptions.
My comment was in response to person basically saying "checked exceptions in Java would be good if only they implemented it the way Swift/Rust did".
Cast a large double to an int -- crash. How do you catch that error? You can't. You have to make sure it never happens yourself. The UserDefaults is another great one. All sorts of ways that it can crash your app, none of them can be caught or handled. Your app just crashes. My advice: convert all your object to a text string (like json) and store that. Do NOT store a dictionary representation.
Having spent extensive time with checked and unchecked exceptions I find Rust's error model to be very robust.
There's extensive tooling to handle and transform them, most of the pain comes from developer who are used to sweeping them under the rug(which totally makes sense in prototype land but when I'm shipping something I want guarantees).
All expressive languages whose type systems don't have a 1-to-1 equivalence in the runtime's type system need to employ some degree of erasure.
The distinction between "erasure" and "no erasure" doesn't make much sense. It's always just about more (CLR 1, JVM < 10) or less (CLR >= 2, JVM >= 10) erasure.
> It's harder then it looks, because .NET has a very rich type system which happens to be not the Scala type system. In fact, it's very hard to map some of the parts of the Scala types like higher-kinded types...so it's a difficult task, much more difficult than JS or LLVM. LLVM is likely going to be the third target [after JVM and JS] because there we don't need to fight a type system which is foreign to us, which is what the .NET one is.
Where does the absolutism in the tech industry come from? We are a bunch of individuals who have individual experience and then try to form a view of the world that satisfies our experiences. What about the experiences you haven't had or conceived of? We are constantly rewriting the rules in our head to fit the new experiences we have every day to make sure we are right all of the time. Surely, our current world views are not complete or we would have no room to grow.
Still, I'll take your comment under advisement in case my classes are big, poorly designed non-Tries.
I have a minor doubt that I won't like the additional level of nesting incurred, but I'll attempt it regardless.
Sure, but I don't think that splitting every possibly-failing API call into throwing and non-throwing forms is the right way to express that difference, and a lot of the time it'll be a matter of context (meaning that the implementation can't magically do the right thing).
It's fairly easy to layer throwing behaviour on top of nonthrowing in a generic and efficient way (Rust's Option, Java's Optional etc), but the reverse is not true.
I must admit I'm losing track of what if anything we're in disagreement about, though...
But having both a Parse and TryParse means that I can ignore the result of the Parse call entirely and let it fall through to the exception handler. It is by-definition always expected to succeed so when it doesn't then that's a bug. If you only have one of TryParse or Parse you cannot judge the intention.
Sure you can. To take Java as an example, if you only have one parse method and it returns an `Optional`, you can indicate the intention by whether or not you call `get` directly or call something like `isPresent` or `orElse` first/instead. Yes, you can get that wrong, but you can get the choice between `TryParse` and `Parse` just as wrong.