[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)