[HN Gopher] Microfeatures I'd like to see in more languages
___________________________________________________________________
Microfeatures I'd like to see in more languages
Author : Tomte
Score : 119 points
Date : 2023-01-05 20:29 UTC (2 hours ago)
(HTM) web link (buttondown.email)
(TXT) w3m dump (buttondown.email)
| tobyhinloopen wrote:
| Elixir's testing library uses meta programming to show the code
| that fails and what the values were at both sides of a
| comparison.
|
| IE a = 1; b = 2 assert a == b
|
| Will fail with error like: Assertion failed,
| a == b Left is 1 Right is 2
|
| So you don't have a bunch of assert-functions; you just assert
| anything and it will spit out a decent error.
| elcritch wrote:
| Yes! That feature is great. I got used to it in Elixir and Nim
| provides it as well. It's one of those little things that makes
| programming nicer.
| masklinn wrote:
| With how old pytest is, I assume that's where they got it from.
| Does it perform recursive value printing, or bespoke
| comparisons?
|
| e.g. in pytest it won't just print out the values of "a" and
| "b", it will recursively document intermediate values until
| it's reached the toplevel expression: assert
| f() == g() assert 42 == 43 where 42 =
| <function TestFailing.test_simple.<locals>.f at
| 0xdeadbeef0002>() and 43 = <function
| TestFailing.test_simple.<locals>.g at 0xdeadbeef0003>()
|
| and it's possible to customise the report so you can report as
| a diff: assert "foo 1 bar" == "foo 2 bar"
| - foo 2 bar ? ^ + foo 1 bar ?
| ^
| 4ad wrote:
| > Instead of writing 10000500, you can write 10_000_500, or
| 1_00_00_500 if you're Indian.
|
| I hate this so much. It means I can't grep for a constant.
| overengineer wrote:
| 1_?0_?00_?0_?500 yes it sucks
| elygre wrote:
| That's not enough -- the underscores can appear anywhere, so
| you need to cater for 500000_0 and 5_0_0_0_0 etc. basically
| an optional underscore between each digit.
| amalgamated_inc wrote:
| just grep for `[0-9_]+`
| masklinn wrote:
| I would assume they're looking for a specific constant
| value.
| turtledragonfly wrote:
| Well you can, you just need a more complicated regex. Same for
| '1e6' and such.
| Smaug123 wrote:
| Out of interest, how often do you do this, and what are the
| semantics of the numbers you're grepping for? I literally can't
| remember a time I've ever tried to grep for a number.
| duped wrote:
| I've done it for error codes but that was awfully cursed
| jbverschoor wrote:
| But you need to search for both hex and dec codes for that
| maxbond wrote:
| If you have constants repeated throughout the codebase, you can
| pull them into a constants file, and then you can go to that
| file, navigate to the definition of interest, and use your IDE
| of choice to find usages.
|
| You'll also be able to give these constants semantically
| significant names, and comment next to them providing
| derivations or citations. And of course, if it's a mistaken or
| outdated value, you can change it one place and apply it
| everywhere.
|
| Consider that, if you were debugging a problem with this
| constant, and the problem was caused by someone having made a
| typo in one of it's usages (eg having typed 1000500 instead of
| 10000500, a mistake that's more difficult to make of you have
| better ways to format numbers [did you have to look back and
| forth to find the mistake? I did]) - your regex would fail to
| find it, even if there were no ambiguity about the format it
| was written in.
| ludston wrote:
| Depends on the size of your codebase and how many people work
| on it. Once you have 20 years of code written full time by
| 200 developers in your monolith, finding the constants file
| out of 400 different subsystem's constant files for a given
| subsystem that you've never seen before can become a
| legitimate and challenging pain.
| maxbond wrote:
| Totally true. I don't know if IDEs commonly have a feature
| like, "find this value, regardless of how it's expressed"
| (even better yet, fuzzily, to catch typos), but I think
| that's the proper general solution. It's a good idea to
| consider a constants file earlyish, while everything still
| fits in your head.
|
| I'd say in the case of such a sprawling system, make a
| constants module, and it can have different files for
| different topics. But keep them all together. Code style is
| an engineering tool you can use to prevent problems.
|
| But I do understand this is cold comfort for those working
| on systems where the decision around this were made 15
| years ago, and there's no possibility of refactoring the
| constants. That's quite annoying.
| CuriouslyC wrote:
| Then you have a problem with having to update both the
| constants module and the service that depends on it to
| change the service, and you need to handle packaging and
| distributing the module. Maybe if you have a monorepo
| where those issues are moot...
| maxbond wrote:
| I didn't mean to suggest the constants module was a
| separate, reusable module, but a component of the same
| piece of software. By "module" here I meant "directory
| which can contain multiple importable files," so you
| could namespace your constants (constants/rfc_abcd.xyz,
| constants/customer_limits.xyz, etc).
|
| I'd rather copy-paste any reused constants to different
| projects to avoid coupling, unless there was some kind of
| compelling domain/project specific reason.
| ludston wrote:
| When your code-base reaches a certain size, you cease
| using the IDE to find code and instead start using
| specialized tools, such as Lucene indexes so that
| grepping through code takes seconds rather than minutes.
| One of the bads of this is that using regex over an index
| is O(n) in comparison to the O(log(n)) of a normal index
| lookup.
| maxbond wrote:
| Perhaps this special tooling can normalize numbers and
| other source code ambiguity.
| CuriouslyC wrote:
| This so hard. A constant that replaces a magic number like
| 5 that is ungreppable, or a constant for a precise number
| like the physical constants - sure. A constant that
| replaces a number like 404 or 500 that you want to be able
| to easily grep across heterogenous code bases for? Pass.
| maxbond wrote:
| Is that 500 the HTTP error or 500 the adhoc limit we put
| on the number of user uploads or a 500, representing half
| a kilobyte?
| CuriouslyC wrote:
| I'll take the momentary ambiguity in some cases (usually
| quickly resolved by line context and file name) over
| having to manually hunt down 5 different projects'
| inconsistently named constants to do 5 different greps
| any day of the week.
| progval wrote:
| I'm in the same boat as GP. Typically, I grep such things
| when I am not familiar with the codebase, so I can't change
| where constants are defined, and I do not know where to find
| such a file.
| maxbond wrote:
| For what it's worth when I dive into a new codebase, the
| first thing I do is try to guess what files exist and then
| find them and get a feel for structure. Constants are high
| in the list.
|
| But there are many ways to skin a cat.
| 4ad wrote:
| You haven't understood the problem at all. No wonder, few
| people do any sort of bare metal programming. It's not about
| defining constants, or even writing code at all, _it 's about
| figuring out what this arbitrary piece of code is doing based
| on hardware datasheets_. You search for constants defined in
| the datasheet in the piece of code you are analyzing...
| dan-robertson wrote:
| The funny way I've seen this go wrong in practice is a typo
| like: // we don't want more than 100m because
| ... quota = 1000_000_000
|
| Where people assume the underscores are in the expected place.
| AnimalMuppet wrote:
| You already can't, because someone can write the constant in
| hex, or even (shudder) octal.
| adrianmonk wrote:
| Or like this: const int
| WAIT_TIME_MICROSECONDS = 42 * 1000 * 1000; // 42 seconds
| perlgeek wrote:
| Some from Raku (formerly Perl 6) that I really like:
|
| * sub MAIN:
|
| sub MAIN(Int $x, :$verbose) { }
|
| generates a command line parser that expects an Integer plus an
| optional named switch --verbose
|
| * It has named params (as seen above), and there are
| abbreviations: instead of thing => $thing you can write :$thing
| to avoid duplicating the name (:thing also exists, though it
| create a pair "thing" => True, so ruby lovers need to be careful
| :D )
|
| * junctions for quick conditionals/validation: 0 <= all($x, $y,
| $z) <= 2 * pi
|
| * this is a probably debatable, but: if you use a * as a term, it
| will create a lambda for you, so *+2 is similar to sub ($x) { $x
| + 2 }
| amalgamated_inc wrote:
| Elixir's sigils are amazing. There are date sigils that allow you
| to do what the OP does: ~N[2023-01-01 12:00:00]
|
| But you can also define your own sigils to create new "custom
| syntax" for almost any struct. Kind of a special case of reader
| macros, I guess. Very convenient.
| mncharity wrote:
| Elixir sigils[1][2].
|
| Eg: ~w(foo bar bat) is a word list. `~ letter bracketed-text
| lettersasmodifiers` desugars as sigil_<letter>(text,modifiers).
| Similar to foo_str() of Julia[3], but for one-letter-names and
| more brackets. But not the unicode brackets of Raku.
|
| [1] https://elixir-lang.org/getting-started/sigils.html [2]
| https://hexdocs.pm/elixir/main/syntax-reference.html#sigils [3]
| https://docs.julialang.org/en/v1/manual/metaprogramming/#met...
| cschmatzler wrote:
| The most recent addition to the digit family being Phoenix's
| new ~p"/healht", which is a HTTP route string that
| automatically verifies whether the route exists, and returns
| compile-time warnings when you link to a path that didn'
| doesn't. It's fantastic, and really surprising it took this
| long to be added to any web framework.
| nicoburns wrote:
| One of the best truly micro features I've seen recently (can't
| remember which langauge unfortunately - it wasn't a mainstream
| one), is general binary literal syntax of the form:
| 0x[de ad be ef 00]
|
| So much nicer than the usual condensed format. And I think it'd
| be valid syntax in any language that allows binary integer
| literal.
| andix wrote:
| A lot of languages allow underscores in numeric literals.
| Something like 10_000. You can put them anywhere and they get
| ignored. I don't know if they also allow it for hex numbers.
| masklinn wrote:
| > I don't know if they also allow it for hex numbers.
|
| They do e.g. Python >>> 0x_ab_cd_01_23
| 2882339107
|
| or >>> 0b_0010_0100 36
| rwmj wrote:
| Even C these days, although they chose ' instead of _ :-(
| [deleted]
| jbverschoor wrote:
| ohhh that's cool.. in Ruby you can do 0x_dead_beef_00 or any
| other combo
| dan-robertson wrote:
| Is that giving you a number or a bytestring?
| mncharity wrote:
| I like that Elixir bitstrings[1] don't have to be bytes.
| <<2:3>> is three bits 010.
|
| [1] https://elixir-lang.org/getting-started/binaries-strings-
| and...
| Doctor_Fegg wrote:
| Negative array subscripts. So a[-1] means the last element of an
| array, a[-2] means the second last, and so on.
| tester756 wrote:
| if it works on literals only, so a[x] doesnt work if x is
| negative, then ok.
|
| otherwise seems like an errors that are hard to spot.
| _a_a_a_ wrote:
| As a choice then perhaps but as a default and unalterable
| behaviour it can be a bloody timewaster when negative
| subscripts are a runtime error in your work. I've hit that in
| python and didn't enjoy it.
| scubbo wrote:
| I was really surprised when I learned my second language (after
| Python) to realize that this wasn't standard!
| unnouinceput wrote:
| That's for languages that can't define arrays with custom
| start/stop indexes. But those that have custom indexes they can
| very easily expand/implement as helper class (for example
| array.indexFromLast(1) which means array[Length(array)]. This
| way you can have best of both worlds.
| masklinn wrote:
| Surely if your language has custom indexes / ranges
| `Length(array)` is completely broken and the language
| provides something like "Index`Last" you can hook on?
|
| Because an array with indexes [3, 7) has length 4, but 4 is
| not the index of the last element.
| Jtsummers wrote:
| * * *
| unnouinceput wrote:
| Yup, correct. What I meant above with array[Length(array)]
| is for the languages that don't have it. Let me be more
| clear.
|
| C/C++ doesn't have custom array indexes and as such
| <array[std::size(array) - 1]> is returning the last element
| of said array.
|
| Delphi has custom array indexes and as such, taking your
| example with defining an array in the form <example_array :
| array[3..7] of integer>, I would not get the last element
| in case of <example_array[Length(example_array) - 1]. In
| this case I would have 2 options. Option 1 would be to use
| <High> function as in <example_array[High(example_array)]>
| to access example_array[7] element. Delphi also has <Low>
| function so you can iterate through a custom defined array
| by using <for> keyword with the help of them. Option 2
| would be to actually build my own helper (this is the most
| wanted case when you're dealing with multi-dimensional
| arrays that also have custom indexes) and I would have
| something like <example_array.FromLastIndex(0)> to access
| example_array[7] element.
|
| Hope this cleared the confusion.
| kqr wrote:
| Yes, then I would expect 'First and 'Last with the obvious
| meaning, and something like 'Range which returns an
| iterator of all indices.
| feoren wrote:
| Any language that supports overriding the index operation
| should support this. You should be able to do this in C# with a
| struct with a backing array, for instance. If you're going to
| do this, use the word "Circular" in it, and I would also insist
| that if a has 4 elements, then a[0] == a[4] == a[8]. In other
| words, you always just take the (positive) index modulo the
| size of the area. Then a[-1] is the same as a[N-1] for an array
| of size N. This could be useful in a lot of contexts, but
| should be made explicit.
| anamexis wrote:
| Where would the circular indexing like that be useful?
| AlotOfReading wrote:
| It's frequently used in signal processing to the point
| where it's considered one of the defining features of DSPs.
| One common case is filtering over a fixed size buffer of
| samples. If you have circular indexing, you can simply
| overwrite the earliest sample and increment the base
| reference to the next element.
|
| I'm not sure I'd want it for every list, but there are
| certain places it's nice.
|
| [1] https://www.allaboutcircuits.com/technical-
| articles/circular...
| feoren wrote:
| First of all, it handles the "get me the 2nd to last
| element" case automatically, but in a way that doesn't feel
| like a weird edge case: it's more "mathematically sound",
| basically. I always want mathematical soundness if possible
| because it leads to serendipity, the opposite of technical
| debt. Where technical debt is "dammit, this is going to
| take so much longer than it should!"; serendipity is "oh
| wow I can implement this cool new feature just by combining
| these other two things in a new way, in like 2 lines. This
| is going to be way faster than I thought." Mathematical
| soundness / purity leads to serendipity.
|
| Directly, it supports caches very well. You just increment
| the number of things you've ever cached and that's where
| your next cached value goes; you don't care when it
| overwrites an old value.
|
| There are other cases where you just need some variant of a
| thing, but you don't actually care that much about which
| variant you get. You might want to vary your wording in
| auto-generated text, for instance, by rotating synonyms. Or
| rotating the tiles you use in a 2D game. In this case I'd
| define an interface where you pass in a "seed" integer and
| it gives you back some deterministic example; a circular
| array is the simplest implementation of this interface (but
| there are others).
|
| You could also do simple load balancing by sending work to
| Worker[workCount++]. While usually you want to track each
| workers' existing workload (because the work takes
| unpredictable time), this simple approach could be
| sufficient if all your work completes in about the same
| time.
|
| If you're doing fancy math or science computing, you may be
| working with finite groups or fields, whose elements you
| could stick in an N-dimensional circular array (based on
| the characteristics of the field).
| _a_a_a_ wrote:
| What 'mod' is for, innit.
| philsnow wrote:
| You'd have to care about the difference between indexing with
| a literal and indexing with variables of different
| types/widths, and how indexing with a variable interacts with
| the size of the area.
|
| For instance if you have an int array that contains the
| numbers 1-250 and you index with a uint8 variable i,
| for (uint8 i = 247; i++;) { // print circ_arr[i]
| }
|
| for the values of i near the overflow points of the circular
| array and of the uint8 it gets weird: i
| circ_arr[i] 247 247 248 248 249 249
| 250 250 251 1 # 251 % 250 = 0 252 2 # 252
| % 250 = 1 253 3 # ... 254 4 255 5
| 0 1 # i overflows to 0 1 2 ...
| mr_mitm wrote:
| I really liked the postfix and prefix notation in Mathematica.
| These three all mean the same:
|
| f[x]
|
| f@x
|
| x // f
|
| It matches the flow of thought more naturally when hammering out
| a couple of one-liners.
| japanman425 wrote:
| How is that third one readable at all? I'd assume it meant
| integer division.
| mr_mitm wrote:
| That's just a matter of habit. The exact symbol isn't
| important anyway.
| Smaug123 wrote:
| Mathematica uses a _lot_ of syntactic sugar. You will find
| _all_ Mathematica code unreadable until you 've learned to
| read it; `//` is no exception.
| contravariant wrote:
| I find Mathematica code unreadable full stop.
|
| Its convenient when writing though.
| mr_mitm wrote:
| I see Mathematica as a shell for math. You can write long
| programs or modules in it, sure, but very often you're
| simply typing up a couple of lines to check some
| computation or visualize an expression which you won't
| even save, in which case readability is secondary.
| carry_bit wrote:
| It's usually used with the formatting functions, so you have
| something like:
|
| <Some big expression here> // Column
|
| Useful where the function in question is an "afterthought".
| rwmj wrote:
| You're writing something and you decide you want to apply 'f'
| to it, so you type '// f' (instead of backspacing like a
| caveman). It's actually rather convenient.
| [deleted]
| orangepanda wrote:
| Php supports kebab-case variables: ${"variable-
| name"}=123;
|
| Isnt it beautiful?
| adrianmonk wrote:
| Perl, too. (Probably not a coincidence.)
|
| You can also put a newline in a variable name if you really
| want. Or a 0 byte.
|
| Here's a demo. I've used the debugger because its "X" command
| can print the true name of the variable: $
| perl -d -e 1 Loading DB routines from perl5db.pl
| version 1.60 Editor support available.
| Enter h or 'h h' for help, or 'man perldebug' for more help.
| main::(-e:1): 1 DB<1> ${"variable-name"} = 123;
| DB<2> ${"variable\nname"} = 456; DB<3>
| ${"variable\0name"} = 789; DB<4> X ~variable
| $variable^@name = 789 $variable^Jname = 456
| $variable-name = 123
| contravariant wrote:
| So does python I suppose locals()["kebab-
| case"]=123
| oconnor663 wrote:
| How many languages support kebab case with any Unicode dash
| that isn't the ASCII one? :)
| jfoutz wrote:
| At the moment, the only one I know for sure is Agda.
|
| I suspect java would work as well, not sure about golang
| unicode var naming.
| zimpenfish wrote:
| play.golang wouldn't let me use figure (-, U+2012), endash
| (-, U+2013) or emdash (--, U+2014) in a variable name
| directly.
|
| But then the spec[1] says that only code points
| characterised as "Letter", an underscore, or characterised
| as "Number, decimal digit" are valid.
|
| [1] https://go.dev/ref/spec#Identifiers
| leipert wrote:
| Coptic Small Letter Dialect-P Ni should work, looks like
| a hyphen.
|
| Go Playground: https://go.dev/play/p/kxgOcEWsznz
|
| https://www.compart.com/en/unicode/U+2CBB
| dunham wrote:
| Agda does, but it also supports a plain ascii hyphen in
| identifiers. It allows operator characters inside identifiers
| and requires spaces around operators otherwise (as proposed
| in the article). So you can use x-y as an identifier:
| x-y : Z - Z - Z x-y x y = x - y
|
| The Agda community also heavily uses unicode characters. I've
| even seen a unicode colon used for a custom syntax because
| the ascii colon was unavailable.
| Smaug123 wrote:
| F# allows arbitrary names within double-backtick identifiers.
| The following is a valid declaration, where I've used a
| hyphen, en-dash, and em-dash: let ``foo-
| bar-baz--quux`` = 3
| adastra22 wrote:
| Probably any language that supports Unicode identifiers.
| prpl wrote:
| I wrote a small DSL for easy compilation to SQL that included
| some similar features. It included datetime literals, but they
| were specified as just a string prefixed with "d".
| d'2020-02-20'
|
| I also tried to make it so that every comparison had both english
| and symbol representations, a range syntax, and an
| approximation/match comparison (e.g. "=~", "!~") which could work
| with both floating point numbers and strings properly.
|
| I found this useful, and wish it was in more languages.
| [deleted]
| jb1991 wrote:
| I would add named function parameters to this list. So useful.
| mhd wrote:
| What I'd like to have brought back is basically the extended
| version of the numbers/kebap-case: Ignore white space in
| constants and identifiers if possible, like Algol-68 did.
|
| That means you can write your number as "1 000 000" and put it in
| the variable "one million", and then feed that to your function
| called "withdraw money".
|
| Yes, sure, makes it harder to grep. Here's a nickel, get a better
| grep tool (or wrapper thereof).
| andix wrote:
| local const variables like JavaScript has them. You can declare
| variables with ,,let" (mutable) or ,,const" (immutable). This is
| really great when reading code, because you never have to check
| if some code may change the variable at some point. And you
| usually declare most variables as const.
|
| A lot of languages provide immutable variables only for class
| members or statics, but not for local variables.
| kaba0 wrote:
| I really like Scala's `val` vs `var`.
| andix wrote:
| Okay, that would confuse me. Looks very similar and easy to
| overlook.
| krapp wrote:
| Omitting parens in a function signature when calling with one or
| no arguments.
| dragonwriter wrote:
| This means you can't reference the function itself by name
| without some kind of quoting construct or other circumlocution;
| also, while the no-arg case might make sense as a special case,
| if you allow the one arg case, you might as well admit the
| general case.
| ljm wrote:
| Ruby's symbols are undersold in terms of how useful they are and
| how they enable meta programming and DSL development. You can
| basically invent new language keywords with them.
| private def foo "bar" end
|
| In this case, 'def foo ... end' returns ':foo', and 'private' is
| just another method that takes it as an argument and decorates
| the provided method. It's not a special language keyword.
| ufo wrote:
| Lua also allows you to choose the string delimiter. If your
| string contains "]]" you can delimit it with [=[ or [==[ instead.
| Any number of "=" so long as the opening and closing delimiters
| match.
| unnouinceput wrote:
| And that's why all modern languages implement streams/string
| helpers/string builders. You do not want to actually write
| strings/manipulate them using "+" (concatenation symbol) in
| code directly because, in modern Unicode world, it tends to
| become a point of failure for obscure bugs / a maintenance
| horror show.
| jbverschoor wrote:
| I don't understand the link with parent, nor why + would be
| bad, besides a) because language is naive about concatenation
| / allocation, and b) if the language allows / doesn't
| differentiate between a char 'x' and an integer, bc that
| would result in an integer addition instead of a
| concatenation.
| jamincan wrote:
| For rust, it is probably the try (?) operator. Fundamentally,
| it's just syntax sugar for a match statement with an early return
| in the Error or None cases, but it really improves the ergonomics
| of dealing with Result and Option types.
| mncharity wrote:
| > roughly three classes of language features [...] 3. Quality-of-
| life features that aren't too hard to add
|
| I'd regrettably add another class, quality-of-life features which
| you'd have hoped weren't too hard to add, but because of past
| choices, now are.
|
| Examples: Adding javascript-like dots a.b.c for Julia Dict's
| a[:b][:c] would conflict with "wasn't intended to be public but
| has been" Dict implementation fields, like .count . Adding { a,b
| | ... } instead of a less concise { |a,b| ...} for Ruby blocks,
| but for a yacc grammar conflict.
| keybored wrote:
| Agree with kebab-case.
|
| > Most languages have multiline literals, but what makes the Lua
| version great is that the beginning and ending marks are
| different characters. This solves the infuriating "unnestable
| quotes" problem string literals have, and you don't have to
| escape all your literal \s.
|
| That paragraph also uses "nestable marks".
| turtledragonfly wrote:
| With regard to strings, they give a good example in Lua, but oh
| boy wait until this person hears about Perl (:
|
| There's a whole section in the manual [1] for string quoting
| operators (qq, qw, qx, ...)
|
| In general, I feel like Perl is one of those languages that has a
| high amount of these "quality of life" syntactic features, and
| helps make it enjoyable to write, once you get over the learning
| curve.
|
| [1] https://perldoc.perl.org/perlop#Quote-Like-Operators
| mncharity wrote:
| Raku (once Perl6) generalizes quoting as a Q , followed by
| optional "how should this behave" adverbs, and text bracketed
| by anyish unicode bracket pair. So Q:w <foo bar> is a list of
| two words. And has Perl-like qw/foo bar/ as sugar. Heredocs are
| Q:to/THEEND/ ... \nTHEEND . I'm unclear on whether you extend
| this without defining your own Q-like thing?
|
| Julia allows[2] defining your own non-standard string literals.
| foo"bar"hee and qux`...` desugar as macro calls
| foo_str("bar","hee") and bar_cmd("..."). But lack the bracket
| flexibility.
|
| http://rigaux.org/language-study/syntax-across-languages.htm...
| briefly sketches other languages.
|
| [1] https://docs.raku.org/language/quoting [2]
| https://docs.julialang.org/en/v1/manual/metaprogramming/#met...
| hbrn wrote:
| qw was great. Even in Python I sometimes write:
| usernames = ''' foo bar baz hello world
| '''.split() # instead of this, which needs too many
| keystrokes usernames = ["foo", "bar", "baz", "hello",
| "world"]
|
| Interestingly, Python named tuples have similar interface for
| fields: # all of these are equivalent
| EmployeeRecord = namedtuple('EmployeeRecord', ['name', 'age',
| 'title']) EmployeeRecord = namedtuple('EmployeeRecord',
| 'name, age, title') EmployeeRecord =
| namedtuple('EmployeeRecord', 'name age title')
| dariusj18 wrote:
| That's actually a great point. Perl has so many syntactic
| sugars that it is a poster child for too much variety in ways
| you can write things. Making it much harder to read someone
| else's code.
| masklinn wrote:
| Perl has so much syntactic sugar it's the poster child for
| syntactic diabetes, really.
| duped wrote:
| > Second, what if parameter blocks were abstractable?
|
| Sounds like a great way to make unreadable code
| #define STANDARD_EXP_PARAMS ... // I hit gotodef on
| func(...) and got here. What are the arguments? void func
| (STANDARD_EXP_PARAMS) { // ... long function body ...
| x = y; // What is the type of x? Is it an argument or a local?
| }
|
| I'd really be irritated if someone ever used this feature,
| optimizing for lines written is shaky territory to begin with.
| When it's at an interface boundary it's not excusable. NB4 "use
| an IDE" - requiring an IDE to make code legible is dumb, and I'm
| an IDE shill!
| robertlagrant wrote:
| You could say the same about a function call or a function that
| takes an object as an argument - who knows what code lies
| behind the impenetrable barrier of structured or object
| oriented programming?
| masklinn wrote:
| > If you look at something like numpy functions, so many of them
| share the exact same parameter definitions. What if you could
| write def log(standard-exp-params) instead of having to write
| them out every single time?
|
| They're not actually written out every time, the issue is mostly
| documentary (and it would be nice if Python or Sphinx ever had a
| good solution). And numpy actually has a bunch of generators for
| that e.g.
| https://github.com/numpy/numpy/blob/45bc13e6d922690eea43b9d8...
| handles filling in the common bits of documentation for the
| ufuncs.
| PyWoody wrote:
| I don't use Numpy but it sounds like they're describing *, **
| operators. default_args = ('x', 'y', 'z')
| default_kwargs = {'p': 'p', 'q': 'q', 'r': 'r'}
| def printer(x, y, z, /, *, p, q, r): print(f'x={x} |
| y={y} | z={z} | p={p} | q={q} | r={r}')
| printer(*default_args, **default_kwargs) # x=x | y=y | z=z |
| p=p | q=q | r=r
|
| EDIT: Formatting.
| masklinn wrote:
| Yes but also that lacks most of the documentation so it's not
| great.
|
| If you have multiple callables taking these parameters
| documenting them is awkward, by default help/pydoc and sphinx
| will tell you that the parameters are `default_args` and
| `default_kwargs`, but that's not actually true, those are
| just intended as shortcuts / helpers .
| davidhyde wrote:
| > You can write # 2001-08-12 # to mean the date 2001-08-12,
| instead of writing something annoying like Date(2001, 8, 12)
|
| I like this article but oh man dates just trigger me. Such a
| missed opportunity to use an unambiguous date example like
| 2001-08-13
| iwsk wrote:
| It would have been unambiguous in a world where we all agreed
| that both months and days start at 0 :)
| unnouinceput wrote:
| Also don't forget about year 0 (which doesn't exists and is a
| single point of failure for so many programs that deal with
| calculating time between now and a BC date)
| jakeinspace wrote:
| Year-day-month would be a truly cursed assumption for a format
| seanalltogether wrote:
| I really like swift's simple way of collapsing try catch blocks
| down to a simple value|nil result. let data =
| try? aFuncThatThrows()
|
| Sometimes I just don't care about the reason for the exception
| and just want to know if it succeeded or not
| pishpash wrote:
| Great, introduce slang to languages, what could go wrong?
| jedisct1 wrote:
| Slang itself is a language: https://www.jedsoft.org/slang/
| [deleted]
| ivan_gammel wrote:
| More sugar for reflection: class Foo {
| @Max(10) int bar; @NonNull String name(); }
| var field = @Foo::bar; var max = @Foo::bar.Max; var
| method = @Foo::name; var foo = new Foo(); var name =
| method(foo); var bar = foo.field;
| btown wrote:
| > [In Chapel] there's the config keyword. If you write config var
| n=1, the compiler will automatically add a --n flag to the
| binary. As someone who 1) loves having configurable program, and
| 2) hates wrangling CLI libraries, a quick-and-dirty way to add
| single-variable flags seems like an obvious win. Letting people
| define configurable variables at their call site is incredibly
| valuable, even if you don't have compile-time support, and even
| if you're working on something not meant to be an isolated
| binary.
|
| At my startup, one our most beloved innovations is that you can
| write `resolve_config("foo", default="bar", request=request)`
| pretty much anywhere you'd normally hardcode a value or feature
| flag... and that's it.
|
| The first time it's seen in any environment, it thread-safely
| inserts-if-not-present the default value into a key-value storage
| that's periodically replicated into in-memory dictionaries that
| live on each of our app servers. Any subsequent time it's
| accessed, it's a synchronous key-value lookup in memory, with
| barely any overhead. But we can also configure it in a UI without
| needing a code redeploy, and have feature flags and overrides set
| on a per-user or per-tenant basis.
|
| Sometimes, you don't need language support if you have some
| clever distributed-systems thinking :)
| masklinn wrote:
| > The first time it's seen in any environment, it thread-safely
| inserts-if-not-present the default value into a key-value
| storage
|
| That seems like a great way to get amazingly hard to replicate
| bugs or odd behaviours if different subsystems use different
| values for the default.
| elcritch wrote:
| You'd just need to have a mutex lock on the values. That
| could be slow, but you probably don't want to have a config
| flag in a hot loop anyways. :)
|
| In Nim you can do compile flags which let you set constants
| so you avoid the problem: const
| myLibraryVersion {.intdefine.} = 3
| masklinn wrote:
| > You'd just need to have a mutex lock on the values.
|
| Oh no they're saying that it's thread-safe, that's not an
| issue. Rather that depending on the order of
| initialisation, possibly of different systems entirely, you
| can have different initial states because different systems
| or subsystem decided of the default value.
| happyrock wrote:
| I have long wondered why Ruby's symbols aren't in every language.
| RodgerTheGreat wrote:
| They exist in K/Q. A single-word identifier-shaped symbol
| begins with a backtick, or a multi-word symbol can be created
| with a backtick and double quotes. A sequence of symbols is a
| vector literal, and is stored compactly. For example:
| `apple `"cherry pie" `one`two`three
|
| Many languages will intern string literals implicitly, or allow
| a programmer to explicitly intern a string; for example Java's
| "String.intern()".
|
| The problem with string interning, especially for strings
| constructed at runtime, is that for the interning pool to be
| efficient it is very desirable for it to be append-only, and
| non-relocatable. A long-running program which generates new
| interned strings on the fly risks exhausting this pool or
| system memory.
| masklinn wrote:
| > A long-running program which generates new interned strings
| on the fly risks exhausting this pool or system memory.
|
| So does a long-running program which generates new symbols on
| the fly.
| masklinn wrote:
| Because in most languages they're not useful. Symbols are
| solutions to problems, some of which are:
|
| 1. mutable strings (ruby)
|
| 2. and / or expensive strings (erlang, also non-global)
|
| If you have immutable "dense" strings and interning, and you
| automatically intern program symbols (identifiers, string
| literals, etc...) then symbols give you very little.
|
| And then there's the slightly brain damaged like javascript,
| where symbols are basically a way to get some level of
| namespacing to work around the dark years of ubiquitous ad-hoc
| expansions so you're completely stuck unable to add new program
| symbols to existing types because you could break any page out
| there doing something stupid.
| amalgamated_inc wrote:
| Both Lisp and Erlang have them, so they're much older than
| Ruby.
| revskill wrote:
| Is it for performant reason ?
| onei wrote:
| Symbols in Ruby are meant to be more performant that strings
| iirc. If I have symbol :a, then it's allocated once
| regardless of how many time it appears. As opposed to "a"
| which is reallocated every time.
|
| I guess it's similar to Python having a single instance of
| small integers. PlayStation also experimented with caching
| small floats which gave them some perf improvements too, but
| I think wasn't as performant in all cases.
| justincormack wrote:
| Lua (and some other languages) intern strings, so all
| strings that are the same point to the same string
| instance. This gives the same benefits (plus string
| equality is just pointer equality) without a different
| type.
| pawelduda wrote:
| There is a caveat in older Ruby versions that they aren't
| garbage collected, so they shouldn't be used for things
| like user input. Not a problem since 2.2 though.
| progval wrote:
| Symbols can even improve performance. Replace them with
| integers at compile time like a global enum, and so the
| runtime only needs to compare integers instead of potentially
| lengthy (especially if UTF-16) strings.
| masklinn wrote:
| > Replace them with integers at compile time like a global
| enum, and so the runtime only needs to compare integers
| instead of potentially lengthy (especially if UTF-16)
| strings.
|
| All of those strings will be interned, and can thus be
| compared by identity. Which is an integer comparison.
| AnthonBerg wrote:
| kebab-case
|
| enkebab-case
|
| emkebab--case
|
| These use dash, en dash and em dash, respectively. Most languages
| that allow you to use a decent amount of Unicode in variable
| names probably will accept that kind of kebab-case.
|
| Those dashes look pretty similar to each other in monospaced
| fonts but not indistinguishable, so it's readable and not super
| confusing. Might work. Why not?
| surfsvammel wrote:
| I love all the sugar of Kotlin, but there is one thing I miss,
| and it is the indexing of arrays of Python when pulling out part
| of an array.
| amalgamated_inc wrote:
| Kotlin is so close on so many things, but then keeps messing
| up.
|
| arrayOf(1, 2, 3) - why not [1,2,3]?
|
| emptyArray() - why not []?
|
| mapOf("key" to "value") - why not {key => value}?
|
| These are all solved problems. Why would they make up some
| harshly suboptimal syntax?
| kaba0 wrote:
| I am not that familiar with Kotlin, but these seems better
| than the syntax primitives from a language design perspective
| (I greatly recommend the "Growing a language" presentation
| done by Guy Steele), these are ordinary functions that are
| well-known from other parts of the language, not an added
| "hack" that has a one-off use. If you were to use a
| concurrent hashmap implementation you no no longer can use
| the syntactic sugar, and writing against an implementation is
| quite common in Java (which plays quite a big role in the
| design of Kotlin), e.g. having a List in the interface,
| instead of ArrayList.
| amalgamated_inc wrote:
| Interesting, I think the exact opposite :)
| rwmj wrote:
| Perl's if and unless operators, which can also be postfixed, eg:
| die "can't be negative" unless $i >= 0;
|
| Perl is full of usability features, like <> for reading input,
| inline literate coding annotations, implicit $_.
| jedisct1 wrote:
| Comptime.
|
| After having discovered Zig, I've been missing that feature in
| every other language.
| amalgamated_inc wrote:
| Compare to lisp macros?
| AnIdiotOnTheNet wrote:
| And error unions with corresponding semantics. And explicit
| casting requirements. And @TypeInfo. And no hidden allocations.
| And probably like 4 or 5 other things I'm not thinking of right
| now.
| Deukhoofd wrote:
| Comptime is great, but I wouldn't classify it as a
| microfeature. It'd fall more in the second category the author
| defines.
| [deleted]
| gavinhoward wrote:
| For me, yes and no.
|
| Sure, comptime is great, but I've also found it hard to reason
| about code with it. I prefer my comptime stuff separated out
| into its own section/file/whatever. With that small change, it
| becomes _so_ much easier.
|
| But yeah, still powerful and nice.
| lullab wrote:
| I'd like to see the "in" operator from SQL in C-style languages.
|
| Something like: if(x in (null, 2, 3.14, foo(123))
| { // }
| duped wrote:
| Python's `with`
|
| Function composition operators eg a |> b # Call
| `b` with `a` as an argument b <| a # Same as above,
| reversed direction
|
| Then you can do something like let x : Map =
| collect <| [a, b, c] |> map(entries)
| |> flatten
| masklinn wrote:
| > Python's `with`
|
| It's very common already: try-with-resource (java), using (C#),
| bracket (haskell), unwind-protect (common-lisp), ... though it
| the latter two it's more of a building block.
|
| Also building block: languages with a convenient and
| "unrestricted" syntax for anonymous function can just use that
| e.g. Smalltalk, Ruby, ... in Ruby a "with" is usually just
| passing a block to the corresponding object's constructor:
| # python with open(...) as f: ...
| # ruby File::open(...) do |f| ... end
| jbverschoor wrote:
| I was under the impression that Elixir introduced this.
| masklinn wrote:
| Elixir has forward pipes, but didn't invent them.
|
| For instance Racket and Clojure have _threading macros_ ,
| which are more flexible as they're just macros (Clojure's
| `->` is equivalent to Elixir's pipe operator, but `->>` will
| fill in the _last_ parameter rather than first, and `-- >`
| lets you use a keyword to define where the parameter is
| inserted in each call).
|
| Haskell let anyone who wants define their own pipe operator,
| historically you had to BYO, which wasn't exactly hard:
| (|>) = flip ($)
|
| or x |> f = f x
|
| would do (modulo fixity), but today it's provided by default
| as "(&)".
| whatshisface wrote:
| I don't get it, how are |> and <| any different from
| parentheses?
| mikepurvis wrote:
| Chaining without nesting.
| whatshisface wrote:
| If you mean without closing parentheses, I think you can
| also do that in languages like Haskell with non-
| parenthetical function calls.
| mikepurvis wrote:
| I'm not a Haskell user, but my experience with this in
| the Nix language is a bit mixed. It definitely works
| _sometimes_ , but then you get a pileup of parenthesis
| nesting anyway, because the default is greedy and you
| have to control which functions get which arguments.
| andix wrote:
| I want more implicit typing in typed languages. Quite often the
| compiler knows exactly what type a function will return, but I
| still need to write it there. Sometimes it's easy (,,int"), but
| what about HashSet<Immutable<Tuple<int,string>>>
|
| Typescript does it well. F# (completely statically typed) too...
| kaba0 wrote:
| Nowadays even Java will do that, var list =
| yourFunctionReturningThatSet();
| andix wrote:
| Also on a function? var getDate() { return
| ,,no date"; }
| masklinn wrote:
| That is often considered undesirable because it makes code
| less clear and compilation errors inscrutable.
|
| For instance the rust developers consciously decided to
| remove that from the language, named functions must be
| fully typed.
| ivan_gammel wrote:
| No, but I think that was a good choice. In local code this
| is great, but in an interface contract being explicit about
| data type is a virtue.
| andix wrote:
| For interfaces this would obviously not work (there is no
| implementation). But for class members it would.
|
| I think this concept works very well in typescript and
| F#.
| mikepurvis wrote:
| Rust does a pretty decent job of this, particularly around
| functions that return collect().
|
| But yeah, C++ has a ways to go on type inference.
| pgorczak wrote:
| Clojure's loop expression hits this spot for me. It sets a
| recursion point to which you can jump using any logic inside the
| body you want, as long as it is from tail position. It's like a
| while loop turned into an expression. I haven't encountered any
| other way to write iterative expressions whose number of
| iterations isn't known at the top (like map and reduce).
| japanman425 wrote:
| for x in y: yield x
|
| Job done
| tremon wrote:
| Also known as (in Python, that is): yield
| from y
| masklinn wrote:
| They were providing a partial example, "yield from" does
| not actually do what the original poster asks about, it
| merely proxies the inner iterable.
| bogdanoff_2 wrote:
| Is that like using "continue" in most C-syntax languages?
| wging wrote:
| Tail call optimization can get you that, too. If you've written
| Scheme and/or gone through SICP you might be familiar with
| this: you write a recursive function, with the recursive
| function call as the last thing the function does ('tail-
| recursion'), and the compiler/runtime is able to optimize those
| recursive calls out rather than consuming one stack frame of
| space per call ('tail call optimization'). Clojure has
| loop/recur at least partially because it _doesn 't_ support
| tail-call optimization.
|
| See https://en.wikipedia.org/wiki/Tail_call for more. Or SICP
| might be a good resource.
| https://sarabander.github.io/sicp/html/1_002e2.xhtml
| dragonwriter wrote:
| TCO also, unlike special syntax for direct tail recursion,
| works when the last call is _not_ (directly) recursive (which
| supports indirect /mutual recursion, and just structures with
| deep call heirarchies that aren't necessarily recursive.)
| amalgamated_inc wrote:
| Interestingly, I almost prefer Clojure's `recur`
| semantically. Means you don't have to change the function
| name twice if you rename it, and it's hard to miss that
| you're recursing.
| wging wrote:
| Those _are_ cool properties. Another one is that you get a
| compilation error if your recursive call isn 't in the tail
| position (and thus would actually grow the stack when you
| thought it didn't).
|
| One thing I don't think you can do with loop/recur, though,
| is optimize more complicated bits of recursion than a
| single function that calls itself. I.e. imagine a recursive
| call pattern that goes like f -> g -> f -> g -> ...
|
| (edit: I'm pretty sure this is why trampoline exists,
| though I've never really played with it...
| https://clojuredocs.org/clojure.core/trampoline)
| masklinn wrote:
| If that's your worry then you can probably use the site's
| namesake. Though simple recursion is generally easy to
| spot.
| amalgamated_inc wrote:
| Which site's namesake? Hacker News?
| masklinn wrote:
| The Y combinator.
| amalgamated_inc wrote:
| Oh is this some Common Lisp thing? Never done it.
| masklinn wrote:
| It's much older, it's lambda calculus stuff. It's a way
| to implement recursion in a language which doesn't have
| recursive functions (but for some reason does have first-
| class functions).
|
| However it allows making anonymous functions recurse as
| well.
| 613style wrote:
| It's also nice to get an error when you `recur` from a non-
| tail position rather than the function just quietly
| becoming truly recursive.
| masklinn wrote:
| > I haven't encountered any other way to write iterative
| expressions whose number of iterations isn't known at the top
| (like map and reduce).
|
| `unfold`, Rust's `loop`, generators, working tail recursion
| elimination (the lack of which loop/recur is a workaround for)
| light_hue_1 wrote:
| Haskell has almost all of these.
|
| > Instead of writing 10000500, you can write 10_000_500, or
| 1_00_00_500
|
| https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/nume...
|
| > Balanced string literals
|
| https://hackage.haskell.org/package/raw-strings-qq-1.1/docs/...
|
| > Generalized update syntax
|
| Use Lens. `fileName %~ max 2`
|
| > you can write the sequence 1, 2, ... n-1 as 1..<n.
|
| Yup. `[1,2..n-1]` There's far more to it, you have access almost
| a SQL-like sublanguage.
|
| > Symbols
|
| In Haskell you use hash has a prefix instead of colon.
|
| Haskell sadly does not do automatic lifting, no extended
| parameter blocks, and no kebab-case.
| unhammer wrote:
| >> Symbols
|
| > In Haskell you use hash has a prefix instead of colon.
|
| Can you give an example?
| purplie wrote:
| Declaration of units for primitive numeric variables.
|
| Example: <https://github.com/mchrisman/variables-with-units-
| language-p...>
| xigoi wrote:
| My favorite is uniform function call syntax. In several languages
| (Nim, Koka, D, ...), you can _always_ write bar.foo(baz) instead
| of foo(bar, baz) and vice-versa.
|
| Another one from Nim is the implicit result variable. Instead of
| having to do this: func sum(nums: seq[int]):
| int = var result = 0 for num in nums:
| result += num return result
|
| you just do this: func sum(nums: seq[int]): int
| = for num in nums: result += num
|
| It saves so much time and I'm disappointed that more languages
| don't have it.
| tremon wrote:
| _and vice-versa_
|
| Doesn't the reverse pollute the function namespace? If every
| obj.fun() can be written as fun(obj), doesn't that cause
| ambiguity with a previously imported global function fun()?
| __jem wrote:
| In Rust, it's still qualified by the type you're calling it
| on, which I assume is also the case in other languages.
| masklinn wrote:
| > which I assume is also the case in other languages.
|
| Nope. Rust has an extremely restrictive form of UFCS. In
| fact officially it's _not_ called UFCS, but "Fully
| Qualified Path syntax".
| Yujf wrote:
| In rust you can do this but they are namespaced with the name
| of the struct. So you can do Object::fun(obj)
| lalaithion wrote:
| If you have two functions abracadabra(foo) and
| foo.abracadabra() which _do different things_ , you should
| rename one of those functions tbh.
| feoren wrote:
| The problem is when you have the functions:
|
| Square.computeArea()
|
| Circle.computeArea()
|
| Clearly these should do different things. I suppose
| "computeArea(shape)" does dynamic dispatch based on the
| type of shape? But you're still putting every function
| defined on every type in your entire codebase in a global
| namespace. It's not _obviously_ awful but I 'd definitely
| be a bit nervous about it.
| masklinn wrote:
| > doesn't that cause ambiguity with a previously imported
| global function fun()?
|
| Yes, although overloading can mitigate the issue.
| elcritch wrote:
| Yah I ran into that issue more in Julia. There's a built-in
| function to help find clashing functions.
|
| In Nim however I've only had it happen a handful of times
| in a few years. Then you just need to use the module name
| to qualify it, or change your imports.
| AnimalMuppet wrote:
| That sounds like no fun() at all.
|
| Right, I'll show myself out...
| andix wrote:
| I'm not so sure about that. Did you ever check out extension
| methods in C#, they are a bit like what you're describing, but
| not so radical.
| barrkel wrote:
| Implicit result var is also in Delphi, as Result. The original
| Pascal is to assign to a variable with the same name as the
| function, which looks kind of odd, and is overloaded in
| recursive scenarios, since functions with no args don't need
| parens to invoke.
| onei wrote:
| I think you can also do the second in Go with named returns,
| e.g. func sum(nums []int) (result int) {
| for _, n := range nums { result += n
| } }
|
| No clue if it's an idiomatic usage, and named returns always
| felt a little too magic for me.
| mikepurvis wrote:
| Lots of languages will implicitly return the final
| expression-- I feel like that's a decent compromise. Not
| quite as magical as an actual named variable that just
| exists, but not as clunky as needing as explicit `return`
| every time.
| zimpenfish wrote:
| It's kinda fine if it's a short function like that[1]. When
| you get above 10-15 lines in a function though, it's easy to
| lose track of what's a return variable and what isn't.
|
| [1] _and_ everything in the codebase uses that style
| otherwise it 's annoying to have to context-switch every 5
| minutes.
| michaelsbradley wrote:
| There are some cons to using `result`:
|
| https://status-im.github.io/nim-style-guide/language.result....
| devmunchies wrote:
| # Function Shorthand
|
| I like how functions in js can be `arg => result`. In F# I have
| to do `fun arg -> result` with the `fun` keyword. It makes sense
| since `MyArgType -> MyResType` is a type signature in f#, but I
| feel like the compiler can just check if the arguments are
| references to types or are argument bindings.
|
| # Multiline Lists/Arrays
|
| I like how F# doesn't require delimiters for multiline lists.
|
| So I can do `let myList = [1; 2; 3]` or let
| myList = [ 1 2 3 ]
|
| # Regex Literal
|
| I like how in Crystal instead of doing `/my[regex]/` i can do
| `%r(my[regex])` where the parenthesis can be any brace type (like
| "(", "{", "<", "[") so I don't have to escape any characters.
| dan-robertson wrote:
| The function syntax I like even more is one with implicit
| arguments so you don't have to name them, e.g.
| waiting = sum workers #(%.in_queue + %.in_flight)
|
| Clojure has some syntax like this though it isn't needed for
| the most obvious use-case of functions to extract fields
| because keywords, which are usually used for map keys, are
| implicitly functions that look themselves up in their arg, e.g.
| (:bar { :foo 3, :bar 2 }) ; => 2
| masklinn wrote:
| > I feel like the compiler can just check if the arguments are
| references to types or are argument bindings.
|
| 1. this is absolutely terrible because now you need feedback
| from the type checker to know how to parse the program
|
| 2. it is furthermore also ambiguous with function _application_
| , requiring arbitrary lookahead to disambiguate, also not a fun
| thing to do
|
| JS gets away with it because the sigil was not previously used
| and it only requires a single lookahead to parse, as only
| single-parameter anonymous functions can have "bare" parameter
| lists.
___________________________________________________________________
(page generated 2023-01-05 23:01 UTC)