Live data from Hacker News

Don't Be Afraid of Types

lmika.org

221–230 of 233 posts

Re: Don't Be Afraid of Types

#221

Earlier quoted context omitted.

I'm a big fan of primitive types, in particular byte arrays. It's okay to create a new data structure that combines some primitive data types in a "struct", like an array that tracks its length. But we don't want to "build abstractions and associate behavior to them" (just associate behavior to data structures like push/pop).

Can you expand on this please?

Sure, in many languages we have the notation of thing.do_thing(arg1, arg2).

I suggest this is a good notation for data structures like, stack.push(10) or heap.pop()

I'm suggesting we don't use this notation for things like rules to validate a file, so I suggest we write validate(file, rules) instead of rules.validate(file).

Then we can express the rules as a data structure, and keep the IMO unrelated behavior separate. Note then we don't need to worry about whether it should be file.validate(rules) perhaps. Who does the validation belong to? the rules or the file? the abstractions that are created by non-obvious answers to "who does this behavior belong to" are generally problems for future changes.

Re: Don't Be Afraid of Types

#222

Kind of disagree with this article, when you add a "noun" (aka type), you're often introducing a new abstraction. Abstractions have a maintenance cost associated with it ie, another developer or possibly yourself must be able to recreate the "algebra" associated with that type (your thought process) at the time of making modifications. This creates some problems: 1. Since there's no requirement to create a cohesive a…

> Kind of disagree with this article, when you add a "noun" (aka type), you're often introducing a new abstraction. The article is talking about simple "bundle of values" types; the example is the CreateSubscriptionRequest. This is not an abstraction. It is simply a declaration of all the fields that must be provided if you want to create a subscription. And it is usually superior to passing those N fields around ind…

yeah this is correct, somehow I jumped to a different conclusion :)

Re: Don't Be Afraid of Types

#223
post #207

Earlier quoted context omitted.

> Functional programming can express patterns just as well as OOP. No! Patterns are just crutches for missing language features or "design patterns are bug reports against your programming language." GoF patterns are concepts useful in OOP, but the recurring patterns and architectures you see in other paradigms are totally different. And they don't apply to Lisp: https://www.norvig.com/design-patterns/ Most don't eve…

> missing language features Yes, Holub mentions C folks had to make do with what they had--but that those implementations, despite differences, would point to one or another common pattern (or done enough times similarly to be a pattern). > bug reports against your programming language Yes and no. I daresay no programming language can expect to be a universal one and still have the same ergonomics as a purposeful one…

> We've only been speaking in terms of single languages, though. Have you combined different programs together before and thought, "Hmm, this is kind of like a pattern for..."?

It's metacircular all the way down. In Forth or Tcl, every command can be its own program. Deploying over many machines has similar dynamics to multithreading on one etc. The same concepts, notation etc. apply - you abstract over them and voila. Better primitive sets make things manageable, or don't.

You have reversed the point of patterns, to be a tool of thought and something to aim for. Instead, they are something you notice by a certain outcome, which guides you when you want that result again.

> Lisp ... tooling for boilerplate

That's where you've missed it. This isn't some functional propaganda; there are many paradigms, OOP and functional are just single ones. You are thinking in terms of boilerplate when these other paradigms just don't have any of it. You can even do OOP without the boilerplate - it's incidental to your tools, Common Lisp with CLOS is an OOP language. The patterns in Lisp architecture are about fundamental issues of domain modeling, how to structure teams, organizations and manage who should implement what. But you can even jump higher in scope and model that in code and "compile your company". As the code executes, at some points it will ask for user input, having an accountant do so and so action or asking a committee to assemble for something else. My company works this way.

-------

> no programming language can expect to be a universal one and still have the same ergonomics as a purposeful one or even a DSL

Yes and no. Yes, overfitting a tool to the current problem space restricts it, but DSLs can have the exact same ergonomics as any other language. Cf. Language Oriented Programming: https://beautifulracket.com/appendix/why-lop-why-racket.html

Re: Don't Be Afraid of Types

#224
post #4

The issue is names. Every darn little thing in Java needs a name. If there is no good name, that's a hint that maybe you don't need a new type. Obligatory Clojure example: (defn full-name [{:keys [first-name last-name]}] (str first-name " " last-name)) This defines a function named `full-name`. The stuff between [] is the argument list. There's a single argument. The argument has no name. Instead it is using destruct…

Meh. OOP has lots of nouns, FP has lots of verbs.

Re: Don't Be Afraid of Types

#225
post #9

Earlier quoted context omitted.

> Every darn little thing in Java needs a name. Don't all types need names, regardless of what language you use? Look at typescript, and how it supports structural typing. They don't seem to have a problem with names. Why do you think Java has that problem when nominal type systems simplify the problem? > There's no need to declare a type ObjectWithFirstNameAndLastName. It would be quite silly. Naming things is hard,…

I think Java culture had something to do with the ridiculously verbose names, but even more so the prevalence of Factory and Singleton paradigms in Java created these issues. Maybe because it was the first OO language that a lot of procedural coders came to in the 90s. Those patterns became sort of escape hatches to avoid reasoning about ownership, inheritance and scope. They're still common patterns for emergencies…

tbf those are features of early Java frameworks (particularly the awful java enterprise crap)

Re: Don't Be Afraid of Types

#226
post #205
post #153

Earlier quoted context omitted.

Objects are famously bad at (relational) databases, hence a near universal loathing of ORMs.

I had that idea too until I started using Entity Framework in dotnet. People probably hate ORMs because they've never used a good one.

Entity Framework with Linq is a huge time saver. But I started using records with EF instead of classes.

Re: Don't Be Afraid of Types

#227
post #131

People might be afraid of types because in OOP land there's the idea that types aren't mere containers for data. You have to use encapsulation, inheritance and polymorphism. Fields and properties shouldn't have public setters. You assign a value only through a method, otherwise you make the gods angry. You have gazillions of constructors, static, public, protected, private and internal. And you have gazillions of met…

Mostly agree, but there are methods that truly are best co-located with their (immutable) data. A good example is Point.Offset(deltaX, deltaY) which returns a new point relative to the callee. Why force that into a static class? There are plenty of examples where you want to use an abstraction over various immutable record types. Services vs. records is a false dichotomy and there is power in mixing the two. Yes, the…

>With a good Dependecy Injection system (and bad ones abound), requesting a service instance is just as simple as referencing a static class.

DI in .NET is very good and you can access an object with ease with DI. Still, why use it? It's another layer between the caller and calee. Creating objects and resolving those through DI takes some CPU cycles without any major added benefits and your code becomes more complex so more things can go wrong.

Re: Don't Be Afraid of Types

#228
post #207

Earlier quoted context omitted.

Holub [1] helped clarify this for me. Functional programming can express patterns just as well as OOP. Implementations--idioms--of a pattern can appear different but still retain its design purpose. Previously, I thought FP was a way to happy-path incidental habits to avoid studying every pattern. But if patterns are discovered, arise out of independently invented idioms, then the best I could do is reinvent what eve…

> Functional programming can express patterns just as well as OOP. No! Patterns are just crutches for missing language features or "design patterns are bug reports against your programming language." GoF patterns are concepts useful in OOP, but the recurring patterns and architectures you see in other paradigms are totally different. And they don't apply to Lisp: https://www.norvig.com/design-patterns/ Most don't eve…

>No! Patterns are just crutches for missing language features > visitor is type switching, singleton global variables, command and strategy are first class functions with closures

Another aspect of FP is compositionnality.

    Singleton -> memoization of zero-arg fn (pro: initialization is implicit)
    Memoization -> fn accepting a fn and returning a memoized version
    Command & Strategy -> just a lambda then ?
Objects/classes are complex. Composing them is not simple, it means composing every single method. In fact composition is handled as a special case of class derivation, interface implementation or wrapper class implementation usually, i.e. you gotta write another class. OOP has given up on this issue from the beginning. There is no point in building a framework to create classes by composing other classes because you'll have to control how each single method compose with its counterpart. Say farewell to your neat composition expressions, the granularity is not there. Just write another class instead.

Re: Don't Be Afraid of Types

#229

Earlier quoted context omitted.

When the data you need isn't entirely contained w/in those "neat boxes" you realize quickly the error was trying to force those "neat boxes" onto your data model.

Before starting to code any application, I tend to verify the ideas by first doing the design on my mind, then spending some time with a pen and paper, and maybe with some diagramming software. If I can't see the whole machinery in front of me, I don't start coding. In your case, if the data is not fitting into neat boxes, it's not being forced into that in the first place. I select tools/features according to the pr…

I don't disagree with any of that.

What I am saying is that Objects are not rows. And we sometimes try to force the Object model onto a data-schema that ultimately is more rich than a chain of objects.

Which is not using the most appropriate tool for the job, per your example.

An ORM is _fine_ for stuff that has a fairly standard shape, like blob posts, user accounts, things like that. Lots of relation questions against persisted data end up not being those exact shapes and patterns yet folks generally reach for the ORM.

Re: Don't Be Afraid of Types

#230

Earlier quoted context omitted.

> But I doubt that's a good practice That's entirely how "string" indexing works in C. Strings in C are just pointers to `char` with some valid allocation size. As long as the integer used for the pointer offset results in a pointer into the allocation after the addition, it's valid to dereference the result. Remember, `array[index]` is syntactic sugar for ` (array + index)` in C. Lots of the C stdlib string function…

> Doesn't matter Any time the microprocessor accesses memory for use as an int, it's a specific kind of int, meaning size and signedness, and the flags are adjusted properly as per the operation performed. > Strings in C are just pointers to `char` with I'm gonna end this here. I taught myself C programming by reading K&R in the late 80s, and then proceeded to do so professionally for YEARS and YEARS . There are peop…

> > Doesn't matter

> Any time the microprocessor accesses memory for use as an int, it's a specific kind of int, meaning size and signedness, and the flags are adjusted properly as per the operation performed.

Sure. But the C standard specifies how addition of a pointer to an integer works in section 6.5.7, particularly paragraph 9. The specifics of what flags get set & the width of integer used are up to the implementation & the programmer, but

> For addition, either both operands shall have arithmetic type, or one operand shall be a pointer to a complete object type and the other shall have integer type. (Incrementing is equivalent to adding 1.)

should be a pretty clear statement that pointer + integer is valid!

> > Strings in C are just pointers to `char` with

> I'm gonna end this here. I taught myself C programming by reading K&R in the late 80s, and then proceeded to do so professionally for YEARS and YEARS.

> There are people that know, and there are people that act like they know. You ever read the first two chapters of Windows Internals? You ever write C code that could make Windows system calls from the same program that could be 32- or 64-bit with a simple compiler flag?

> I have.

I'm an embedded C developer. I've been writing C for decades, but not for windows. But I do write code that can work on both 8-bit and 32-bit systems with just a compiler flag. Strings are arrays of character type with a null terminator, and array-to-pointer decay works as usual with them.

>> C strings are fundamentally fragile.

> Not if you know what you're doing. You're almost certainly using a C program to type this response in an operating system largely written in C. You get any segfaults lately? I don't EVER on either my Ubuntu or Debian systems.

> Thanks for playing.

C strings are arrays of character type with a null terminator. That is fundamentally fragile, since it includes no information about the encoding or length of the string, and thus allows invalid states to be represented. That doesn't mean you will get segfaults, only that it's possible for someone to screw up & interpret your UTF-8 data as ASCII or write a `\0` in the middle of a string or other such mistake, and you'll get no protection from the type system.

Post reply on HN