Does anybody else feel this way about @spec (visually overpowering)?

actually you could represent that with a minor edit in the Elixir AST. Look at what I did with TypedElixir and MLElixir. ^.^

2 Likes

You intrigued me with this Ocaml, so I learned some basics fastly and yes it’s got really good type inferring mechanism, but
 as always there are some but’s.

For Ocaml compiler code below is good, with fantastic types int, as just for programmer who wrote this code fastly. But this code got serious bug. Can you see it at first look?

Side note: Used Fibonacci to simplify example as much as possible.

let rec do_fib = function
  | 0, prev, cur -> prev
  | n, prev, cur -> do_fib(n - 1, cur, prev + cur);;

(* val do_fib : int * int * int -> int = <fun> *)

let fib n = do_fib(n, 0, 1);;

(* val fib : int -> int = <fun> *)

So the bug is:

fib(0);;
(* - : int = 0 *)
fib(1);;
(* - : int = 1 *)
fib(-1);;
(* BANG: forever and ever :) *)

So relaying only on inferring types can be sometimes really deceiving.

With implicit type annotation or definition you have more chance to not introduce this bug at all or if you introduce it more chance to find this bug just by reading code before running this code with bad value at runtime.

@spec fib(non_neg_integer()) :: non_neg_integer()
def fib(n), do: fib(n, 0, 1) #omg, where is guard for n >= 0?

def fib(0, prev, _),   do: prev
def fib(n, prev, cur), do: fib(n - 1, cur, prev + cur)

[quote=“crabonature, post:43, topic:11665”]
but
 as always there are some but’s.[/quote]

It’s a very old language so it has it’s warts, but it is very well designed though. :slight_smile:

MLTypes cannot protect you from logic bugs, for that you’d need something like Idris. :wink:

However, that can be caught more easily if you use guards with matchers like in Elixir:

let rec do_fib = function
  | 0, prev, cur -> prev
  | n, prev, cur when n>0 -> do_fib(n - 1, cur, prev + cur);;

(* val do_fib : int * int * int -> int = <fun> *)

let fib n = do_fib(n, 0, 1);;

(* val fib : int -> int = <fun> *)

Notice I added the ‘when’ there to constrain the ‘value’ (not the type), and now it will properly throw you an exception. ^.^

Technically you can encode logic into the ML type system too, though it becomes more
 work (though not ‘that’ different from Idris, but more wordy by far).

Idris is fascinating to learn but a bit more brain melting at times (and so sloooooow to compile).

1 Like

Hi @ryanwinchester,

I feel exactly the same way you do. I understand the arguments around typing in Elixir leading to this long thread. For now dialyzer is the best we can get.

Rather than fork the repo and run my own version of the extension as gonz suggests, I just keep a custom copy of the grammar file elixir.json along with a custom color scheme/theme file based on ST3’s Monokai in a private repo under ~/.vscode/extensions.

When I want to make tweaks to syntax highlighting, I just edit the elixir.json grammar and optionally the theme file, then run an .exs script which copies it over the installed extension’s files. Whenever the extension updates itself I have to manually re-run the script to copy over the files again. This is a pain but easier IMO than the fork
pull from upstream
merge option every time there is a new extension release.

Here is the snippet of JSON you will need in your custom elixir.json file located at ~/.vscode/extensions/JakeBecker.elixir-ls-0.2.10/syntaxes and it should work out-of-the-box with any color scheme you like:

{
  "comment": "treat @spec as a comment/documentation to de-emphasize",
  "begin": "@spec ",
  "beginCaptures": {
    "0": {
      "name": "comment.documentation.spec.elixir"
    }
  },
  "while": "(^|\\G)(?!\\s*(def |defp ))",
  "contentName": "comment.documentation.spec.elixir"
}

This assumes the @spec line(s) are directly before a def or defp line.

Please let me know if this helps or if you’d like me to put a gist up.

7 Likes

This should only be a git pull --rebase. I’m not sure what your custom script is supposed to do better than that.

1 Like

I’m sure you are right it would be as simple as a git pull --rebase. I wasn’t sure, and haven’t researched, exactly how the vscode extension automagic updater works after forking. I didn’t want it to blow away my grammar customizations on an upstream update so I just rely on a simple script that replaces the extension grammar json file after they update with my customized one.

The important part to address the OP’s original question is the grammar snippet that highlights @spec as a comment. I have been bothered by this also and his post made me want to finally do something about it. It took me an hour of trial and error today to get the regex working right on multi-line specs. Really glad I don’t have to work with regex and these textmate grammar files every day!

2 Likes

I was thinking about this a bit more - I wonder if a problem is, the @spec and def's aren’t lined up vertically. Haskell, IMO, isn’t as chaotic looking because of the vertical alignment:

mult :: x -> y -> z
mult x y = x * y

So, brainstorming, if we could make @sp be a synonym for @spec, then the orig. functions would look like:

@sp get_integration_provider!(integer) :: %IntegrationProvider{}
def get_integration_provider!(provider_id) do

I like that a lot. The doubled-up name makes a nice visual signpost for the start of the function. Alternatively, we could format the function start with two extra spaces:

@spec get_integration_provider!(integer) :: %IntegrationProvider{}
def   get_integration_provider!(provider_id) do

@OvermindDL1 Any ideas how to create this @sp synonym? I’d like to give that a try.

1 Like

And a different brainstorm: a single unified spec+def syntactic sugar, in some format that’d be easy to expand back to the originals E.g.,

def get_integration_provider!(provider_id ~ integer) :: %IntegrationProvider{}

See what I did there? :slight_smile: I changed @spec to def and added in the parameter followed by some delineating character. I think that’d enable a mapping back to the original def and @spec.

In OCaml this would be:

let mult : x -> y -> z =
  fun x y -> x * y

Or more succinctly as:

let mult (x : x) (y : y) = x * y

Or entirely separate as:

(* header file *)
val mult : x -> y -> z
(* implementation file *)
let mult x y = x * y

The argument name still gets misaligned though?

Honestly I’d love some combined kind of definition, maybe like:

defs get_integration_provider!(provider_id :: integer) :: %IntegrationProvider{} do
  ...
end

Though I quite prefer inlined types so I can see far more easily, and it works very well when the arguments get long too as each can get on it’s own line:

def blahblah(
  arg1 :: integer,
  arg2 :: String.t,
  arg3 :: BlahblahBlah.t # Oh if only we had trailing commas allowed inside parenthesis...
) :: %BlahReturnStruct{} do
  ...
end

Which I find reads very well. :slight_smile:
You could even have that on just the non-implementation head, or you could have it on each head and they’d get combined properly. :slight_smile:

A fairly simple use ... at the top could make @sp work like @spec as a synonym, the module would just convert the AST as necessary to the @spec’s at the end.

I’d just use :: like I demonstrated above as it matches existing type syntax. ^.^

3 Likes

Love it :heart:

1 Like

@ryanwinchester just as an idea, you can put the @spec declaration inside the @doc with interpolation, and it should just work.

This is what I mean:

The @spec is here visually a part of the documentation, but properly executed as code, picked up by dialyzer , ex_doc and co.

Update:

I just compiled it to docs, and found in the place of the spec expressions the string “true”. Really sad, I just began to like it :smiley:

Hmm, is the return value form @spec really necessary, I mean nil would be just fine as well?

Visual studio code does now have a setting to customize theme colors: https://code.visualstudio.com/updates/v1_20?source=techstories.org#_theme-specific-color-customizations

2 Likes

I made a VS Code extension to do this: ByeSpec—a VS Code extension to customize @spec appearance

1 Like