HN user

zoomerang

201 karma
Posts1
Comments67
View on HN

Haskell functions return side-effects using the IO type, with the boilerplate plumbing being hidden with monads and do-notation. "main" in Haskell by default has a return type of "IO ()", and any "IO" values returned by that function are executed by the runtime.

The end result in this case is something that just looks and feels completely imperative.

but if you were to try and call, say, the "rmdir" function inside another function that didn't have an IO return type, you'd get a compile error. (More specifically, you could technically call the function, you just couldn't return the "IO" value as a result, so it couldn't perform any actions).

Typescript is really starting to look quite compelling. If we could have null-safety and ADTs I'd be using it in a heartbeat.

Really, the only reason Javascripts future looks very bright is because it has a monopoly - it's the only language that runs in the browser.

Javascript is a great language for what it is, and ES6 fixes a lot of the warts - but it's still a rather mediocre language compared to many of the other options out there.

It's a great quick-n-dirty language for hacking things together, but it's less ideal for more significant projects. (You certainly can maintain large projects in Javascript, but there are much better languages for this task).

ASM.js will soon make V8 _much_ faster

ASM.js is not Javascript, and it won't make Javascript run any faster. It's a compile target that happens to resemble a syntactically valid subset of Javascript for backwards compatibility purposes.

ASM.js can only really be used as a compile target for unmanaged languages, and acts as a bytecode that compiles down to pure assembly. It's not something that any Javascript developer would write by hand.

I can remember this switch back in the day, but for some reason my memory has failed me. I could have sworn the original was in Haskell, not Lisp.

Selective memory I guess.

As somebody with more experience with Haskell and Scala than Clojure - how to Transducers differ from Functors in Haskell?

On the surface they seem very similar.

Out of curiosity, what's difficult about that mutable array implementation?

I'm a relative Haskell novice, but was able to write some mutable array code with only a cursory read through the documentation.

Granted it's extremely verbose compared to most imperative languages.

First up - I'll preface my reply below with a big disclaimer that I'm a relative notice with Haskell, so these are purely my opinions at this point in my learning curve.

What's "it" - Haskell, or referential transparency? Referential transparency definitely has its victims, and debugging is one of them. Debug.Trace is quite useful, and also violates referential transparency. That Haskell provides it is an admission that strict R.T. is unworkable.

I'd disagree that this is any real attack on the merits of referential transparency, since Debug.Trace is not part of application code. It violates referential transparency in the same way an external debugger would. It's an out of band debugging tool that doesn't make it into production.

Baloney! Haskell's laziness makes the order of execution highly counter-intuitive. Consider

I wouldn't say it makes order of execution highly counter-intuitive, and your above example is pretty intuitive to me. But expanding your point, time and space complexity can be very difficult to reason about - so I'll concede that's really a broader version of your point.

Haskell returns a new pointer to a buffer, while other versions need to copy into a new buffer? This is nonsense.

C uses null-terminated strings, so it order to extract a substring it must be copied. It also has mutable strings, so standard library functions would need to copy even if the string were properly bounded.

Java using bounded strings, but still doesn't share characters. If you extract a substring, you're getting another copy in memory.

Haskell, using the default ByteString implementation, can do a 'substring' in O(1) time. This alone was probably a large part of the reason Haskell came out ahead - it wasn't computing faster, it was doing less.

Obviously in Java and C you could write logic around byte arrays directly, but this point was for a naive implementation, not a tuned version.

This would seem to imply that Haskell will "read ahead" from a file. Haskell does not do that

It would seem counter-intuitive that the standard library would read one byte at a time. I would put money on the standard file operations buffering more data than needed - and if they didn't, the OS absolutely would.

Like laziness, immutability is almost always a performance loss.

On immutability -

In a write-heavy algorithm, absolutely. Even Haskell provides mutable data structures for this very reason.

But in a read-heavy algorithm (Such as my example above) immutability allows us to make assumptions about the data - such as the fact that i'll never change. This means that the standard platform library can, for example, implement substring in O(1) time complexity instead of having to make a defensive copy of the relevant data (Lest something else modify it).

On Laziness -

I'm still relatively fresh to getting my head around laziness, so take this with a grain of salt. But my understanding, from what I've been told and from some personal experience:

In completely CPU bound code, laziness is likely going to be a slowdown. But laziness can be also make it easier to write code in ways that would be difficult in strict languages, which can lead to faster algorithms with the same effort. In this particular example, it was much easier to write this code using streaming non-blocking IO that it would be in C

It is not especially difficult to write a referentially transparent function in C. Haskell gives you more confidence that you have done it right, but that measures correctness, not performance.

Except that GHC can do some clever optimizations with referential transparency that a C compiler (probably) wouldn't - such as running naively written code over multiple cores.

When it comes to parallelization, it's all about tuning. Haskell gets you part of the way there, but you need more control to achieve the maximum performance that your hardware is capable of. In that sense, Haskell is something like Matlab: a powerful prototyping tool, but you'll run into its limits.

I completely agree. If you need bare to the metal performance, then carefully crafted C is likely to still be the king of the hill for a very long time. Haskell won't even come close.

But in day to day code, we tend to not micro-optimize everything. We tend to just write the most straight forward code and leave it at that. Haskell, from my experience so far, for the kinds of workloads I'm giving it (IO Bound crud apps, mostly) tends to provide surprisingly performant code under these conditions. I'm under no illusion that it would even come close to C if it came down to finely tuning something however.

No specific reason really. I didn't think about it at the time, that's just how I typed it.

Probably because C is a single letter, and thus potentially needs some differentiation from the surrounding sentence, whereas Haskell is an actual word. But no idea really.

that is altar upon which you sacrifice the abilitity

I see statements like this all the time from people that either fundamentally misunderstand Haskell, and use to have the same misunderstandings myself. You really don't sacrifice anything by using it.

the abilitity to write print statements to do debugging

I can slap a `trace` statement wherever the fuck I want inside my Haskell code for debugging. Even inside a pure function, no IO monad required. If I want to add a logger to my code, a 'Writer' monad is almost completely transparent, or I can cheat and use unsafePerformIO.

and lose the ability to reason about order of execution.

If I'm writing pure code, then order of execution is irrelevant. It simply does not matter. If I'm writing impure code, then I encode order of execution by writing imperative (looking) code using do-notation, and it looks and works just like it would in any imperative language.

But does it really result in more performant code

Haskell has really surprised me with its performance. I've only really been using it for a short time, having been on the Java bandwagon for a long time.

One example I had recently involved loading some data from disk, doing some transforms, and spitting out a summary. For shits and giggles, we wrote a few different implementations to compare.

Haskell won, even beating the reference 'C' implementation that we thought would have been the benchmark with which to measure everything else, and the Java version we thought we'd be using in production.

Turns out that laziness, immutability, and referential transparency really helped this particular case.

- Laziness meant that a naively written algorithm was able to stream the data from disk and process it concurrently without blocking. Other implementations had separate buffer and process steps (Even if hidden behind BufferedInputStream) that blocked the CPU while loading the next batch of data

- Immutability meant that the Haskell version could extract sections of the buffer for processing just by returning a new ByteString pointer. Other versions needed to copy the entire section into a new buffer, wasting CPU cycles, memory bandwidth, and cache locality.

- Referential transparency meant that we could trivially run this over multiple cores without additional work.

Naturally, a hand-crafted C version would almost certainly be faster than this - but it would have required a lot more effort and a more complex algorithm to do the same thing. (Explicit multi-threading, a non-standard string library, and a lot of juggling to keep the CPU fed with just the right amount of buffer).

On a per-effort basis, Haskell (From my minimal experience) seems to be one of the more performant languages I've ever used. (That is to say, for a given amount of time and effort, Haskell seems to punch well above its weight. At least for the few things I've used it for so far).

I'm still of the impression that well written C (or Java) will thoroughly trounce Haskell overall, but GHC will really surprise you sometimes.

I haven't used OCaml much - but my understanding is that the GIL makes it quite difficult to write performant multi-threaded code, something that Haskell makes almost effortless.

Well yes and no. GHC does a pretty good job of warning you when you're missing a potential option in a pattern match, and it's generally pretty easy to write code that avoids partial functions as a result. But still, you're right - it's not 100% safety, and will only give you a warning.

There's no reason to throw the baby out with the bathwater though, since 90% safety is a hell of a lot better than 0% safety.

Personally I'm keen to see mainstream languages adopt better totality checking for that exact reason - my fantasy language would enforce that `main` is always a total function*

*(For this fantasy language, I'd probably allow infinite recursion to still exist, since the halting problem is theoretically impossible to solve without introducing a lot of pain to prove that your code will actually terminate, and that level of totality checking is often counter-productive for general-purpose code)

The idea that compile-time checks is much better than runtime checks is also not obvious (Java's and other commercial languages nonsense about type safety aside - Java is no more safe than CL).

Runtime checks will only trip up if you happen to hit a code path the introduces an incorrect type. This may only occur in some extremely rare scenario that you never pick up in testing.

Compile time type checking allows you to prove that your program is definitely type safe, with 100% certainty.

Not really. The point of Haskell is not to avoid having side effects. The point of Haskell is to allow code to be referentially transparent - this makes it both easier to reason about as a developer, and easier for the runtime to optimise.

Out of curiosity, have you tried F#?

It's a substantially nicer language than Javascript, and there's a lot of problem domains that are far easier solved in it.

People wouldn't use it because they don't want to learn Javascript. They'd use it because they don't want to use Javascript, on account of knowing better.

"Quick" - sure. HTML5 lets you spin up UIs fairly quickly.

"Powerful" - I'm not so sure. Generally I find native toolkits, while requiring a bit more effort up front, generally yield much nicer user experiences.

For simple apps, HTML5 works fairly well. But you quickly run into problems.

My company has been working on a cross mobile application in HTML5 using Cordova. It was quick to prototype, but as we scaled up we started hitting the limitations of the platform very quickly.

The main issue is that every single mobile device has a slightly different rendering engine. Even two different Android phones from the same manufacturer will behave slightly differently. If you're making a simple CRUD app, this probably won't affect you - but once you reach a moderate level of complexity you spend more time playing whackamole fixing bugs and testing on every possible combination of devices.

We're currently rewriting as pure native across all platforms, since it works out to be less effort in the long run.

This is on top of the obvious limitations of HTML5 - Performance is never quite there for anything other than trivial tech demos, and the lack of native widgets mean your users will have that "uncanny valley" experience where it doesn't quite feel right.

tl;dr HTML5 is ok for prototyping and simple apps, but doesn't scale well. Ditto for Javascript.

Yes. Java can be compiled to native machine code via GCJ, and can expose a normal C++ Api.

Whether you'd want to do this or not is another story. C# and Xamarin would be a far better choice for a one-size-fits-all cross platform codebase.

JavaScript is the only language that let's you target every platform

Ironically, the three languages you listed - Java, C#, and Objective-C are all available on all major platforms. I could write code in any of those languages and have it run on

  - Windows
  - MacOS
  - Linux
  - Android
  - iOS
Furthermore, the apps would all be native. That closest Javascript can come is to use the uncanny-valley of HTML5 without native widgets or performance.

Frankly, Given that the burden of learning a new language is tiny, I see no reason why we should try and cram Javascript into every nook and cranny. It's a fairly mediocre language at best.

tl;dr The three languages you listed are all capable of better cross platform support than Javascript.

My Swift Dilemma 12 years ago

"Shitty" may not be the right word, since C#'s type system works fairly well for its problem domain. The point is that the type systems found in C# and Java are conservative and in many cases overly burdensome for the type-safety benefits you get.

As a result, when compared against purely dynamic languages, the advantages of static typing in Java and C# are not so clear cut (Hence why so many developers just use dynamic languages).

If your only exposure to static typing is in Java or C#, then you're really haven't seen a good type system at work.

My Swift Dilemma 12 years ago

generic is really useful

Container types are the most obvious. Lists, Arrays, Dictionaries, Vectors, Stacks. You'd be hard pressed to find code that doesn't use a container type of some kind.

My Swift Dilemma 12 years ago

Am I the only one agrees with the author that generics probably do more harm than good

It's really the same debate as 'Dynamic' vs 'Static' languages, since without generics your code is relying on runtime type checking in anything remotely complex, thus is effectively a dynamic language.

Personally, I'm of the opinion that people that don't like static typing only feel that way because they've only used the shitty implementations in Java or C#.

Regardless, This is a religious war that has raged for decades, and isn't likely to be answered anytime soon. The answer really comes down to "It Depends". I fall firmly in the 'static typing is good' camp, mainly because I have hard evidence to back up my opinion that it results in significantly fewer defects.

(Specifically, very clear reports from from issue tracking systems showing our defect rate in production dropping by 90% (!!!) when we switched from Groovy to Scala, with a notable increase in productivity).

Some of the developers complained, since they had to learn knew tools. But being professionals, they learned new tools and were better off for it.

Now while I'm an extremist religious zealot about proper static typing being the one true way, I'm quite mindful that, for many developers, that tasks they are working on just aren't complex enough for it to make much of a difference in practice - they are able to test all edge cases and deploy stable software to production, just with a little more runtime testing than they'd otherwise need.

Some languages - such as Go, or pre-enlightenment Java, do not implement Generics, and thus require runtime casting in many cases. In these languages, there's still a degree of compile time checking, just not as thorough as it should be. As with dynamic languages, they can work with no perceived issues for projects up to a certain size and provide a reasonable halfway point. Beyond this, you're going to hit a wall.

As to your argument that Generics do "More harm"? I'd strongly disagree. If you're unfamiliar with the gotchas generics introduce (i.e. Variance can be a mindfuck), then they can seem difficult and problematic. But like any other professional tool, once you've gotten over the learning curve you're more productive with it than without.

tl;dr If I wanted to bang a few pieces of wood together, I'd feel comfortable using a Hammer. the learning curve small, and I can connect those two pieces of wood in no time.

My Uncle is a carpenter. As a professional carpenter, he bangs pieces of wood together all day long, every day, for his entire career. As such, a nailgun is a more appropriate tool. While being more complex to use and having a steeper learning curve, he's a professional, and uses a professional tool to do a professional job. Occasionally he might want to bang some quick project together in his shed, and getting out the nailgun is overkill, so he uses a hammer for the odd thing here and there.

I'm a professional programmer. I use professional tools, even if they have a steeper learning curve and might be more complex. Occasionally I want to whip up a quick script, so will just hack it together in Python.

The problem with raw SQL queries is that they don't compose. Using an ORM, I can do two things.

1. Pass around query objects, and build queries derived from others. I can also combine multiple queries into one and split the results.

2. Update model records in multiple places passing them through several layers of business logic before serializing.

This is on top of the other obvious benefits of ORMs, such as abstraction over my storage engine. I can write a single 'query' that be be executed against a variety of SQL servers, Salesforce, MonoDB, an in-memory cache, or whatever else I want to do with it.

As a real-world example of why this matters - On my current project, I have a heuristic algorithm that allocates pending jobs to workers that meet the required skillset. As part of this, I have individually defined rules that must be met. As part of the operation, each rule can query the database for relevant results.

Each rule is a standalone module, and by design cannot have any knowledge of other rules. Rules can be written by third parties and are plugged in and loaded at runtime. To make this work, we can either

1. Run one query per rule, hitting the database more than needed 2. Compose all the rules into one query, hitting the database once.

Using an ORM, I'm able to take a base query and fold it through all the available rules, allowing each one to return an updated query to be merged. Some rules conflict - they may require results that another rule has filtered out. To solve this, the ORM will automatically detect the conflict and build a single 'Query' object that compiles to a broader query behind the scenes, takes the results, stores them in an in-memory cache, and then runs the individual rules in-memory against them to get a final resultset. In the worst case scenario where this is not possible, it will compile to the minimum possible number of SQL queries to satisfy all of the individual callers.

As a result, each rule can run a different query against the database, getting the individual resultset it wanted, while not hitting the database so heavily.

Why not just query multiple times? In this case, we're running this against a Salesforce database. On top of the fact that you pay per API call, there's anywhere up to 2 seconds of latency before getting a result. Composing the queries means we take an operation that might have taken a few minutes and used a lot of expensive API calls into an operation that takes a few seconds and uses 1 API call.

At the end of this I get a resulting immutable object. I can perform business logic on this in multiple places, accumulating changes, at the end of which I have an `update` object containing an original version and a delta. I can then just update the things that actually need updating, and can run additional business rules intelligently based on which fields were changed. If there are multiple records that need updating, the ORM will batch them for me to further reduce API calls.

Using raw SQL, it would be possible to implement a rough hack that approximates this, but it would be nowhere near as efficient or scalable.

I went into FP from the perspective of a Python/Javascript/Groovy zealot, considered FP to be a silly academic thing with no real world applications.

Took me about a week to be productive in Scala in a functional style. Haskell is a little more complex, but I found that throwing myself in the deep end, while frustrating, was not overly difficult.

The cost of switching to FP only seems high because people don't want to even bother trying. Once you've gotten over the initial bump, FP is (in my opinion) substantially easier to wrote code in.

Why We Use OCaml 12 years ago

Having substantial experience running a startup on Scala (Not OCaml, but many of the same aspects apply) I'm going to throw my 2c in.

There is not a strong community behind OCaml. You can't take advantage of the numerous libraries and references created by the community like you can with Ruby, Python, Javascript, Java, or any of the other mainstream languages.

You'd be surprised how good the community is around FP languages.

OCaml is hard (and so are most functional programming languages)

I'm going to strongly disagree on this one. FP is different to what you're used to. Once you've learned it I actually think FP is substantially easier, especially when maintaining large codebases. The amount of cognitive effort required to write code in an FP style is substantially lower.

You will always need exceptionally talented developers to work on your code base and that will get expensive

I work with Scala, not OCaml personally, but I've been able to train Junior Java devs up on Scala (In a pure-fp style) with minimal effort. In addition, I find there's a very large talent pool of excellent developers wanting to work with FP in a commercial environment, and are willing to work for less for the opportunity.

Hiring has actually been substantially easier since I switched to Scala, and I'm getting better developers for less.

OCaml is a risk. If you're starting a consumer-oriented service, you need to prove there is a market for it. Will OCaml help you get to market faster? I don't know. Will Ruby-on-Rails? Yes. I'd pick RoR any day just to eliminate the risk of getting to market late.

I'm sure people were saying the same thing about PHP vs RoR back on the day.

Any new technology is a risk if you're new to it.

For those that have taken the 'risk' of using an FP language, the payoff has been worth it. From my experience with FP Scala, the go to market time has been substantially quicker than with any other platform I'd used previously. The defect rate has been 90%(!) lower, productivity is higher, the codebase is easier to work on, an it's easier to find top-tier developers.

Mind you I can understand not wanting to bet the farm on it from day one - this is why Scala was an easier choice for us, because we could fall back to Java if we have problems (We didn't).

Monadic futures are a fairly effective solution to the problem.

No callback hell, just a clean linear flow of imperative looking non-blocking code that runs concurrently but still guarantees order of execution.

I can't wait for somebody to hack this and make every Tesla on the planet start honking at the same time. (Presuming there's a 3G hookup of some kind going on here)

Brand spanking new MotoX with Android 4.4 and Chrome. Homepage runs like a dog and scrolling is so erratic it's unusable.

If you spent two years hyping vaporware and then do a major Apple-esque launch with all the fanfair while claiming to the next evolution of technology...

... you should at least attempt to make the scrollbar on your own website work correctly.

Seriously. You don't have to be Steve Jobs to realize that your scrollbar needs fixing. It's not just unpolished, it fundamentally doesn't work.

Angular has directives, which are used to make encapsulated reusable components.

First up - React.js components are substantially better than Angular directives for this purpose, but the design of applications in Angular are generally not broken up into directives the same way you would use components in React.

In React, something as simple as a basic todo has a parent component, embedding another component for each row. In Angular, you would just iterate over your model.

Depending on your use case, having the whole thing as one snippet of straight up HTML with an ng:repeat annotation might be far more maintainable than breaking it up into parent and child components for such a simple use case, since you can get a full cohesive view of the code generating that part of the page.