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

I prefer them together (if the type cannot be left out entirely due to it being properly inferred) so I can see what type and name something is both as I’m using it. I don’t need to see just the type in a file as when I’m programming the IDE will show my the types of things as I type to call something anyway. :slight_smile:

I guess it’d be better if they were on their own lines if you had to have types if you did not have an IDE worth using, but thankfully that is not today’s world. ^.^

1 Like

I could not agree more. @specs are really quite ugly.

I suggest putting a blank line between your @specs and function heads.

2 Likes

I think you are missing my point. I never said they were not important. They are there to provide info, to you or to tools. That’s important.

1 Like

Yes, I think that’s a concise summary. Hence the question in my previous post: What’s the goal here? I think making your own modification to ElixirLS is trivial and the only upkeep you’d need to do personally is to git pull their changes whenever you feel like it. Considering the minor changes you’d make for this conflicts are extremely unlikely. At “worst”, you’d make a PR to make the syntax highlighting of @spec configurable.

1 Like

Because type-hinting in PHP actually kind of sucks for a really long time (although they are fixing it). If you want to pass in an iterable object, or an array you couldn’t enforce a type if you want the users of your library to be able to use either, even though either are iterable. You can typehint for array or ArrayAccess but not both. You could type hint for an iterable object or an array, but not both. So, there are cases where enforcing types in PHP can kind of suck for your users. A docblock in PHP is similar to a @spec in elixir. You can have static analysis, IDEs can provider autocomplete and hints, etc. but they aren’t used by compiler or execution.

Exactly why you should use a language with better types! Great example! ^.^

1 Like

Have any recommendations?

@spec some_function(SomeType.t()) :: SomeOtherType.t()
def some_function(some_arg)

This feels so heavy and much harder to read. Strong/static typing is the feature I really miss in Elixir. Esp. from a security standpoint I really like to know what types of data get in/get out.

+1, this is much better, concise and readable

btw does anybody know where I could find Elixir grammar/formal spec if it exists ?

1 Like

I think the sources of the tokenizer and the parser come closest to what you are asking for.

3 Likes

Again this relates entirely to what you are familiar with.

I used to feel the same way until I realized that declaring names and types in the same definition forced multi-line signatures much faster. Some might argue that keeping names and types separate is actually more concise - similar to how header and row information in a table interact, The spec focuses on typing while the function header focuses on naming - sacrificing easy parameter name/type correlation for focus.

The first approach declares a function type while the second approach types the parameters and the return value - producing a function type as a side effect. (Sometimes “preferences” can get in the way - accept whats there and move on).

3 Likes

Yes, indeed. As is your post :wink:

If you think that this:

@spec some_function(SomeType.t()) :: SomeOtherType.t()
def some_function(some_arg)

is easier to read, more concise and less error-prone than:

def some_function(some_arg :: SomeType.t()) :: SomeOtherType.t() do
end

So be it. Let’s agree to disagree.

What if you had 40 000 functions ? Would you write 40 000 separate type defs, including misc. pattern matching function prototypes ? Aren’t you afraid your code base might quickly become bloated ?

1 Like

Oo 40 000 functions just in one module or written at once or by one day by one programmer? Seriously or are you joking? Quite really really bad design. Don’t forget about 40 000 @doc blocks. :wink:

Yep first example is less error prone, because you have to think twice about the same problem. Firstly you think about types and what function will have to produce, and secondly you just think up names for entities. All is true only if you write @spec before your function. If you write specs for written functions earlier it got less sesne just like writing simple unit tests after implementation. Always is better to think twice about a problem than once. :slight_smile:

Side note:

Haskell is statically typed language untill compilation and got wonderfull types definitions, and they are just like annotations.

sayMe :: (Integral a) => a -> String  
sayMe 1 = "One!"  
sayMe 2 = "Two!"  
sayMe 3 = "Three!"  

One more example from Elixir:

def some_function(%Some{p: pattern_match_here_blah, blah: blah} = arg1 :: integer(),
                  :match = match :: atom(),
                  arg3 :: integer()) :: integer()
do
  # something
end

vs.

@spec some_function(integer(), atom(), integer()) :: integer()
def some_function(%Some{p: pattern_match_here_blah, blah: blah} = arg1,
                  :match = match,
                  arg3)
do
  # something
end 

Which one give you more information when you first look at it?

Second example:

@spec funnier(integer()) :: integer()
def funnier(0), do: 1
def funnier(1), do: 0
def funnier(n), do: funnier(n-1)

So with types in function head you would like to put them where?

# define special clause for types? - it's just spec without @spec polluted by arg names :)
def funnier(n :: integer()) :: integer()

def funnier(0 :: integer()) :: integer(), do: 1  #or just here?
def funnier(1 :: integer()) :: integer(), do: 0  #or maybe here?
def funnier(n :: integer()) :: integer(), do: funnier(n-1) #or here??
# or everywhere???

So?

3 Likes

Well take OCaml, you ‘type’ things like:

let blah (a:float) (b:string) = (string_of_float a) ^ b

But doing that is entirely useless as it is trivially inferred, so you can just do:

let blah a b = (string_of_float a) ^ b

You really only need to specify a type if something is ambiguous (almost never) or you are purposefully restricting it. This is how adding types should be done. (In addition the IDE will show the type of anything, including functions when I hover over or put my cursor in it. ^.^)

Or in OCaml:

let sayMe = function
| 1 -> "One!"
| 2 -> "Two!"
| 3 -> "Three!"
(* Along with a warning for missing possible inputs, you'll get an exception then *)

^.^

Verses having good type inference. :wink:

For note, in OCaml you can specify types inline, as I showed above, but you can also put them in an mli file, which is the ‘public interface’ file, thus keeping your main file clean.

Well with things like 0 and 1 the type is already there as part of the constant value, so adding extra types seems useless, like doing (6.28 : float) in OCaml is entirely useless.

3 Likes

I’d go with:

let blah : float -> string -> string = 
  fun a b -> (string_of_float a) ^ b

and of course in the mli

val blah : float -> string -> string

You really only need to specify a type if something is ambiguous (almost never) or you are purposefully restricting it. This is how adding types should be done. (In addition the IDE will show the type of anything, including functions when I hover over or put my cursor in it. ^.^)

I’ve always felt that type inference is a double edged sword. Its convenience is indisputable but unfortunately it also lets you sometimes progress so far into the weeds that by the time it complains you have no idea where all these conflicting types are coming from or what they mean. Explicitly specifying the intended types acts like progressive checkpoints:

  • If you can’t specify the type, you probably don’t know what you are doing anyway - so timeout and figure out what you are doing.
  • Type inference can then catch divergence between the intended and the actual type at the earliest possible moment - in Elm I liked the fact that missing type declarations were warnings that could be elevated to errors.

Apart from the fact that I don’t like to have to rely on the presence of an IDE to tell me something what should be in plain sight.

3 Likes

Haskell will infere types properly too just like in Ocaml. All helps you in finding some potential places of code bugs, no more.

But you know that writing some types in functional language or other languages helps in readibility and understability of code (just like with documenting your code), no matter that compiler, dialyzer can infere it?

Programmer is not a compiler or „inferer”, with some type hints it’s just simpler to understand code no need to prove that this thing quack like a duck so it could be a duck.

There is some rule in programming that you write code once but there will be a need to read it n times in future by many people.

2 Likes

To be fair, OCaml has great support for editors in merlin which you can use to get type information. Being that OCaml as a language has made a lot of choices that make it much easier to infer types without issues (ambiguity is much rarer than in Haskell) it actually does fine without type annotations as long as people are using the common tools. I can’t imagine why people wouldn’t, either, because OCaml tooling is one of the easiest to set up in my experience.

With that said, I think it’s useful to document your work (as I’m sure most people agree with). It’s just that when you’re exploring it’s not as necessary to do that in OCaml, where as in Haskell you can quite quickly end up with the compiler not being able to infer your types.

1 Like

Yep you are typing the blah instead of the arguments there, perfectly reasonable, but it means you have to fully define the type instead of just a single specific bit. ^.^

Not really with OCaml, everything is inferenced within a single function, not out, unlike Haskell and it’s HKT’s that require whole program inference, which can create truly gnarly errors.

And in OCaml the compiler can generate the mli files for you. ^.^

Except they aren’t. If you see the function then you can see how arguments are used, thus you already know from that. If you are looking at a callsite of a function, then well it cannot tell you in source, so an IDE is awesome there. :slight_smile:

Not entirely. Haskell is an HKT language rather than pure ML, thus meaning that you absolute have to put types in many locations that you otherwise would not need to in an ML language.

Hear hear, but I do prefer the generated mli files (that I can then prune to shrink the public interface).

Which is a big reason I prefer strong static types! ^.^

This right here.

That is because of its HKT typing system.

2 Likes

I think some degree of automation with generating specs could go a long way. If language server provided it’s best guess to the IDE that could serve as a a decent starting point

2 Likes

To some extent, the syntax is overpowering. In your simple example, it looks like syntactic sugar could solve it: E.g., we’d write specs like Swift syntax, which would be translated into @spec at a lower level:

def squareRoot(i: Float) -> Float do
  ...
end

Far less noisy and could translate 1:1 into Elixr/@spec syntax. Just a pre-processor would be necessary to work with the current tooling.

2 Likes

Don’t you think you’d need a parsing change to be made in order for this to work, though? When considering something like that, it might be an idea to move to an entirely new parser/language that doesn’t require as much ceremony at all to define a function.