> I've interviewed a lot of functional candidates in a decade-long stint of functional programming professionally. My interview approach is always the same. All practical exercises, no leetcode here.
Does this mean that you're measuring, like, how fast someone can deploy a CRUD webapp in the given language? I can imagine F# would do poorly on that kind of metric; it's optimized for maintainability and doesn't take unprincipled shortcuts, whereas something like Ruby lets you type one line and it will blat out a bunch of defaults via unmaintainable magic.
> Implies that if someone just saw some code in F# and realized what people can do in it, they would be super impressed. I have not found that to be the case. If that's a general problem and not just a quirk of my personal experience, that has to be fixed first.
I don't think it's a general problem; almost everyone who spends any significant time working in F# returns to C# reluctantly if at all. It's already a "better" language in the sense of how nice it is to program in and how productive a programmer feels when doing so. But those aren't the metrics that matter.
They may not be built into Ruby-the-language but they're very much part of Ruby-the-development-community.
> I’ve seen and worked on some very maintainable rails apps
I've seen far more upgrade breakages in apps built on rails than any other stack.
React Native would like to argue with you on that point
Is F# too approachable due to its closeness to C#, popular with C# professionals, and therefore not approached as a functional programming language as much as a C# extension?
But tbh, from what little I know, I'd be expecting you to expect me to solve the issue from first principles. So using a "and then magic" technique might be something interviewees shy from.
It's unfortunate that they decided to pick F# on such problems when they didn't have the (mental) toolkit to tackle them, but I think it speaks more to people being really eager to communicate their enthusiasm for FP. I wouldn't try to ascribe any higher meaning to it.
Depends on what you mean by messy? Non-conforming JSON? A custom FParsec parser might be able to sensibly extract the data. If it is conforming to JSON then you'd use normal F# code to work with the standard JSON parsers.
However technical capability and efficiency of the tool isn't the only concern picking up the comments in this thread so far. The herd effect sadly can make people nervous creating a barrier for people to try it especially if career is in it. What other people think of it, and their preconceived notion can matter.
Normally now I ask Chat GPT to create an F# record from the JSON. Then go through and check everything line by line, redo the types to something sane and use that.
But if it were a quick script to do a one off then a type provider will work well.
Not trying to nitpick, your comment just piqued my curiosity because you made the point of distinguishing your exercises from leetcode and also stated that those who chose F# were generally poor performers.
Your observation about F# may be valid, but this does appear to be a test of one specific use case for a language, not how productive people are when building entire applications with them.
Scala developers were referred to a Java refugees, Swift developers to Objective C refugees, and F# as C# refugees. A weird side effect of Microsoft doing a better job with C# is that there less of a push to F#. Plus F# by virtue of being in Dev Div had the core value proposition (Ocaml on .Net) undermined by the Win vs Dev Div internal battles that tried and failed to kill .Net.
My guess is that interviewees wishing to return to the typed world they are comfortable with would first try to type the JSON they are working with. Given that the JSON is messy this could be an unbounded amount of work that is unlikely to pay-off within the span of an interview.
In the context of JSON you can work on it without types from a typed language. It’s just that as a force of habit coders may chose to spend time adding types to things when they shouldn’t.
It has absolutely nothing to do with dynamically typed programming. It's also a really terrible name for what is essentially caching.
There is much naïveté among the strongly typed herd.
Is exactly the reverse also true? Let me try: "There is much naïveté among the weakly typed herd." For every person who thinks Python or Ruby can be used for everything, there is another person who thinks the same for C++ or Rust.Also, the example that you gave is incredibly specific:
When tasked to create glue code translating between two opposing type systems, which is a very common data engineering task
Can you provide a concrete example? And what is "data engineering"? I never heard that term before this post.I'm not sure why using a static language would make translating data types difficult, but I add as many typehints as possible to my Python so I rarely do anything with dynamic types. I guess they're saying for small tasks where you're working with lots of types, when using a static language most of your code will be type definitions, so a dynamic language will let you focus on writing the transformation code.
Lots of people assume static typing means creating domain types for the semantics of every single thing, and then complain that those types contain far more information than they need. Well, stop doing that. Create types that actually contain the information you need. Or use the existing ones. If you're deserializing JSON data, it turns out that the deserialization library already has a type for arbitrary JSON. Just use it, if all you're doing is translating that data to another format. Saying "this data is JSON I didn't bother to understand the internal content of" is a perfectly fine level to work at.
Stop inventing work for yourself.
But yes, you can have a JsonNode type, which is still better than having every type being “object”.
Another technique: While the JVM is running with a debugger attached, it is possible to inject a new version of a class. IDEs usually make this seamless. It also works when remote debugging. Do you consider this monkeypatching also?
There's no theoretical reason you can't. No languages that I know of provide that combination features, because monkey-patching is a terrible idea for software engineering... But there's no theoretical reason you couldn't make it happen.
I think you've conflated static typing with a static language. They're not the same thing and can be analyzed separately.
But monkey-patching is also often used to alter behaviors of existing things, and that could be done without needing new types.
In another dimension, you could use that feature in combination with something rather like Ruby's metaclasses to change definitions globally in a statically-typed language.
I can't think of a language that works this way currently out there, but there's nothing impossible about the design. It's just that no one wants it.
Given that, a sketch of a statically-typed system would be something like... At the time a definition is added to the environment, you type check against known definitions. Future code can change implementations, as long as types remain compatible. (Probably invariantly, unless you want to include covariant/invariant annotations in your type system...)
This doesn't change that much about a correct program in a dynamic language, except that it may provide some additional ordering requirements in code execution - all the various method definitions must be loaded before code using them is loaded. That's a bit more strict than the current requirement they the methods must be loaded before code using them is run. But the difference would be pretty tractable to code around.
And in exchange, you'd get immediate feedback on typos. Or even more complex cases, like failing to generate some method you had expected to create dynamically.
Ok, I can actually see some appeal here, though it's got nothing to do with monkey-patching.
[{
name:”ex obj”
value:”a sample value”
extraProperty:”I’m important”
}]
And the code class myDTO{
string name;
string value;
}
var myObjs= DerserializeFromFile<myDTO>(filepath)
SerializeToFile(myObjs, filePath2)
filepath2 would end up with without the extraProperty field.You can also write code like
function PrintFullname(person) {
WriteLine(person.FirstName + “ “ + person.LastName)
}
And it will just work so long as the object has those properties. In a statically typed language, you’d have to have a version for each object type or be sure to have a thoughtful common interface between them, which is hard.All that bring said, I generally prefer type safe static languages because that type system has saved my bacon on numerous occasions (it’s great at telling me I just changed something I use elsewhere).
You can write code in a statically typed language that treats the data as strings. The domain modelling is optional, just choose a level that you need:
1. String 2. JSON 3. MyDTO
If you do choose 3, then you can avoid serde errors using property based testing
I am surprised that you are using Python for data transformation. Isn't it too slow for huge data sets? (If you are using C/C++ libraries like Pandas/NumPy, then ignore this question.) When I have huge amounts of data, I always want to use something like C/C++/Rust/C#/Java do the heavy lifting because it is so much faster than Python.
I find Python is fast enough for small to medium datasets. I've normally worked with data that needs to be loaded each morning or sometimes hourly, so whether the transformation takes 1 minute or 10 minutes it doesn't matter. The better way is of course to dump the data into a data warehouse as soon as possible and then use SQL for everything, so I only use Python for things that SQL isn't suited for, like making HTTP requests.
In retrospect, Marz's hope that specter might get incorporated in clj core was wildly optimistic (even if the core team wasn't hostile to outsider contributions), because it feels like he built it to his own satisfaction, and never got around to removing the sharp edges that newcomers cut themselves on.
It's a shame, because I think specter is a cool idea, and would love to see a language based on its ideas.
Relevant blog post is Parse don't Verify
"Dynamic programming" traditionally has nothing to do with dynamic languages but is instead a class of algorithms that are "dynamic" in the sense that they represent time.[0] This might be what your parent was referring to because these algorithms lend themselves well to Haskell's lazy evaluation, and they reference F# as being eager.
That said, they also talk about F# as being type safe, so they could also be referring to dynamic programming languages. The grandparent was definitely referring to this one, but "dynamic programming techniques" sounds much more like the algorithmic meaning.
That's the type bias. If you look at a non-typed codebase, it always feels like it will be better with types. But if you had a chance to go back in time and start the same codebase in Typescript, it would actually come out way worse than what you have today.
Types can be great when used sparingly, but with Typescript everyone seems to fall into a trap of constantly creating and then solving "type puzzles" instead of building what matters. If you're doing Typescript, your chances of becoming a product engineer are slim.
One would expect that by now, out-of-process COM would be supported across all OS extension points, instead it is still pretty much in-process COM, with the related .NET restrictions.
Then there is the whole issue that since Longhorn, most OS APIs are based on COM (or WinRT), not always with .NET bindings, and even VB 6 had better ways to use COM than .NET on its current state (.NET Core lost COM tooling).
In case you aren't aware, WinRT basically marks the turning point started with Longhorn ideas being rewriten into COM.
With WinRT, they basically went back to the drawing board of Ext-VOS, a COM based runtime for all Microsoft languages, hence why .NET on WinRT/UWP isn't quite the same as classical .NET and is AOT compiled, with classes being mapped into WinRT types (which is basically COM, with IInspectable in addition to IUnknown and .NET metadata instead of COM type libraries).
This didn't turned out as expected, but still the idea going forward is to make WinRT additions to classical COM usable in Win32 outside UWP application identity model.
"Turning to the past to power Windows’ future: An in-depth look at WinRT"
https://arstechnica.com/features/2012/10/windows-8-and-winrt...
Today's post-UWP C#/WinRT bindings don't require AOT either.
Beyond WinRT, .NET (Core) has supported all the raw COM, including COM component hosting since at least .NET Core 3.0. It's Windows-Only, of course, to use that, but that should go without saying. It's mostly backwards compatible with the old .NET Fx 1.0 ways of doing COM and a lot of the old code still "just works". .NET has proven that with .NET 5+ and all the many ways it (again) supports raw Win32 fun in the classic WinForms ways. (And all the ways that even .NET Fx 1.0 code has a compatibility path into .NET 5.)
It would have been nice if Windows had stronger embraced .NET, but WinRT is still closer to .NET in spirit than old raw COM anyway.
.NET Core doesn't do COM type libraries like the Framework does, you are supposed to manually write IDL files like in the old days.
Additionally CCW/RCW infrastructure is considered outdated and you should use the new, more boilerplate based COM APIs introduced for COM and CsWinRT support.
Lots of changes, with more work, for little value.
F# is a slice through the language design space that optimizes for developer productivity, other languages with different design choices are optimized and are indeed better for other things.
I think there is a benefit to learning learning ‘bad’ languages as they teach you about the different design trade offs that are available. A person with dynamic programming language experience would have known that the given JSON task was tractable without types and not started the task with a ‘type all the things’ mentality.
Maybe I am mistaken here, but it seems to me that the line you are quoting from the essay implies the opposite of what you said.
I'm sure you could find a person who would claim this same exact thing for any language if you searched around a bit.
To derive a useful conclusion you would need to control for all of the factors involved in the interviews and objectively measure their performance compared to other people's, anything less than that is completely useless at best.
The most important thing is to always be learning. You’ll never be done, embrace that and rejoice in it. Most people more or less stop learning in early adulthood: don’t do that, and you’ll eventually be ahead of the pack.
Can be a great Java programmer, now go do something in assembly, doesn't reflect on the Java skills.
I.e. social proof
I feel like F# deceptively presents itself as simple, when in reality it is closer to C# in complexity. I've written in languages that are actually simple (tcl) and it is a joy. I've written in languages that are unashamedly complex (Wolfram language) and that is also fun. But F# occupies that weird middle ground where it seems easy to do what you want, but for some reason you trip over your own shoelaces every time you take a step.
Pure syntax wise, its pretty nice, though having a single pass compiler makes it a bit dated feeling compared to other languages.
though having a single pass compiler makes it a bit dated feeling compared to other languages
Can you please share how F# could improve with a multi-pass compiler?I'm not particularly attached to order of definitions within a module, but I definitely like the linear ordering of modules. Without the compiler enforcing that you always end up with cross dependencies between modules, which I think makes it much harder for humans to read the code.
I've had similar conversations about variable shadowing. I find it natural and it bothers me when I can't do it, especially in functional languages, but I have a friend who really doesn't like it and finds it jarring. I think his dislike stems from having learned Erlang before any language that has shadowing, because in Erlang when you reuse a variable name it'll be checking for equality with the value you're "assigning" it to.
In codebases that allow out of order definitions, things get wild real quick.
As I understand, Java allows this. You can effectively have circular dependencies. If true, I have worked on multiple million+ lines Java projects in my career; none of them were "wild real quick". Also, C++ has forward type declarations to effectively allow the same. Again, there are ginormous projects like Google Chrome and Firefox written in C++.Not an issue in C# at all. Get wild in what way?
I'm not sure a project gets into that state "real quick", but it remains something that projects can do over time, sometimes without realizing it, especially when a DI abstraction makes it even less obvious how circular a project's dependencies really are.
[1] https://en.wikipedia.org/wiki/Cyclomatic_complexity
[2] https://en.wikipedia.org/wiki/Software_Peter_principleF# always came across as very weird in the syntax area
In my experience, the worst performance is by C++ candidates.
Why? Not because C++ is bad, or attracts bad programmers.
Rather, because if that's the language you reach for a quick solve, you probably don't know very much, and that says something.
I would consider most popular languages, like C#, Java, and Python decidely unpragmatic as languages. There's way too many hoops to jump through to concisely describe the problem domain. In F#, I define some types that describe the domain, write some functions, and move on. It's that easy.
Like in an interview setting, even if they say you can choose any language, it is probably a safer bet to go with something the interviewer has probably seen before.
I'm not a great fan of f# (it's not rational - but every time I've tried to dip my toes, something in the syntax has felt... Tedious? In a way that for example StandardML does not). But it still seems like an eminently powerful and pragmatic language, so I'm surprised by your experience.
Ed: I see this was addressed downthread:
https://news.ycombinator.com/item?id=36133560
https://news.ycombinator.com/item?id=36133578
Fascinating that fsharpers trip up on ad- hoc json wrangling - to be fair i used to agonize over nested lists/association lists in lisp - rather than doing the "professional" thing and YOLO assume that three levels down, behind :user - there's a fifth element :email...
I've interviewed a lot of functional candidates in a decade-long stint of functional programming professionally. My interview approach is always the same. All practical exercises, no leetcode here. You can do the exercises in the language you're most comfortable in.
If I had to pick a language that predicted you'll do poorly on a practical interview exercise, I would pick F# every time. For some reason the candidates just do not do well. Now I can think of some confounders - maybe the types of people who would apply to a job with some dynamic programming requirements and people who are good at F# just have no overlap. I've thought about that.
But it seems like this line
Implies that if someone just saw some code in F# and realized what people can do in it, they would be super impressed. I have not found that to be the case. If that's a general problem and not just a quirk of my personal experience, that has to be fixed first.