Thanks for the explanation about guards, makes sense. And I totally agree with you, that can be implemented as an assertion at the very begin of function's body.
Again, the question - is there a way to leverage typespecs? Is there a way to implement something like this "hey elixir, is this value corresponds to this type?" I'd like to avoid a custom way to define specs for that assertions macros. On Thu, Nov 8, 2018 at 12:58 AM Louis Pilfold <lo...@lpil.uk> wrote: > Hey > > The implementation you've given there is expensive and only works for > lists up to a certain length. > > To solve this one you'll need to step outside of guard clauses as they > only support a limited subset of Elixir/Erlang. The idea is that all > operations in guards are very fast and run in constant time, so iterating > over a list or arbitrary length is not supported. > > Another option would be to write a macro that prepends a type checking > statement to a function body, asserting that the arguements are of the > correct type. > > Cheers, > Louis > > On Wed, 7 Nov 2018, 22:41 Sergiy Kukunin, <sergey.kuku...@gmail.com> > wrote: > >> Found another problem: can't express "list of strings" in guards nor >> pattern matching. It's an easy task for typespecs `[String.t(), ...]`, but >> I can't check typespecs in runtime. Found a very dirty hack, that works for >> lists up to 5 strings, enjoy: >> >> defguardp is_list_of_strings(x) >> when (length(x) == 1 and is_binary(hd(x))) >> or (length(x) == 2 and is_binary(hd(x)) and is_binary(hd(tl(x)))) >> or (length(x) == 3 and is_binary(hd(x)) and is_binary(hd(tl(x))) and >> is_binary(hd(tl(tl(x))))) >> or (length(x) == 4 and is_binary(hd(x)) and is_binary(hd(tl(x))) and >> is_binary(hd(tl(tl(x)))) >> and is_binary(hd(tl(tl(tl(x)))))) >> or (length(x) == 5 and is_binary(hd(x)) and is_binary(hd(tl(x))) and >> is_binary(hd(tl(tl(x)))) >> and is_binary(hd(tl(tl(tl(x))))) and >> is_binary(hd(tl(tl(tl(tl(x))))))) >> >> Wouldn't it be cool to be able to write something like >> >> defguard is_list_of_strings(x) match_type([String.t(), ...]) >> >> Again, I'm pretty new, and I know nothing about the implementation and >> where Elixir ends and Erlang starts, and how feasible it is. Just an idea >> =) >> >> On Wed, Nov 7, 2018 at 10:11 PM Sergiy Kukunin <sergey.kuku...@gmail.com> >> wrote: >> >>> Actually, that what I understood only in my last message - I can >>> implement it right now. I'm pretty new to Elixir, so that wasn't obvious to >>> me. >>> >>> Currently, it seems it's resolved, there are only suggestions to improve >>> syntax, that are too minor. >>> >>> Thank everyone for assistance >>> >>> On Wed, Nov 7, 2018 at 9:15 PM Louis Pilfold <lo...@lpil.uk> wrote: >>> >>>> Hi Sergiy >>>> >>>> I'm afraid I don't follow. From what I understand of your proposal the >>>> current defguard system meets your needs- what are you looking to add? >>>> >>>> Cheers, >>>> Louis >>>> >>>> On Wed, 7 Nov 2018 at 18:38 Sergiy Kukunin <sergey.kuku...@gmail.com> >>>> wrote: >>>> >>>>> I afraid you missed my point, I might have expressed it poorly. Let's >>>>> assume I have a simple type: {is_atom(), is_number(), is_binary()}. I want >>>>> to define a guard to match it. Without reusing I can write a function >>>>> accepting it: >>>>> >>>>> func({x, y, z}) when is_atom(x) and is_number(y) and is_binary(z), do: >>>>> true >>>>> >>>>> but then I want to define another function which expects the same >>>>> tuple: >>>>> >>>>> another({x, y, z}) when is_atom(x) and is_number(y) and is_binary(z), >>>>> do: true >>>>> >>>>> I don't have a way to define a custom guard to match tuple elements >>>>> since there is no pattern matching in defguard nor there is `elem` in >>>>> guards. So both options don't work: >>>>> >>>>> defguard is_mytype({x, y, z}) when is_atom(x) and is_number(y) and >>>>> is_binary(z) >>>>> >>>>> nor >>>>> >>>>> defguard is_mytype(x) when is_atom(elem(x, 0)) and is_number(elem(x, >>>>> 1)) and is_binary(elem(x, 2)) >>>>> >>>>> Furthermore, I would want to define a function that receives a value >>>>> of my type inside of complex structure: >>>>> >>>>> function({:ok, {x, y, z}}) when is_atom(x) and is_number(y) and >>>>> is_binary(z), do: true >>>>> >>>>> it would be cool to have it defined as >>>>> >>>>> function({:ok, x}) when is_mytype(x), do: true >>>>> >>>>> P.S. Actually, I've found that `elem` works in guards, so I can define >>>>> my guard without pattern matching. That's good for now, but >>>>> >>>>> func({x, y, z}) when is_atom(x) and is_number(y) and is_binary(z), >>>>> do: true >>>>> >>>>> sounds cooler, IMHO =) >>>>> >>>>> On Wednesday, November 7, 2018 at 8:20:22 PM UTC+2, Louis Pilfold >>>>> wrote: >>>>> >>>>>> Hi Sergiy >>>>>> >>>>>> The functionality you've described can be implemented with macros, no >>>>>> need to modify Elixir or Erlang. >>>>>> >>>>>> To start it could be as simple as defining guards that assert nothing >>>>>> in the production environment. >>>>>> >>>>>> defmodule Test do >>>>>> if Mix.env() == :prod do >>>>>> defguard is_my_type(x) when true >>>>>> else >>>>>> defguard is_my_type(x) when is_atom(x) >>>>>> end >>>>>> >>>>>> def go(x) when is_my_type(x) do >>>>>> x >>>>>> end >>>>>> end >>>>>> >>>>>> This could be a little error prone though as unless you remember to >>>>>> apply the guard to every clause of the function your logic may change >>>>>> when >>>>>> they are removed. Even if you apply them to every clause if you use >>>>>> exceptions as flow control you may run into problems as values that >>>>>> previously would result in a FunctionClauseError would be passed though. >>>>>> >>>>>> Plenty to think about! Perhaps experiment with a little proof of >>>>>> concept library and see what happens :) >>>>>> >>>>>> Cheers, >>>>>> Louis >>>>>> >>>>>> On Wed, 7 Nov 2018 at 17:44 Sergiy Kukunin <sergey....@gmail.com> >>>>>> wrote: >>>>>> >>>>> Thanks for the answers. Just want to note, that I don't want to invent >>>>>>> type system such as in statically typed languages. I mean more about >>>>>>> defining schemas we can check different values with. All pattern >>>>>>> matching, >>>>>>> guards and typespec might work for this. Furthermore, it would be cool >>>>>>> to >>>>>>> make it composable and reusable (such as defguards and typespecs right >>>>>>> now). >>>>>>> >>>>>>> Just to conclude, I would suggest that either of these would improve >>>>>>> the safety and convenience of the language: >>>>>>> - allow pattern matching in custom guards (either via the built-in >>>>>>> guard such as `Kernel.match?/2` or by extending the defguard syntax) >>>>>>> - having a macro to check whether a value corresponds to a defined >>>>>>> @type >>>>>>> >>>>>>> What's about such syntax? >>>>>>> >>>>>>> defguard is_mytype({x, y}) when is_atom(x) and is_number(y) >>>>>>> >>>>>>> def test({:ok, value}) when is_mytype(value), do: true >>>>>>> def test(_), do: false >>>>>>> >>>>>>> test({:ok, {:hello, 5}}) # should be true >>>>>>> test({:ok, {2, 5}}) # should be false >>>>>>> >>>>>>> There are a couple of reasons I've raised this question: >>>>>>> >>>>>>> - do I miss something? don't I try to solve the problem in a wrong >>>>>>> way? >>>>>>> - to estimate how hard is it to implement in a 3rd-party library or >>>>>>> does it require changes to core Elixir/ErlangVM >>>>>>> >>>>>>> Thanks >>>>>>> >>>>>>> >>>>>>> On Wednesday, November 7, 2018 at 7:20:47 PM UTC+2, Louis Pilfold >>>>>>> wrote: >>>>>>> >>>>>>>> Hi all >>>>>>>> >>>>>>>> The desire for more safety in Elixir is reasonable, both at compile >>>>>>>> time and at runtime. >>>>>>>> >>>>>>>> The core team have previously experimented with introducting a >>>>>>>> compile time type checking system, and we also have the dialyser and >>>>>>>> gradualizer tools that can be used with Elixir. >>>>>>>> >>>>>>>> Checking at runtime is something we already do in Elixir and Erlang >>>>>>>> through the use of pattern matching and guards such as `is_binary/1`. >>>>>>>> A library of macros that automates these checks could be an >>>>>>>> interesting project, perhaps an area worth exploring for members of the >>>>>>>> community. >>>>>>>> >>>>>>>> Cheers, >>>>>>>> >>>>>>> Louis >>>>>>>> >>>>>>>> On Wed, 7 Nov 2018, 16:46 Ivan Yurov, <ivan.y...@gmail.com> wrote: >>>>>>>> >>>>>>> If you want type-safety why not to just pick a strongly typed >>>>>>>>> language, like Ocaml for example? Elixir is bound to Erlang VM and >>>>>>>>> will >>>>>>>>> never provide any features like you're describing that are not >>>>>>>>> supported by >>>>>>>>> Erlang. And I don't think type-checking ever happens at runtime in any >>>>>>>>> language. >>>>>>>>> >>>>>>>>> On Wednesday, November 7, 2018 at 12:00:53 PM UTC+1, Sergiy >>>>>>>>> Kukunin wrote: >>>>>>>>>> >>>>>>>>>> Hello there. This is my first message to the elixir group. Thanks >>>>>>>>>> for the great language. >>>>>>>>>> >>>>>>>>>> While I'm writing my code, I want to make functions to be safer. >>>>>>>>>> It's bad practice if a function accepts unexpected input and pass it >>>>>>>>>> further, and it blows in a completely different part of a system. >>>>>>>>>> >>>>>>>>>> At first glance, I have pattern matching, but it's pretty >>>>>>>>>> limited. It becomes really powerful in conjunction with guards, so I >>>>>>>>>> can >>>>>>>>>> write a signature to match literally everything. >>>>>>>>>> But they hard to re-use, If I have multiple functions operating >>>>>>>>>> with the same object. Yes, I can define a custom guard, but can I use >>>>>>>>>> pattern matching there? `Kernel.match?/2` doesn't work, so I'm >>>>>>>>>> limited with >>>>>>>>>> only guards in my custom guards. >>>>>>>>>> >>>>>>>>>> Another thing that we have typespecs. It seems exactly what I'm >>>>>>>>>> looking for: you have a wide set of built-in types, and I can easily >>>>>>>>>> compose and reuse my own types. The problem with it, that it doesn't >>>>>>>>>> affect >>>>>>>>>> runtime. I know about static analyzer `dialyzer`, but I'm not sure >>>>>>>>>> it will >>>>>>>>>> catch all cases since it's a static check, not a runtime. >>>>>>>>>> >>>>>>>>>> Let's assume a simple function, that wraps a value into a list: >>>>>>>>>> >>>>>>>>>> @spec same(number()) :: [number()] >>>>>>>>>> def same(number) do >>>>>>>>>> [number] >>>>>>>>>> end >>>>>>>>>> >>>>>>>>>> I'm sure the `dialyzer` won't complain since a signature is >>>>>>>>>> valid. But what if I do: `same("abc")` ? What will prevent Elixir >>>>>>>>>> from >>>>>>>>>> returning a wrong type? I guess, nothing. >>>>>>>>>> An example from a real life: I have a function, that accepts a >>>>>>>>>> custom shaped value (using tuples) and feeds it to a queue. Then, in >>>>>>>>>> the >>>>>>>>>> totally different part of the system, a consumer gets values from the >>>>>>>>>> queue. And when a wrong value was fed on the producer side, it blows >>>>>>>>>> on the >>>>>>>>>> consumer side. So I decided to put some constraints on the producer >>>>>>>>>> side to >>>>>>>>>> fail fast. >>>>>>>>>> >>>>>>>>>> Yes, I could define a guard, but again, if I have a pretty >>>>>>>>>> complex type instead of the simple `number`, I had to duplicate the >>>>>>>>>> type >>>>>>>>>> defining: one for typespec, another is for a custom guard (which is >>>>>>>>>> limited, since I can't use pattern matching there). >>>>>>>>>> >>>>>>>>>> Wouldn't it be cool, If we had a mechanism to assert a value to >>>>>>>>>> its type, in runtime? To avoid performance penalty we could enable >>>>>>>>>> it only >>>>>>>>>> for runtime. Is there a way right now to check whether a value >>>>>>>>>> corresponds >>>>>>>>>> to a type in runtime? Can I implement a custom macro to provide a >>>>>>>>>> good DSL >>>>>>>>>> for this? Is it helpful at all? >>>>>>>>>> >>>>>>>>>> P.S. You may say, use structs and pattern matching would work in >>>>>>>>>> this case. But what if my type is better represented by a tuple: >>>>>>>>>> {atom(), >>>>>>>>>> pos_integer(), string()}. Converting it to a struct might complicate >>>>>>>>>> a way >>>>>>>>>> to work with the value. >>>>>>>>>> >>>>>>>>> -- >>>>>>>>> You received this message because you are subscribed to the Google >>>>>>>>> Groups "elixir-lang-core" group. >>>>>>>>> >>>>>>>> To unsubscribe from this group and stop receiving emails from it, >>>>>>>>> send an email to elixir-lang-co...@googlegroups.com. >>>>>>>> >>>>>>>> >>>>>>>>> To view this discussion on the web visit >>>>>>>>> https://groups.google.com/d/msgid/elixir-lang-core/8c4d9dac-134d-471c-a402-e9696bf5aecf%40googlegroups.com >>>>>>>>> <https://groups.google.com/d/msgid/elixir-lang-core/8c4d9dac-134d-471c-a402-e9696bf5aecf%40googlegroups.com?utm_medium=email&utm_source=footer> >>>>>>>>> . >>>>>>>>> For more options, visit https://groups.google.com/d/optout. >>>>>>>>> >>>>>>>> -- >>>>>>> You received this message because you are subscribed to the Google >>>>>>> Groups "elixir-lang-core" group. >>>>>>> To unsubscribe from this group and stop receiving emails from it, >>>>>>> send an email to elixir-lang-co...@googlegroups.com. >>>>>>> >>>>>> To view this discussion on the web visit >>>>>>> https://groups.google.com/d/msgid/elixir-lang-core/c7e602a5-a694-46f9-99a5-983b4d50eea0%40googlegroups.com >>>>>>> <https://groups.google.com/d/msgid/elixir-lang-core/c7e602a5-a694-46f9-99a5-983b4d50eea0%40googlegroups.com?utm_medium=email&utm_source=footer> >>>>>>> . >>>>>>> For more options, visit https://groups.google.com/d/optout. >>>>>>> >>>>>> -- >>>>> You received this message because you are subscribed to the Google >>>>> Groups "elixir-lang-core" group. >>>>> >>>> To unsubscribe from this group and stop receiving emails from it, send >>>>> an email to elixir-lang-core+unsubscr...@googlegroups.com. >>>> >>>> >>>>> To view this discussion on the web visit >>>>> https://groups.google.com/d/msgid/elixir-lang-core/f6a0f326-ffa4-4b69-998d-6f60a91abe87%40googlegroups.com >>>>> <https://groups.google.com/d/msgid/elixir-lang-core/f6a0f326-ffa4-4b69-998d-6f60a91abe87%40googlegroups.com?utm_medium=email&utm_source=footer> >>>>> . >>>>> For more options, visit https://groups.google.com/d/optout. >>>>> >>>> -- >>>> You received this message because you are subscribed to a topic in the >>>> Google Groups "elixir-lang-core" group. >>>> To unsubscribe from this topic, visit >>>> https://groups.google.com/d/topic/elixir-lang-core/fvn29FjvSks/unsubscribe >>>> . >>>> To unsubscribe from this group and all its topics, send an email to >>>> elixir-lang-core+unsubscr...@googlegroups.com. >>>> To view this discussion on the web visit >>>> https://groups.google.com/d/msgid/elixir-lang-core/CABu8xFBC%3DM6s0p9po2CsoWXQ-j0gRRiyNdGms13YBUt4-sC%2BMg%40mail.gmail.com >>>> <https://groups.google.com/d/msgid/elixir-lang-core/CABu8xFBC%3DM6s0p9po2CsoWXQ-j0gRRiyNdGms13YBUt4-sC%2BMg%40mail.gmail.com?utm_medium=email&utm_source=footer> >>>> . >>>> For more options, visit https://groups.google.com/d/optout. >>>> >>> -- >> You received this message because you are subscribed to the Google Groups >> "elixir-lang-core" group. >> To unsubscribe from this group and stop receiving emails from it, send an >> email to elixir-lang-core+unsubscr...@googlegroups.com. >> > To view this discussion on the web visit >> https://groups.google.com/d/msgid/elixir-lang-core/CADp0H2jzEf38pTd9E8bxXxK%2BG5tGeZRrj0PjNoC5S7FePpDA5g%40mail.gmail.com >> <https://groups.google.com/d/msgid/elixir-lang-core/CADp0H2jzEf38pTd9E8bxXxK%2BG5tGeZRrj0PjNoC5S7FePpDA5g%40mail.gmail.com?utm_medium=email&utm_source=footer> >> . > > >> For more options, visit https://groups.google.com/d/optout. >> > -- > You received this message because you are subscribed to a topic in the > Google Groups "elixir-lang-core" group. > To unsubscribe from this topic, visit > https://groups.google.com/d/topic/elixir-lang-core/fvn29FjvSks/unsubscribe > . > To unsubscribe from this group and all its topics, send an email to > elixir-lang-core+unsubscr...@googlegroups.com. > To view this discussion on the web visit > https://groups.google.com/d/msgid/elixir-lang-core/CABu8xFBN_wh-pFWt9XJhmUmm7xgJPQWfLCkT9BGP2A%3DBqYfnKw%40mail.gmail.com > <https://groups.google.com/d/msgid/elixir-lang-core/CABu8xFBN_wh-pFWt9XJhmUmm7xgJPQWfLCkT9BGP2A%3DBqYfnKw%40mail.gmail.com?utm_medium=email&utm_source=footer> > . > For more options, visit https://groups.google.com/d/optout. > -- You received this message because you are subscribed to the Google Groups "elixir-lang-core" group. To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-core+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CADp0H2h2sEadOftQMHfDXFsOYzEH5DM81JF1Wcfdtvq9M4SUyA%40mail.gmail.com. For more options, visit https://groups.google.com/d/optout.