[HN Gopher] Elixir - Why the dot when calling anonymous functions?
       ___________________________________________________________________
        
       Elixir - Why the dot when calling anonymous functions?
        
       Author : weatherlight
       Score  : 189 points
       Date   : 2023-08-14 15:16 UTC (7 hours ago)
        
 (HTM) web link (dashbit.co)
 (TXT) w3m dump (dashbit.co)
        
       | ezekg wrote:
       | You can do the same thing in Ruby, although I think it's cursed:
       | fn = -> x { puts "hello #{x}" }         fn.('world')
       | 
       | In Ruby, .() is an alias to #call.
        
         | josevalim wrote:
         | And because in Ruby it is a shortcut, you can invoke it on any
         | object:                   irb(main):008:0> class Integer
         | irb(main):009:1> def call(b); self.+(b) end
         | irb(main):010:1> end         => :call         irb(main):011:0>
         | 1.(2)         => 3
         | 
         | :D
        
       | behnamoh wrote:
       | People have argued that Elixir could replace Python, but tbh,
       | Elixir's syntax is too ugly to replace something as elegant and
       | simple as Python.
        
         | nvarsj wrote:
         | I prefer Erlang to Elixir but I think I'm in the minority.
         | Rubified Erlang has its place I suppose.
        
         | boxed wrote:
         | Imo that's not Pythons great strength, although I like the
         | syntax. Python is great because of the C interop. FFI in Java
         | is horrible, and I assume it's not great in Erlang/Elixir
         | either. In Python? It's pretty damn great. Which is why Pythons
         | library ecosystem is so enormous.
        
           | freedomben wrote:
           | Elixir C interop is actually pretty pleasant. After Java I
           | was expecting it to be a nightmare, but it was shockingly
           | easy and well though out.
           | 
           | The big thing in Elixir community right now is to write the
           | native/performance code in Rust[1] and interop with Elixir.
           | 
           | [1]: https://github.com/rusterlium/rustler
        
             | paradox460 wrote:
             | There are equivalent libraries for zig and Nim too
        
           | josevalim wrote:
           | Erlang has a well defined and clear layer for integrating
           | with C code, including functionality to help you ensure the
           | code you plug into will be thread-safe and work with the VM's
           | underlying concurrency models:
           | https://www.erlang.org/doc/man/erl_nif.html
           | 
           | Erlang does not have low-ceremony ways of calling C from
           | Erlang, as I believe Python has. It could be done but that
           | would be frowned upon anyway, because it would defeat the
           | preemptive and fault-tolerant guarantees of the runtime.
           | 
           | So I guess it depends on what you mean by great. FWIW, Elixir
           | has no problems integrating native code from XLA, LibTorch,
           | Polars, that also powers equivalent libraries in Python.
        
         | giraffe_lady wrote:
         | "i'm not falling for that hot take. that's clearly someone with
         | a fetish for getting yelled at. i refuse to participate in that
         | kind of perversion"
        
         | sph wrote:
         | I mean, have you looked at Python's syntax for closures? You
         | call that elegant?
         | 
         | Any high-level language that is afraid of map/reduce and other
         | functional constructs is a waste of time to me.
         | 
         | Python has a lot of mindshare because it _looks_ simple, but by
         | God if it isn 't an absolute kitchen sink of a language full of
         | weirdnesses and gotchas. And still, completely afraid of
         | functional constructs.
         | 
         | Nice bait though.
        
           | boxed wrote:
           | > I mean, have you looked at Python's syntax for closures?
           | You call that elegant?
           | 
           | You mean anonymous functions surely? The syntax for closures
           | is basically nothing, you just make a function.
        
           | bPspGiJT8Y wrote:
           | > Any high-level language that is afraid of map/reduce and
           | other functional constructs is a waste of time to me
           | 
           | Elixir is afraid of most "functional concepts".
        
         | Xeamek wrote:
         | Simple? Sure.
         | 
         | Elegant though...
        
         | [deleted]
        
         | innocentoldguy wrote:
         | I'd have to disagree. Having used Python professionally since
         | the mid 90s and Elixir since 2015, I think Elixir's syntax is
         | cleaner and easier to read. I also think language features like
         | pattern-matching, guard clauses, immutable data, green
         | processes, and a complete lack of OOP make Elixir a great
         | language to work with.
        
         | matsemann wrote:
         | I'll bite: Impossible to write pure functional style code in
         | python, hence it loses any elegance contest right out of the
         | gate. ;)
         | 
         | I work in a python shop now, and the code I've written the last
         | years must be the ugliest of my career. If it's too complicated
         | to be solved with a list comprehension, it will be so much
         | uglier than lambdas in a language with a form of piping.
        
           | boxed wrote:
           | > Impossible to write pure functional style code in python
           | 
           | What does that even mean?
        
             | behnamoh wrote:
             | They probably mean you need to import some modules to do
             | things like partial functions. But it's not like functional
             | programming is not possible in Python. I'd argue that
             | Elixir's syntax is more of hindrance to FP than Haskell's
             | elegant syntax.
        
               | matsemann wrote:
               | No, if importing something was enough it would be fine.
               | It's more that how a lambda can only contain a single
               | expression means you need to split and name things
               | everywhere, how you can't pipe/chain things means you
               | need to have intermediate variables or an ungodly amount
               | of nesting etc. Yeah, technically feasible, but it's not
               | "pythonic" so you solve it some other (to me) less
               | elegant way.
        
               | bPspGiJT8Y wrote:
               | > It's more that how a lambda can only contain a single
               | expression
               | 
               | That's exactly how lambdas (and all functions) work in
               | actually functional languages like Haskell. And it's a
               | good thing,lambdas shouldn't allow statements.
               | 
               | > how you can't pipe/chain things
               | 
               | Ironically after switching from Elixir to a language
               | where I can have any operator I wish, I found myself
               | using the equivalent of Elixir's |> only on a very rare
               | occasion. IMO they're vastly overrated.
        
             | regulation_d wrote:
             | When I first started writing Elixir, I had no idea how much
             | I would come to love immutable data as a language feature.
             | "functional" can mean so many things, but for me immutable
             | data as part of the runtime (i.e. not bolted on later) is
             | one of the most important things. Never having to ask: did
             | I pass by reference or value? Never having worry about data
             | changing out from underneath you. It's pretty amazing.
             | 
             | Sure there are times that a need for performance calls for
             | mutable data. But for me those times are pretty rare, and
             | when they happen it's usually easy enough to quarantine the
             | mutation.
        
       | lvass wrote:
       | I've barely used the dot syntax since Kernel.then was added. It's
       | good that it exists even if you never use it.
        
         | conradfr wrote:
         | Yes the syntax to pipe the first argument to an anonymous
         | function for another function and use it as second+ argument
         | was akward and hard to remember.
         | 
         | Like:
         | 
         | "hacker news" |> (&Map.put(:site, "ycombinator", &1)).()
        
         | paradox460 wrote:
         | Then is really nice.
         | 
         | I always used to write "clean" pipelines in my gen server and
         | such, that ended in an anon function to return the needed tuple
         | for the respective handlers. Inevitably we'd get a squabble in
         | the code review, where some wanted everything put in a variable
         | then the explicit tuple at the end, while others agreed that
         | the dot syntax, while a bit odd and line-noisy, was ultimately
         | better
         | 
         | Then solved that. The line noise complaints went away, code
         | readability went up, and everyone was happy
        
       | colonwqbang wrote:
       | It seems like different issues are being conflated here. Should
       | function arity overloading be permitted, and when? Should we
       | require a specific syntax for calling locally defined "anonymous"
       | functions, for whatever reason? I don't see what these two things
       | have to do with each other.
       | 
       | (Haskell doesn't support function arity overloading, despite what
       | the article suggests. Haskell also doesn't use a special syntax
       | for calling local functions)
       | 
       | Other questions to ask: If we do permit overloading for toplevel
       | functions why not do it also for local functions? If we think a
       | special syntax is needed to use local variables of function type,
       | why not use the same syntax for local variables of all types?
       | E.g. $localvar, $localfunc() vs globalvar, globalfunc().
       | 
       | It doesn't feel fully thought-out.
        
         | josevalim wrote:
         | My goal was to say that Haskell has a single namespace, not
         | that it supports arity overloading, but your interpretation is
         | valid as the text is currently phrased. I will address that.
         | However, I don't believe it says Haskell has a special syntax
         | for local calls.
         | 
         | The arity overloading is related because it adds to the
         | expressive power of Lisp 1. As the examples show, you need a
         | single function for defining the initial accumulator and the
         | reduction operation, instead of passing two arguments or a
         | composite type (see this example from Clojure [1]). By allowing
         | a single function to encode more information via multiple
         | arities, you also only need a single override (which must
         | adhere to all arities). It is also an important characteristic
         | of Elixir, so the article would be lacking if it was not
         | included in the discussion.
         | 
         | [1]: https://clojure.org/reference/reducers#_using_reducers
        
       | bnchrch wrote:
       | For those of you who are interested by Elixir but find the lack
       | of static typing an issue here are somethings to be aware of:
       | 
       | 1. Static Typing is planned and currently the top priority of the
       | team
       | 
       | https://elixir-lang.org/blog/2022/10/05/my-future-with-elixi...
       | 
       | 2. There is a type checking tool
       | 
       | https://github.com/jeremyjh/dialyxir
       | 
       | 3. You can go a long way with pattern matching and guides in the
       | meantime and have alot more guarantees that a typical dynamic
       | typed language.
        
         | rdevsrex wrote:
         | Interesting, will check out. I am still evaluating Gleam, a
         | language that's targets beam, but the compiler is written in
         | Rust. It has actual strong typing, and I rather like it's Rust
         | like syntax.
        
           | bnchrch wrote:
           | I really like Gleam, and I am a huge fan of more being built
           | on the EVM.
           | 
           | Personally I want the EVM to (eventually) get the same level
           | of adoption and mindshare as the JVM.
           | 
           | And currently wider Elixir use/education seems like the path
           | to that.
        
             | comex wrote:
             | EVM is the Ethereum Virtual Machine. You mean BEAM, right?
        
           | eggy wrote:
           | I was too until they strayed from the more Standard ML-like
           | syntax to Rust-like syntax all for trying not to alienate the
           | Algol crowd and Rust aficionados. For popularity. I like APL,
           | BQN, and J so I'm not swayed by it.
        
         | sbjs wrote:
         | I'm glad we're in the age of static typing. The age of dynamic
         | typing as a response to _incorrect_ static typing was a
         | necessary evil. We had to take a break from broken languages in
         | order to realize what needed to be fixed in those languages. I
         | 'm convinced that JavaScript will eventually be one of the
         | best, fastest, and safest languages, as long as we keep this
         | cycle up of abandoning necessary languages and returning to
         | them with very thorough fixes to much better understood and
         | detailed problems with them.
        
           | hosh wrote:
           | I'm convinced that until Javascript makes it easy to do the
           | right thing by default (instead of its current state of doing
           | many wrong things by default) the Javascript ecosystem will
           | continue to have a lot of low-quality library code intermixed
           | with a few high-quality ones. My neutral regard for JS had
           | rapidly degraded when I had to actually start writing code in
           | it or understanding code others have written.
        
             | sbjs wrote:
             | What ways does do the wrong things by default? I can't
             | think of a single one. You might say, well, == is broken
             | and you have to use ===, but that's not "the wrong thing by
             | default" unless you assume that == is what _ought_ to be
             | the default operator and === ought to be secondary. But
             | that assumption has to come from somewhere, and probably
             | comes from the fact that other languages do it that way.
             | But there 's nothing inherently incorrect about === being
             | the default operator and == being the rare secondary one. I
             | think the same principle applies to any example you might
             | be able to give me: if you assume that another language is
             | objectively right, only then can you say that JavaScript is
             | objectively wrong in comparison to that language which
             | apparently descended from the heavens.
        
               | wtetzner wrote:
               | I would argue that implicit conversions in a dynamically
               | typed language is a mistake in general. It's the "wrong
               | thing by default" in the sense that it's too easy to
               | write broken code by accident. And I think that's what is
               | usually meant by "wrong thing by default".
               | 
               | I think if you _really_ want implicit conversions, then
               | you want to do what Perl does (never thought I 'd say
               | that): different operators for different types.
        
               | airstrike wrote:
               | Exhibit A: https://www.destroyallsoftware.com/talks/wat
        
               | mjburgess wrote:
               | You're assuming a certain sort of perspectivalism: the
               | only reasons X could be better than Y is that, from
               | someone's pov, some language has X.
               | 
               | This is kinda insane. People can give reasons X is better
               | than Y on grounds of various theoretical virtues of X --
               | that has nothing to do with a preference for any
               | language.
               | 
               | Here's an example virute:
               | 
               | Behaviour should be consistent unless specialised. When
               | specialised it should be obvious which specialisation is
               | chosen.
               | 
               | (Violation: basically all of js' operators).
        
               | claytongulick wrote:
               | It's pretty easy to footgun when you're mixing
               | event/callback based flow with promises/async.
               | 
               | It's really easy to forget a closure in an event driven
               | function and to produce difficult to debug, error prone
               | code.
               | 
               | A classic example I just screwed up:
               | export function getSFTPConnection(options) {
               | let key = readFileSync(options.keyfile_path, 'utf8');
               | let ssh = new ssh2.Client();             return new
               | Promise(                 (resolve, reject) => {
               | ssh.on('error', reject);
               | ssh.on('ready', () => {
               | ssh.sftp((err, sftp) => {
               | resolve(sftp);                         });
               | });                     ssh.on('close', (err) => {
               | reject(new Error('SSH connection closed: ' + err));
               | })                     ssh.connect(
               | {                             host: options.host,
               | username: options.username,
               | privateKey: key,                         }
               | );                 }             )         }
               | 
               | The bug here isn't obvious, at least to me - but it
               | caused me much heartache and pain because I was moving
               | too fast and not thinking - but a good example of
               | unexpected behavior by default.
               | 
               | The bug is that there's not a closure around the ssh
               | instance, so if you call this function multiple times
               | it'll actually return the same connection instance.
               | 
               | The extra fun part of this bug is that the code worked -
               | but because I was using the same instance, when I'd
               | download multiple files in parallel, it would interleave
               | the data, corrupting the files.
               | 
               | And the thing is, I _know better_. I 've been doing js
               | for decades.
               | 
               | I love js, but stuff like this is pretty frustrating, and
               | can be a nightmare for new devs.
        
               | minitech wrote:
               | > The bug is that there's not a closure around the ssh
               | instance, so if you call this function multiple times
               | it'll actually return the same connection instance.
               | 
               | I'm not entirely sure what you mean by this, but it
               | doesn't sound like a correct diagnosis. Each call to
               | `getSFTPConnection` creates a new `ssh2.Client` instance
               | (unless `ssh2.Client`'s constructor does something
               | weird), and the promise can only resolve with the value
               | `ssh.sftp` passes (although there's a missing error
               | check).
        
               | david422 wrote:
               | Well === was added to make up for deficiencies in == so
               | seems reasonable that saying == is the default that does
               | things incorrectly.
        
               | bee_rider wrote:
               | == was already a silly operator, === is just ridiculous.
        
           | frou_dh wrote:
           | Maybe it's just me but there's something fundamentally
           | unappealing about bolt-on static type-systems on top of pre-
           | existing dynamic languages. Sure, what's come about with e.g.
           | TypeScript is pragmatic and very useful, but if imagining a
           | future utopia then TS/JS++ and similar would not feature in
           | the dream.
        
             | lvass wrote:
             | It's a nightmare, not a dream, if the language is mutable.
             | Mutability is the worst and least fixable issue with almost
             | every popular language today.
        
             | ecshafer wrote:
             | I don't really like Typescript, I think I would rather just
             | code in Javascript. Typescript adds a lot of structure and
             | tries to shoehorn more of a Java like programming style. A
             | lot of the type checks seem unnecessary. If I am going to
             | use types I would rather use Fable or ReasonML, something
             | that really just has types from the start.
        
             | sbjs wrote:
             | Why not? I can't think of anything off the top of my head
             | that I think is fundamentally broken about TypeScript, or
             | even that I strongly dislike about it.
        
               | LadyCailin wrote:
               | Its type system is apparently Turing complete. That's a
               | pretty major flaw, imo. Meaning that static analysis
               | isn't always possible.
        
               | consilient wrote:
               | Turing complete type systems are extremely common. C#,
               | C++, Java - it's hard to avoid if you have subtyping and
               | generics. In practice it almost never comes up.
        
               | consilient wrote:
               | A small sample:
               | 
               | The existence of `any`; worse yet, the use of `any` _in
               | the standard library_.
               | 
               | Mutable arrays are treated as covariant: the classic
               | `cats : Cat[] ; animals: Animal[] = cats;
               | animals.push(dog);` problem.
               | 
               | Methods are both co- and contravariant in their arguments
               | by default, which is comically wrong. Member variables
               | which are functions, meanwhile, are handled correctly.
               | 
               | `readonly` is a lie:                   const test:
               | {readonly a: number } = {a: 0}         const test2: {a:
               | number} = test         test2.a = 5
               | 
               | `Record<string, string>` is actually `Record<string,
               | string | undefined>`. There's no way for an interface to
               | specify that it really does return a value at _any_
               | string index (e.g. for a map with a default value).
               | 
               | `{...object1, ...object2}` is typed as the intersection
               | `typeof object1 & typeof object2`, which is not correct.
               | const a: {a: 5} = {a: 5}         const b: {a: 4} = {a: 4}
               | const spread = <L, R>(l: L, r: R): L & R => ({...l,
               | ...r})         const impossible: never = spread(a, b)
               | 
               | You have to resort to bizarre conditional type hackery to
               | control when unions distribute and when they don't.
               | 
               | You can constrain generics as `<T extends string>foo(t:
               | T) => ...` but not `<string extends T>foo(t: T) => ...`,
               | which makes many functions (e.g. Array.includes) much too
               | strict about what they accept.
        
               | frou_dh wrote:
               | - Too many statements instead of expressions.
               | 
               | - Lacks structural pattern-matching.
               | 
               | - Lacks first-class sum-types (although they can be
               | clumsily encoded as objects with "kind" properties).
               | 
               | - Static type-checking not directly (or at all)
               | contributing to the execution performance of the code.
               | This is particularly egregious with e.g. CPython.
               | 
               | Presence of the first three are the sort of things that
               | people appreciate in Good static languages (as opposed to
               | the status quo static languages you alluded to that
               | Python/Ruby/JS/etc were rebelling against in the 2000s).
               | 
               | The last is the dead giveaway of not utopia but rather a
               | bizarre compromised situation.
        
               | wtetzner wrote:
               | Depending on who you ask, the fact that the type system
               | is unsound could be considered a form of brokenness.
        
         | arturkane7 wrote:
         | What's currently the best static typed language for web
         | development?
        
           | bnchrch wrote:
           | Typescript, by a large and deserved margin.
        
             | ak_111 wrote:
             | Should have added other than Typescript :)
        
               | bnchrch wrote:
               | Then I would vote Go (best stdlib in the business), then
               | Java.
        
             | weatherlight wrote:
             | It's not sound :(. Type systems should be bomb proof or get
             | out of the way.
        
           | giraffe_lady wrote:
           | ReScript is excellent. Same type system as ocaml, which is
           | IMO the sweet spot between power and straightforwardness. You
           | lose the "you can represent ANYTHING" capability of TS which
           | is both a pro and a con. In exchange it's much simpler to
           | work with and has excellent type inference for day to day
           | work.
           | 
           | The externals binding system for JS libs works well for
           | integrating libraries. And react bindings are included in the
           | standard lib, they also work great. The compiler is fast and
           | produces generally quite reasonable JS.
           | 
           | You could argue that it's not "better" than elm in a
           | theoretical sense. But for practical work it's much closer to
           | the mainstream of web dev in mindset and syntax. Easier to
           | learn, much easier to integrate with other frameworks or into
           | existing teams & codebases. And it's less opinionated about
           | rendering: the tooling generally assumes you're using react,
           | but you don't have to and it can emit anything if you do some
           | extra work wiring it up. I have used it with node and even,
           | irresponsibly, deno fresh.
           | 
           | Been using it whenever I can for frontend stuff for a year
           | now and haven't enjoyed a language this much since... well...
           | elixir.
        
           | consilient wrote:
           | Purescript has by far the strongest type system of any mature
           | compile-to-js language, good FFI, footprint comparable to
           | react.
           | 
           | Elm is a heavily stripped down purescript, plus an
           | ambiguously-benevolent dictator for life. Also truly
           | _terrible_ interop. It 's a good learning environment but I
           | would strongly recommend against using it for anything big.
           | 
           | Rescript is basically Ocaml with half-js syntax.
           | 
           | Typescript is bizarre. On a scale from 0 (C) to 10 (Haskell),
           | it's a 12, a 6, and a 2 duct taped together. It's got some
           | incredibly powerful features (template literals, conditional
           | types), but it's missing most of the standard "advanced"-but-
           | production-ready type system features (e.g. no HKTs, no type-
           | directed emit) and the foundation is absolutely riddled with
           | soundness holes.
        
             | bPspGiJT8Y wrote:
             | Also worth mentioning Idris, although its tooling and
             | ecosystem are still pretty much nonexistent.
        
               | consilient wrote:
               | Yeah, that's what I meant by "mature". Agda also has a js
               | backend, but neither one can really stand alone. Haskell
               | is closer, but GHCJS is still very much a second class
               | citizen of the ecosystem.
        
               | bPspGiJT8Y wrote:
               | Clean also has WASM as the compile target via its IR, and
               | its tooling is great. Unfortunately though I couldn't
               | find any sort of a community when I was looking into it,
               | but other than this the language looks quite mature and
               | very industry-oriented. Then there's Lean which also can
               | compile to WASM via some sort of a bridge, it has decent
               | tooling but the ecosystem is still nonexistent.
        
           | lmm wrote:
           | Few people on the web side know about it but it's secretly
           | Scala.
        
           | deathtrader666 wrote:
           | Rescript
        
           | ak_111 wrote:
           | The only popular choices: Typescript, Java, Go (although I
           | don't think it is that popular as a webapp backend language)
           | or C# (I think).
        
           | weatherlight wrote:
           | Rescript, 100%
        
         | choiway wrote:
         | I do like me some static typing but it took me a while to
         | realize that the guards and pattern matching provides runtime
         | guarantees and not just compile time guarantees.
        
         | [deleted]
        
         | callamdelaney wrote:
         | Everybody knows elixir is ass, just use Erlang.
        
       | sigwinch28 wrote:
       | Edit: this was meant to be a reply to
       | https://news.ycombinator.com/item?id=37122798 but I messed it up.
       | 
       | I don't see a good reason Elixir wouldn't allow it, since Erlang
       | (yes I know it's not Elixir) often compiles anonymous functions
       | ("Funs") into top-level definitions...
       | 
       | Here's some IR showing how a top-level fun from this Erlang
       | module...                   -module(foo).         -export([f/1]).
       | f(X) ->           G = fun (Y) -> X + Y end,           G(X * 4) *
       | G(X * 2).
       | 
       | ...gets converted into a top-level function called '-f/1-fun-0-':
       | {module, foo}.  %% version = 0                  {exports,
       | [{f,1},{module_info,0},{module_info,1}]}.
       | {attributes, []}.              {labels, 9}.
       | {function, f, 1, 2}.             [SNIPPED FOR HN]
       | {allocate,1,2}.             {move,{x,0},{y,0}}.
       | {swap,{x,0},{x,1}}.             {call,2,{f,8}}. % '-f/1-fun-0-'/2
       | {'%',{var_info,{x,0},[{type,number}]}}.
       | {gc_bif,'*',{f,0},1,[{tr,{y,0},number},{integer,2}],{x,1}}.
       | {move,{y,0},{x,2}}.             {move,{x,0},{y,0}}.
       | {move,{x,1},{x,0}}.             {move,{x,2},{x,1}}.
       | {call,2,{f,8}}. % '-f/1-fun-0-'/2
       | {'%',{var_info,{x,0},[{type,number}]}}.
       | {gc_bif,'*',{f,0},1,[{tr,{y,0},number},{tr,{x,0},number}],{x,0}}.
       | {deallocate,1}.             return.
       | {function, '-f/1-fun-0-', 2, 8}.           {label,7}.
       | {line,[{location,"foo.erl",5}]}.
       | {func_info,{atom,foo},{atom,'-f/1-fun-0-'},2}.
       | {label,8}.             {gc_bif,'+',{f,0},2,[{x,1},{x,0}],{x,0}}.
       | return.
        
         | throwawaymaths wrote:
         | If you stripped the arity how would you know how many register
         | need to be moved into the new call routine's register list, so
         | that reordering, clobbering, etc. at the call site are
         | optimized?
        
           | sigwinch28 wrote:
           | I am a bit rusty on the specifics of the BEAM's registers
           | (the VM that Erlang and Elixir run on) but IIRC the short
           | version is these are registers in a VM and the VM takes care
           | not to let them clobber.
           | 
           | Also, in the BEAM, intra-module and inter-module calls
           | operate differently.
           | 
           | The Erlang/OTP did a blog post on this a little while ago
           | which I think is great: https://www.erlang.org/blog/a-brief-
           | beam-primer/
           | 
           | Here is some useful info:
           | 
           | > BEAM is a register machine, where all instructions operate
           | on named registers. Each register can contain any Erlang term
           | such as an integer or a tuple, and it helps to think of them
           | as simple variables. The two most important kinds of
           | registers are:
           | 
           | > * X: these are used for temporary data and passing data
           | between functions. They don't require a stack frame and can
           | be freely used in any function, but there are certain
           | limitations which we'll expand on later. > * Y: these are
           | local to each stack frame and have no special limitations
           | beyond needing a stack frame.
        
             | throwawaymaths wrote:
             | > VM takes care not to let them clobber.
             | 
             | The Erlang VM absolutely clobbers registers within
             | functions all the time, since there are only 64? of them
             | and they are a 'limited' resource. My point is that the
             | logic to efficiently move data around is more complicated
             | when you don't know the arity ahead of time.
             | 
             | You can't, for example, keep a register file as a linear
             | slice. You have to allocate a whole slate of 64 registers
             | on each call with the last (n) registers blanked.
        
               | sigwinch28 wrote:
               | Yes, you're right.
        
       | polack wrote:
       | The real question is why they do Module.function() and then
       | function.() instead of .function()?
       | 
       | It makes no sense to have the dot at the end of an anonymous
       | named function.
        
         | sodapopcan wrote:
         | `function()` is stored inside a `Module`, so we call
         | `Module.function()`.
         | 
         | An anonymous function "`()`" is stored inside a `variable`, so
         | we call `variable.()`.
         | 
         | It does make some good sense. It's even kinda elegant!
        
           | darraghenright wrote:
           | I've sometimes wondered if I am the only person who actually
           | likes the syntax :D There's a reason for it, but
           | additionally, I like the fact that it's explicit -- I can
           | look at `some_call.()` and specifically know it's an
           | anonymous function.
        
             | sb8244 wrote:
             | I'm a barewords fan, but even I don't understand the hate
             | towards the dot.
             | 
             | Honestly, it feels like low level fruit ripe for baiting
             | engagement.
             | 
             | Sure it would be cool if it wasn't there, but does it
             | really materially change anything?
        
       | callamdelaney wrote:
       | Welcome to to ruby-esque syntax, which nobody likes. It's really
       | what the beam needed /s.
       | 
       | Odd that a language that takes it's syntax from a language about
       | 50 years old is 10x more syntactically clean than elixir.
        
       | JonChesterfield wrote:
       | Elixir still runs on the Erlang VM, which is         dynamically
       | typed, so we should not expect any         meaningful performance
       | gain from typing Elixir code.
       | 
       | This doesn't follow at all. Lots of code runs on the x64 machine
       | which is mostly untyped and still gets performance gains from
       | type information in the source language.
       | 
       | The whole point of a compiler is to have different behaviour
       | between source and target language. If the source language has a
       | static type system that the compiler uses to control code
       | transforms, you get performance/errors regardless of whether the
       | target has a type system.
        
         | lolinder wrote:
         | The key difference between x64 and the BEAM is that the BEAM
         | does dynamic type checking on the fly no matter how thoroughly
         | you type check your code ahead of time. x64 just sees bytes, so
         | if you want to leave off type checking you can, hence the
         | performance gains.
         | 
         | EDIT: Also, when I go to look for this quote I can't find it.
         | Did you somehow end up on last year's announcement of an
         | upcoming type system for Elixir [0]?
         | 
         | [0] https://elixir-lang.org/blog/2022/10/05/my-future-with-
         | elixi...
        
           | JonChesterfield wrote:
           | People did not like that example choice.
           | 
           | The type checking beam does can coincide with the checking
           | the source language does but that's not fundamental. Say you
           | compile SML to beam. Would you conclude that the compiler
           | isn't able or allowed to do anything with the types in the
           | source language?
        
             | lolinder wrote:
             | I'm not sure where you're going with that. I'm not saying
             | that a compilation has to be 1-to-1, I'm saying that
             | Erlang's VM will always spend cycles type checking at
             | runtime regardless of how thorough your compiler is. And I
             | believe that's all Jose is saying in the article you're
             | quoting from.
        
               | JonChesterfield wrote:
               | The code the VM spends time checking is _different_ if
               | the source language compiler emits different code, such
               | as if it uses type information instead of discarding it,
               | and different code has different runtime performance.
        
           | JonChesterfield wrote:
           | It's in a sibling post about type systems, not in the op, and
           | I have an off by one in where I have replied to
        
         | gnull wrote:
         | > Erlang VM, which is dynamically typed
         | 
         | > x64 machine which is mostly untyped
         | 
         | Dynamically typed and untyped are not the same, no?
        
           | JonChesterfield wrote:
           | Spent so long trying to get quote formatting on a mobile that
           | I didn't notice I've replied to the op, not the subthread.
           | Bad times.
           | 
           | Whether x64 is typed or untyped feels like the start of an
           | argument about whether xmm registers have the same type as
           | stack memory which is kind of interesting but orthogonal to
           | elixer.
           | 
           | I would agree that static, dynamic and untyped are distinct
           | things. You can compile from a source language with any type
           | system to a target language with any type system. I think,
           | there may be edge cases around excessively constrained
           | languages.
           | 
           | As an extreme example, the target machine says nothing about
           | whether you can constant fold 1+2 into 3 in the source
           | language, but the source language can definitely block or
           | enable that minimal optimisation.
        
           | gnull wrote:
           | Also: x86 is very much statically typed. Your types are 8-,
           | 16-, 32- and 64-bit words. Each machine instruction knows the
           | exact types of its operands, so there's no overhead on
           | determining the types of values at runtime (like you'd need
           | to do in case of dynamic typing).
        
             | JonChesterfield wrote:
             | It's a tangent but a good one.
             | 
             | Machine code, at least x64/aarch64/amdgpu and the like,
             | cannot be considered statically typed.
             | 
             | 1/ there is no static checker
             | 
             | 2/ if there was, the interpreter would run the failing code
             | anyway
             | 
             | 3/ memory can be integers, floats, machine instructions all
             | at the same time
             | 
             | 4/ registers overlap, e.g. writing to a 'i32' probably
             | zeros the adjacent 32 bits in the same register
             | 
             | 5/ instructions on the same register sometimes refer to
             | floats and sometimes to integers
             | 
             | It also isn't dynamically typed, though a kernel might kill
             | the program or hardware crash on some operations.
             | 
             | I think it's most usefully considered untyped in that
             | there's no ahead of time verification and there's no
             | runtime checking either.
        
               | gnull wrote:
               | What is "untyped" exactly? Google tells me it's the same
               | as "dynamically typed".
               | 
               | Remember the context. The thread started with Elang VM
               | making it impossible to make code faster, due to the need
               | to accommodate Erlang's dynamic typing features and do
               | runtime checks for that. Top comment said that dynamic
               | typing per se can't be the obstacle for programs being
               | faster, and gave x86 as an example of a presumably
               | dynamic VM which does not limit the speed of the
               | programs.
               | 
               | So "dynamically typed" from the article here meant that
               | there's some runtime checks you can't unsubscribe from,
               | and due to them there's a cap on how much faster you can
               | make your code. It applies to some extent to x86, there's
               | also checks there like segfault for example, and maybe
               | you could save some space on silicon if you removed them
               | and required the machine code to be verified to never
               | cause segfaults. But I argue that this is too much of a
               | stretch. Runtime checks that x86 performs are negligibly
               | simple, and x86 is not a valid counter-example for the
               | article's point. In my view the article's point is valid.
               | 
               | > 1/ there is no static checker
               | 
               | "return 0" is the static checker for x86 machine code.
               | 
               | But seriously: this is not a necessary condition for
               | something to be statically typed, it can't be.
               | 
               | > 2/ if there was, the interpreter would run the failing
               | code anyway
               | 
               | Failing code does not exist. All code is valid.
               | 
               | Haskell is undoubtedly statically typed. But division by
               | zero will still cause a runtime error.
               | 
               | > 3/ memory can be integers, floats, machine instructions
               | all at the same time
               | 
               | Ok. Disregard what I said about machine words. The only
               | datatype in x86 is byte. Some operations use 2 bytes,
               | some use 4. They may interpret them differently (some may
               | see an int, some float), but it changes nothing, since
               | both int and float operations are operations on bytes.
        
         | josevalim wrote:
         | Hi Jon, wrong thread but I got it. :D The Erlang VM bytecode
         | contains the type operations in there, such as get_map_key or
         | get_tuple_element or get_list_head. So it would still be
         | checking the types unless we rely on a mechanism for annotating
         | the bytecode with additional information (which they are
         | already using for the JIT). It may be possible in the future
         | but it is not an immediate concern at the moment.
        
       | jakear wrote:
       | Why not allow function literals that define multiple arity
       | implementations? Something like...                   def
       | sum(list) do           plus = {              fn -> 0 end ;
       | fn x, y -> x + y end  }                Enum.reduce(list, plus(),
       | fn x, y -> plus(x, y) end)         end
       | 
       | Also throwback to when I decided to rant about Elixir for some
       | reason 6 years ago, with the final comment:
       | Converting a Module Function to a First class function
       | (&Math.square/1):            Why do you make me lose the ability
       | to use Elixir's admittedly powerful Pattern matching on arity
       | feature?"
       | 
       | https://gist.github.com/JacksonKearl/57b617de38b1c647ec41404...
        
         | munificent wrote:
         | What if you wanted your local function to shadow some of the
         | arities of the outer function but not others?
        
           | jakear wrote:
           | There's no value/function shadowing in Elixr so it isn't
           | possible regardless. But one could always just call the outer
           | Function from the labmbda's innards.
        
         | josevalim wrote:
         | > Why not allow function literals that define multiple arity
         | implementations? Something like...
         | 
         | Perhaps I was unable to get my point across but that's what the
         | blog post is meant to answer. The TL;DR is two fold:
         | 
         | 1. In order for the feature to be worthwhile, we should remove
         | the distinction between name-arity pairs altogether from the
         | language (so module functions and variables effectively exist
         | in a single namespace)
         | 
         | 2. However, this double namespace is a core feature of the
         | Erlang VM, so it would require radical changes to it (or you
         | would need a statically typed language with FFI bindings to
         | Erlang in order to "work-around" this efficiently)
         | 
         | Overall Elixir is a Lisp-2 language, with two distinct
         | namespaces, and it requires conversion between functions of
         | those namespaces. It does not have currying, it does not
         | support point-free style, but in practice guards and pipelines
         | help alleviate those concerns.
         | 
         | Regarding your gist, I am honestly not sure if 15 minutes is
         | enough to evaluate a programming language, but in case you want
         | to dig deeper, many questions are answered in the official
         | guides. Here are some quick links:
         | 
         | * On maps vs keywords: https://elixir-lang.org/getting-
         | started/keywords-and-maps.ht...
         | 
         | * On do-blocks and syntax: https://elixir-lang.org/getting-
         | started/optional-syntax.html
        
           | jakear wrote:
           | Right, I wasn't putting that it forward as a sign of my deep
           | investment in the topic. More just a laugh at dumb things I
           | got worked up over in college. thanks for the links.
           | 
           | That all said, I don't see why you'd need to remove the
           | double namespace feature in order to have function literals
           | that define multiple interpretations. The value namespace
           | still has just the single value-land binding to the literal,
           | only the `call` procedure (the .( operator, so to speak)
           | needs to be modified to dispatch to the appropriate function-
           | land name as determined by airity and the literal the value
           | was bound to.
        
             | josevalim wrote:
             | I see. :D I am lucky my time in college was just before the
             | internet "became permanent". Double lucky that all pictures
             | and recordings from my cover band disappeared with it!
             | 
             | Anyway, regarding the dot, we could make `fun.(...)`
             | dispatch to the correct arity, but that would make every
             | function dispatch slower. However, even if we assume that's
             | an ok price to pay, it wouldn't take long for people to
             | request function capture without an arity, such as
             | `&Foo.bar` (otherwise it would feel incomplete). And this
             | feature would add further penalties as we further postpone
             | the call.
             | 
             | Both would also reduce the amount of compile-time checks we
             | can emit and hurt integration with the overall Erlang
             | ecosystem. On the large scale of trade-offs, I don't think
             | it is worth it. :)
        
           | throwawaymaths wrote:
           | I think the real question we all want to know is why lambdas
           | don't have "do"
        
             | sodapopcan wrote:
             | Lambad definitions don't take arguments, they jump straight
             | to argument matching.
             | 
             | Take:                   case value do           true ->
             | "true"           false -> "false"           _ -> "uh..."
             | end
             | 
             | vs                   fn           true -> "true"
             | false -> "false"           _ -> "uh..."         end
             | 
             | `do` is simply syntactic sugar for a keyword list
             | _argument_ containing a `:do` key:
             | if(true, [{:do, "hi there"}])
             | 
             | So it wouldn't make sense for `fn` to take a `do`.
        
               | throwawaymaths wrote:
               | fn do           true -> "true"           false -> "false"
               | _ -> "uh..."         end
        
               | sodapopcan wrote:
               | As much as I despise people responding in pure code,
               | you're right, it's technically `fn/1` [0]. Even though
               | the docs say `fn(clauses)`, `cond/1`, which requires the
               | `do`, also says `cond(clauses)` [1] so looks like I'm
               | wrong.
               | 
               | [0] https://hexdocs.pm/elixir/1.14/Kernel.SpecialForms.ht
               | ml#fn/1
               | 
               | [1] https://hexdocs.pm/elixir/1.14/Kernel.SpecialForms.ht
               | ml#cond...
        
             | josevalim wrote:
             | Because `do` binds the farthest away. In these examples:
             | case some_fun(1) do         end              case
             | some_fun() do         end              case some_fun do
             | end
             | 
             | We all know `do` binds to case. Therefore, if `fn` used
             | `do`, the `do` would bind to the farthest call to:
             | # this code         Enum.map list, fn do         end
             | # or this code         map list, fn do         end
             | 
             | would both bind `do` to `map`.
             | 
             | Of course we could special case `fn do` but having `do`
             | bind to different places based on `fn` would be extremely
             | confusing.
        
               | throwawaymaths wrote:
               | Does anybody actually call Enum.map without parentheses?
               | That's actively discouraged currently.
               | Enum.map(list, fn do _ -> :ok end)
               | 
               | Is unambiguous
               | 
               | Honestly I do appreciate not having to type two
               | characters, but I'm currently onboarding a bunch of n00bs
               | and they're very puzzled by this one inconsistency
        
               | sodapopcan wrote:
               | Well, I didn't have much luck last time I responded to
               | you, but I'll try again since you haven't gotten a
               | response yet!
               | 
               | Regardless of whether or not it's actively discouraged,
               | it still must be supported since Elixir is so heavily
               | bootstrapped. Since `defmodule`, `def`, etc are all just
               | macros written in Elixir, there are no special rules
               | around which functions/macros are allowed to be called
               | without parens. There was some discussion about that on
               | the forums a while ago (like requiring parens for
               | functions and not for macros) and the answer is that that
               | will never happen.
        
               | throwawaymaths wrote:
               | Well I mean it wouldn't have been a big deal, if the do
               | block binds to the outermost, then in most cases just
               | would get a compiler error since usually a private fn/0
               | doesn't exist. Still though in the early days elixir more
               | strongly preferred no-parens, so avoiding that is
               | understandable.
        
               | sodapopcan wrote:
               | True!
        
         | lvass wrote:
         | >Why do you make me lose the ability to use Elixir's admittedly
         | powerful Pattern matching on arity feature?
         | 
         | If every first class fuction had to check for arity at runtime,
         | wouldn't there be a performance cost? Also would reduce
         | drastically the amount of errors caught at compile time. Maybe
         | a different mechanism for dispatching like that (a macro?)
         | could be done, how useful would that be?
        
           | jakear wrote:
           | There already must be a arity check at some point, no? If the
           | type contained the set of airties the lambda accepts rather
           | than just the singe one, all the same type checking could be
           | done when it's already done.
        
             | lvass wrote:
             | There's no type checking on function dispatch unless guards
             | are explicitly added. The arity dispatch for functions
             | captured into first class is done at compile time and
             | require explicitness. That said, I think you can create
             | your own dispatch macro that's probably as efficient as
             | possible for doing what you proposed.
        
         | nesarkvechnep wrote:
         | Did you actually read the article?
        
           | jakear wrote:
           | Did you read the HN guidelines for commenting?
        
             | giraffe_lady wrote:
             | Hopefully when the entire article is an answer to exactly
             | the question you're asking in the comments there is a
             | little leeway.
        
               | jakear wrote:
               | See the comments in other threads where an actual topic
               | of contention was presented and accordingly follow up
               | discussions could be had. The reason for the HN
               | Guidelines is to foster good conversation. "Did you read
               | the article" doesn't.
        
               | giraffe_lady wrote:
               | The followup discussion of the author just summarizing
               | the article for you? Ok.
        
               | jakear wrote:
               | That's all you've been able to comprehend from reading
               | this thread? Surprising.
               | 
               | I'd tell you to get off your high horse, but it seems to
               | be your identity.
               | 
               | To summarize: the only reason presented not to do what I
               | said is dubious claims about perf, which were not
               | mentioned in the article whatsoever.
        
         | throwawaymaths wrote:
         | If you look at how anonymous functions are compiled at a low
         | level you'll understand why that's not possible: a function is
         | literally "the module it's in" + the bytecode "line number" +
         | arity (so that it knows how many register items to
         | instantiate). For inlined anonymous functions, a secret private
         | function gets created.
        
           | jakear wrote:
           | Okay, so make the lambda's binding refer to several of those
           | 'structs' and have .( dispatch to the correct one based on
           | the airity at the call site.
        
             | throwawaymaths wrote:
             | That will introduce unnecessary overhead in the basic case.
             | At some level you do want to access the "low level"
             | function (interfacing with Erlang, e.g.) if you really want
             | that sort of dispatch you can write your own polyfunction
             | data type.
        
           | [deleted]
        
           | ajkjk wrote:
           | So the answer to "why does Elixir require a dot?" is "because
           | of dubious design choices for the innards", rather than any
           | sort of reason that makes sense based on its actual syntax or
           | features.
        
             | josevalim wrote:
             | As other languages that run on existing environments most
             | likely have done. C#, F#, Clojure, TypeScript, Swift, and
             | many others probably had to take similar considerations.
             | Then at a lower level, if you want to maintain ABI
             | compatibility, that may also impose restrictions.
             | 
             | Much of the software we write needs to deal with the
             | constraints of its environments. It would be unfortunate if
             | we decide to give all of them the uncharitable description
             | of being defined by "dubious design choices for the
             | innards" even when known well-defined patterns surface from
             | designing within constraints.
        
               | ajkjk wrote:
               | It would be unfortunate if we were unwilling to call
               | design choices dubious and learning from past mistakes
               | instead of rationalizing why they're actually fine. I
               | don't know Elixir but you're telling me in 2023 I'm
               | supposed to learn that a language calls anonymous
               | functions with a different syntax than regular ones and
               | not be annoyed? Like.. that just sucks. If you have to do
               | it that way for unfixable reasons, at least deliver the
               | bad news with an apology instead of a white lie about why
               | it makes perfect sense.
        
               | josevalim wrote:
               | I am completely fine with calling past dubious design
               | choices. This isn't one of them. That was your labeling,
               | provided with no evidence or reasoning, not mine, and
               | that's what I was criticizing.
               | 
               | The Lisp-1 and Lisp-2 discussion is several decades old,
               | with many discussions arguing the benefits of one over
               | the other. The article highlights some of those trade-
               | offs, which should be clear if someone is willing to get
               | past superficial syntax notes.
               | 
               | However, given how intent you are on putting words in my
               | mouth and on distorting contrasting opinions as white
               | lies, let's call it a day.
        
               | sandbags wrote:
               | As a developer who came to Elixir from Clojure I had the
               | same reaction to fun.(), "this sucks". Then someone
               | explained why. So, okay, there is a good reason but
               | "bleh." I think that lasted maybe a day, by which time I
               | had accepted it as part of the language. It's just not a
               | big deal when the language offers so much in return. And
               | I _liked_ Clojure.
        
             | throwawaymaths wrote:
             | That's not a dubious design choice. It's actually amazing.
             | How would _you_ design a lambda that can be pickled and
             | rerun across time (run on a different invocation of the
             | vm), or space (sent across a network and executed)?
        
               | ajkjk wrote:
               | Well like... the same as it is except allowing multiple
               | arities in the definition.
        
               | [deleted]
        
           | josevalim wrote:
           | I consider this to be pretty much an implementation detail
           | though and not necessarily set in stone. The Function data
           | structure is their public interface though and it does define
           | an arity.
        
       ___________________________________________________________________
       (page generated 2023-08-14 23:00 UTC)