jhass changed the topic of #crystal-lang to: The Crystal programming language | https://crystal-lang.org | Crystal 0.35.1 | Fund Crystal's development: https://crystal-lang.org/sponsors | GH: https://github.com/crystal-lang/crystal | Docs: https://crystal-lang.org/docs | Gitter: https://gitter.im/crystal-lang/crystal
teardown has quit [Ping timeout: 240 seconds]
teardown has joined #crystal-lang
zorp has quit [Ping timeout: 264 seconds]
postmodern has quit [Quit: Leaving]
DTZUZU has quit [Ping timeout: 246 seconds]
DTZUZU has joined #crystal-lang
<deimos_> is there a Base32 function or shard?
deavmi has quit [Read error: Connection reset by peer]
<FromGitter> <Blacksmoke16> not that im aware of
<FromGitter> <Blacksmoke16> prob wouldnt be too hard to implement?
deavmi has joined #crystal-lang
<deimos_> i started looking at base64.cr to see how the Base64 is written
<FromGitter> <Blacksmoke16> https://github.com/Axentro/crystal-base32
<FromGitter> <Blacksmoke16> looks kinda dated tho, so not sure if it still works
<deimos_> huh, odd, i didn't find it with a search engine
<FromGitter> <Blacksmoke16> gotta up your google fu :p
<deimos_> i'll try it, thanks!
<FromGitter> <Dan-Do> good skill googler :)
f1refly has quit [Ping timeout: 272 seconds]
f1reflyylmao has joined #crystal-lang
_whitelogger has joined #crystal-lang
<FromGitter> <confact> I am trying to create a NamedTuple with dynamic types with macro: ⏎ ⏎ ```code paste, see link``` ⏎ ⏎ I can't get around how to let it know hash is a Hash, so it won't complain about hash.keys. And does it exist a way to get the type of a hash value in macro? [https://gitter.im/crystal-lang/crystal?at=5f65a2c1a9c2c8511ea4dfe7]
<oprypin> @confact: what do you think `{% hash.keys %}` is supposed to do? at compile time, travel into the future, see what keys the Hash will happen to have, then go back and specially compile the program accordingly?
<FromGitter> <confact> @oprypin point taken. I am thinking wrong then. ⏎ ⏎ How can I dynamically add parameters instead of a lot of ifs then? I apparently need a NamedTuple, and NamedTuple is immutable... How can I add and change parameters on a method depending on URL?
<oprypin> confact, not only immutable but also keys need to be fully known at compile time. hmm could you give more details?
<oprypin> so you can indeed do a lot of ifs, nothing wrong with that, or you could make that function just accept its params through a Hash, yea
<FromGitter> <confact> here is the class: ⏎ https://gist.github.com/confact/0b07cd259a6fdfadd41008a5ffb72d02
<FromGitter> <confact> Using Lucky's div method for rendering a div. If only way is for it to accept hash, I will talk to the Lucky people. But wanna try go make it work without first.
<FromGitter> <maxbertinetti> Hi pals. ⏎ Just published the updated VSCode extension for Crystal under the cristal-lang-tools publisher (https://marketplace.visualstudio.com/items?itemName=crystal-lang-tools.crystal-lang ): actually is only the updated version of the one from @faustinoaq (so I started the vesion from 0.5.0) just to test how to publish. ⏎ In this week end I'll work on an Issue and PR template and to update the
<FromGitter> ... README.md. ⏎ ⏎ Next weekend I start the real work for having tests, CI and CD in place. Anyone that want help with this or other IDE plugins for the language or scry is very welcome!! ... [https://gitter.im/crystal-lang/crystal?at=5f65ae27c1d1a53705b0dac5]
<oprypin> confact, no that's just not how it should be done. of course this "div" method should accept just a Hash
<oprypin> if it doesn't, i just cant even
<oprypin> and i thought the reputation of Lucky couldnt go any lower in my eyes
<FromGitter> <confact> Oh, how could i miss that. Let me try.
<FromGitter> <Dan-Do> how to fix this here doc in macro? ⏎ ⏎ ```code paste, see link``` [https://gitter.im/crystal-lang/crystal?at=5f65af82d993b837e072bd4a]
<FromGitter> <Dan-Do> it works if you delete the line break before `if`
<oprypin> Dan-Do, i dont know, looks like it's a bug
<FromGitter> <confact> @oprypin seems they don't: Error: no overload matches 'LoaderComponent#div' with type Hash(String, String)
<oprypin> confact, umm looks like you're required to pass it a block
<FromGitter> <confact> @oprypin haha, thanks, I passed the compiler now. let's see if it works while visiting.
<FromGitter> <confact> @oprypin works - I will add a issue that I have to add a block - I tried hash but not with a block and thought it was not supported..
_whitelogger has joined #crystal-lang
<FromGitter> <maxbertinetti> I also created the Gitter channel for discussion about improvement of VS Code plugin (crystal-lang-tools/vscode) here on Gitter
<FromGitter> <Dan-Do> 💚
zorp has joined #crystal-lang
HumanG33k has joined #crystal-lang
johnny101 has quit [Quit: ZNC 1.7.5 - https://znc.in]
DTZUZU has quit [Ping timeout: 272 seconds]
johnny101 has joined #crystal-lang
<raz> i think all exceptions in crystal should be checked but without a requirement to explicitly declare what a method can throw (just make it a compiler error if something is not handled). change my mind :P
<raz> i think it would nicely force library authors into wrapping their errors into meaningful library-level exceptions and library users into actually handling them
<raz> rather than having some random IO::Error from the depths of nowhere bubble up into my app
<jhass> puts "Hello World" -> begin; puts "Hello World"; rescue e; whatdoidonow; end
<raz> raise Error::FailedToPrint.new
<jhass> just saying it adds noise in many legit situations where you just don't care
<raz> well, if that puts in your library can crash my whole app, i would care!
<jhass> it can!
<jhass> standard library puts can
<jhass> >> STDOUT.close; puts "hi"
<raz> yup, that's why in practice i just never get that warm fuzzy feeling when using any shard that does I/O, which is pretty much all of them
<DeBot> jhass: from ??? - https://carc.in/#/r/9qf2
<raz> "did i handle every timeout/disconnect/whatever exception that might bubble up from here?"
<raz> "i want to distinguish between timeout and connection error, so which exceptions do i catch for that?"
<jhass> people will just put an unrestricted rescue around their entry point to make the compiler shut up and nothing is won
<raz> of course it's doable. but since i have to do it anyway, i wonder if checked exceptions couldn't make it easier
<raz> yes, but these people could then be tarred and feathered and publicly shamed
DTZUZU has joined #crystal-lang
<jhass> looking at java and the languages that evolved from it (say things like kotlin), alot of them seem to get rid of checked exceptions or make them less harsh
<raz> i mean explicit error handling is so en vogue now in other langs (rust, go-lang etc.). i think the idea isn't bad. i just don't like the verbosity of explicitly passing that stuff around all the time. wouldn't checked exceptions be a middle ground?
<raz> yea, i've seen the discussion in java etc.
<raz> for a long time i agreed with them. but somehow i'm starting to lean towards the other side
<jhass> I think it would end up being even noiser, it trades noise on the pass along case for noise on the discard case
<raz> i feel like the java bug is to have *both* checked and unchecked
<jhass> maybe something like an "exhaustive" rescue could be interesting
<jhass> (akin to exhaustive case)
<raz> but having that noise somewhere is inevitable, innit? the alternative is to have timebombs in the code
<raz> yup, exhaustive rescue could be an interesting step in that direction
<jhass> if I put an exhaustive rescue, warn/shame me on missing some exceptions, if I don't, leave me alone
<raz> yup, i like that. only problem i'd see is that then library authors aren't forced to keep their stuff clean
<raz> but that might not be too bad. at least i, as the app author, will have a way to know which collection of IO::Whatnot::Error's my shards can throw at me
<raz> hmm yea, i like that idea al ot
<jhass> wrapping exceptions is a trade off in inflating the number of exceptions you have to handle at app level anyway
* raz plots evil plan for a PR to ameba that will then shame everyone who doesn't use exhaustive rescue :P
<jhass> rescue A::IOishError, B::IOishError, ....; vs rescue IO::Error
<jhass> also too often you see just A::Error for anything
<jhass> so I'm not entirely sold on libs wrapping all exceptions being a good thing yet
<raz> yea, but i think it might be better to force that trade-off rather than silently ignore it (like now). for example, i wrote a shard that does a bunch of crypto stuff. and even i have no idea what exotic exceptions the underlying sodium binding might throw at me under some conditions. the users of *my* shards stand a snowball's chance in hell of figuring that out.
<jhass> get sentry and a supervisor that always restarts your app :P
<raz> well i have all that. but it feels like a very rough solution :D
<raz> i mean erlang style fail-fast and supervision can be a valid strategy. but only when it's pervasive and really supported across the stack
<jhass> mmh, I get ya. I sometimes feel people got the erlang idea wrong by applying it to whole apps rather than the smallest possible component
<raz> yup. crashing the universe by default is fine when your app consists of many universes (some of which are only concerned of restarting yours when it crashed)
<raz> if you only have a single universe, crashing it is kinda a big deal... one web request has a problem, all the others go down too
<raz> anyway, i like the exhaustive rescue idea :)
<raz> the more i think about it, the more i like it. allows to be selective about where to put the burden. since i think in most cases it's only really required in a few places anyway (like on top of the worker pool that wants to stop the whole app from crashing when one of the workers barfs)
johnny101 has quit [Quit: ZNC 1.7.5 - https://znc.in]
johnny101 has joined #crystal-lang
teardown has quit [Ping timeout: 258 seconds]
teardown has joined #crystal-lang
<FromGitter> <j8r> Sometimes I use union types instead of raising exceptions
<FromGitter> <j8r> Especially for "light errors", non critical ones
<jhass> exceptions should be exceptionally rare, they rarely are a good tool to handle common error conditions that consumers are expected to handle frequently as part of normal control flow :)
<FromGitter> <j8r> Yeah, exception should be... an exception :P
<FromGitter> <j8r> Like null, exceptions are convenient but unsafe, type-wise
<FromGitter> <j8r> Languages using null, we have less noises related to `Nil` like in Crystal
<FromGitter> <j8r> I hope Crystal will bring something to the table after `1.0`, could be an `Error` type.
<FromGitter> <naqvis> I've totally opposite view here :P
<FromGitter> <j8r> @naqvis on null or exception?
<FromGitter> <naqvis> and I'll justify myself as ⏎ In your programs, you must handle errors; errors are unavoidable. When you use exceptions correctly and no errors occur, your code is faster than manually error-checked code. If an error does occur, exception handling can sometimes be slower, but you make huge gains in robustness and maintainability over the alternative.
<FromGitter> <naqvis> exceptions
<FromGitter> <naqvis> When used effectively, exceptions can clean out large amounts of error-condition-checking clutter from your code.
<FromGitter> <naqvis> in reality use of exception handling leads to programs that are faster when they execute normally, and better behaved when they fail.
<FromGitter> <j8r> The issue is for usual errors
<FromGitter> <j8r> what to use?
<FromGitter> <j8r> The only thing sane to use is Union types
<FromGitter> <j8r> My point it not rare exception, not a problem (like panic in Go/Rust), but "normal/common" errors
<FromGitter> <j8r> @naqvis the same is true for `null`
<FromGitter> <j8r> performance is marginal
<FromGitter> <naqvis> we can argue against the performance, but i'm sure we will agree on the cluttering that error-check generates
<FromGitter> <j8r> But sometimes we do want to handle it!
<FromGitter> <j8r> Not particularly panic, critical ones, that we want to let crash the program, but normal ones
<FromGitter> <naqvis> true, so does exceptions allow you do that, even with more information then a normal flag
<FromGitter> <naqvis> simple example file handling
<FromGitter> <j8r> Yeah, but unwinding is expensive, and again, not type safe - the use can't know what the method return
<FromGitter> <naqvis> how would you communicate multiple reasons file i/o failes via returning codes?
<FromGitter> <j8r> Union types
<FromGitter> <naqvis> and you expect user of library to perform a detailed case after calling your api?
<FromGitter> <naqvis> just to see what it returns?
<FromGitter> <j8r> then if the use really don't want to handle some/all possible, it could use `#unwrap`
<FromGitter> <j8r> Do you know all possible errors opening a file can return? That's not easy now :/
<FromGitter> <naqvis> i would say exceptions mechanism exists for a reason and if it was a bad practice, we wouldn't be having support for this in almost all mainstream languages
<FromGitter> <naqvis> golang is an exception :D
<FromGitter> <j8r> Sure, like `null`
<FromGitter> <j8r> But there are languages trying to improve this
<FromGitter> <j8r> Rust, Haskell...
<FromGitter> <naqvis> i'm curious how would you handle failure cases in constructor?
<FromGitter> <j8r> A `self.new` is needed
<FromGitter> <naqvis> will you still go through whole object instantiation and in the end telling user, sorry, even i get instantiated but i've some problem
<FromGitter> <j8r> Not sure to understand?
<FromGitter> <j8r> That's not a problem?
<FromGitter> <naqvis> but that's still raising
<FromGitter> <Blacksmoke16> my opinion is exceptions are prob better/clearer the majority of the time. If you need more perf, then that would be a time to consider doing a more union type approach
<FromGitter> <j8r> @naqvis yeah, I mean by returning types we will know directly what possible errors can happen
<FromGitter> <j8r> I'm happy whatever the technical solution is
<FromGitter> <j8r> Just want to know all possible (common) errors :)
<FromGitter> <mattrberry> > *<repo>* if you *really* want to have them in something you can later iterate over, you might want to store them in a tuple instead. This way you still have max type safety ⏎ ⏎ Thanks for the info! I'm using an array because I'd like to simply index to a proc at runtime with minimal lookup cost. If you can think of a better way to define an indexable structure at compile time, I'm open to ideas! I'll
<FromGitter> ... take a look at the link you sent!
<FromGitter> <j8r> And for now in Crystal, that's too easy to miss an exception, so we catch all :/
<FromGitter> <j8r> and that's bad. Or having 2 main errors
<FromGitter> <j8r> One for Critical, other for normal, don't know
<FromGitter> <Blacksmoke16> :thinking:
<FromGitter> <naqvis> sorry, i might have missed the context of initial dialogue
<FromGitter> <naqvis> I thought we were talking about the merits of exceptions :P
<FromGitter> <j8r> ha...
<FromGitter> <j8r> sure they are merits haha
<FromGitter> <j8r> for me it would be fine: https://carc.in/#/r/9qfm
<FromGitter> <Blacksmoke16> im not sure i would want to deal with that with a largeish program
<FromGitter> <naqvis> ```code paste, see link``` [https://gitter.im/crystal-lang/crystal?at=5f66511ff51808513b53d89d]
<yxhuvud> That is really nice but the union for file io are so huge :/
<FromGitter> <j8r> Yeah, then call `#unwrap` ;)
<FromGitter> <naqvis> this is the part which I dislike about returning unions to api end-users :)
<FromGitter> <Blacksmoke16> or how about just not do any of that and use exceptions :p
<FromGitter> <naqvis> this approach put too much workload on user side, as they need to distill through Unions to find what get returned
<FromGitter> <j8r> As I said, the possible errors are not obvious
<FromGitter> <naqvis> honestly, not a very big fan
<FromGitter> <j8r> so calling `#unwrap` is such a big deal?
<FromGitter> <j8r> that's very like dealing with `Nil`, with `#not_nil!`
<FromGitter> <naqvis> how and when to call `unwrap`??
<FromGitter> <naqvis> its like golang, where its idiomatic to check for error after every call
<FromGitter> <j8r> When you don't want to deal with unions, and like exceptions
<FromGitter> <j8r> that's a bit like `#not_nil!`
<FromGitter> <naqvis> my point is, if there is an error, say it loud
<FromGitter> <naqvis> if no, then please don't let me go through that case statement
<FromGitter> <j8r> that's sometimes useful, sometimes not
<FromGitter> <naqvis> and if know something going to fail, i better put that in rescue block
<FromGitter> <Blacksmoke16> i think it would also force other usages of the downstream code to worry about this, i.e. require custom error handling just for this one thing, versus having a system that works across all programs and thats built into the lang
<FromGitter> <naqvis> so that i can deal with that, when that occurs, or else i can go with normal flow
<FromGitter> <Blacksmoke16> exception stuff also comes with `else` and `ensure` which are pretty neat
<FromGitter> <Blacksmoke16> which idt you could do with case/unions?
<FromGitter> <Blacksmoke16> maybe `else`
<FromGitter> <j8r> at the end case/when and ensure/else are similar
<FromGitter> <j8r> but exceptions let you not deal with it directly
<FromGitter> <Blacksmoke16> hm?
<FromGitter> <j8r> by returning an union, we have to deal with each case directly
<FromGitter> <j8r> and exception can be handled upstream
<raz> `exhaustive rescue`
<raz> and all is good!
<FromGitter> <Blacksmoke16> exception bubbling can be a good thing
<raz> yes, it's the best of all worlds
<FromGitter> <Blacksmoke16> can have higher level code to handle unhandled exceptions
<raz> j8r gets his union type (well, a set of exceptions), naqvis doesn't have the verbosity of needing to declare it all the time, blacksmoke16 gets bubbles
<raz> and everyone gets the safety of all exceptions getting handled somewhere (as long as there's an exhaustive rescue in the chain)
<FromGitter> <Blacksmoke16> i think there are use cases for the union stuff, but not that it should be used over exceptions all the time
<raz> rust and golang have tried the union stuff, it sucks
<FromGitter> <j8r> The Swift approach is neat
<FromGitter> <Blacksmoke16> mainly for graceful handling of common errors in an isolated context
<FromGitter> <j8r> > Error handling in Swift resembles exception handling in other languages, with the use of the try, catch and throw keywords. Unlike exception handling in many languages—including Objective-C—error handling in Swift does not involve unwinding the call stack, a process that can be computationally expensive. As such, the performance characteristics of a throw statement are comparable to those of a return
<FromGitter> ... statement.
<FromGitter> <Blacksmoke16> can we not just make unwinding lazy/only happen when/if requested?
<FromGitter> <Blacksmoke16> i.e. you call like `.inspect_with_backtrace` or something
<yxhuvud> well, you still have to figure out how far to roll back the stack
<yxhuvud> which perhaps don't need to be heavy. *shrug*
<raz> why is that a problem? exceptions are not meant for flow control
<FromGitter> <j8r> Swift is goof for perf, but still not type safe
<FromGitter> <j8r> *for errors
<raz> when an error occurs, those couple extra microseconds are the least of my worries. at least i haven't yet run into a case where exceptions would've been the bottleneck
<FromGitter> <Blacksmoke16> ^
<FromGitter> <j8r> For example, today I got a little issue: what's the possible errors that prevent me to have a `Bcrypt` password. I had to look at the source code :/
<raz> and i mean, if it really becomes a bottleneck somewhere - you should probably use a retval instead of an exception for this
<raz> j8r++
<FromGitter> <Blacksmoke16> i have an issue created about that, something that allows viewing that in the api docs
<raz> `exhaustive rescue` seems such an awesome idea to me. game changer
<raz> all the other languages will be jealous and secretly copy it under different names
<FromGitter> <Blacksmoke16> yea
<FromGitter> <naqvis> agree and if someone is really after some realtime software development, they won't be considering gc enabled languages for that :P
<FromGitter> <j8r> Some suggests to have an external tool, would be a lot better to have this built-in
<raz> yea it should be in the compiler
<raz> and normal rescue should continue to exist. so you can decide where you want to care about _some_ errors and where you want to care about all of them
<raz> handle at the low level what can be handled there. make sure nothing gets missed at a higher level
<raz> and have it all auto-documented
<FromGitter> <j8r> raz 💯
<raz> maybe `rescue` vs `rescue!` (exhaustive)
<FromGitter> <Blacksmoke16> `catch` :trollface:
<FromGitter> <j8r> The current easy way is union types, that's why I use it in some part of my code
<FromGitter> <j8r> or, yield a block
<raz> yea but that's non-standard. i can't benefit from it when i uses your shard
<FromGitter> <j8r> that's true yes
<FromGitter> <j8r> I don't use this for libraries
<raz> and other parts of your code, or the shards you include, can still throw random stuff at me
<FromGitter> <j8r> yep
johnny101 has quit [Ping timeout: 260 seconds]
<yxhuvud> Hmm, I hope .36 is out soon.
<FromGitter> <naqvis> do we have 0.36? i thought it would be 1.0
<FromGitter> <Blacksmoke16> yea atm its `1.0.0-dev`
<yxhuvud> well, point was that I wanted never code than 0.35.1. Exact version doesn't matter.
<yxhuvud> mainly because I do horrible things and huge monkeypatches of core classes and accidentally used HEAD instead of the released version :D
<FromGitter> <mattrberry> Is it possible to define a class to be only generic over Number types?
<FromGitter> <Blacksmoke16> at that point it wouldnt need to be generic?
<jhass> I can imagine something like {% raise ... if T something %} within def initialize, but yeah, why not make it non-generic and just restrict your inputs to Number?
<FromGitter> <mattrberry> I'd like to allow users to define the class over say a UInt8 vs a UInt32, but I want to restrict the types allowed
<yxhuvud> What do you actually mean by making it generic over number types?
<FromGitter> <Blacksmoke16> got an example?
<yxhuvud> Well, adding type restrictions can make for better errors. I can totally see it be nice to have that on generic arguments too. But I am fairly certain that doesn't exist.
<FromGitter> <Blacksmoke16> like do you want to have a generic type where you can do `MyType(UInt32)` but not `MyType(String)`?
<FromGitter> <mattrberry> Exactly
<FromGitter> <Blacksmoke16> yea just do what jhass said then
<FromGitter> <Blacksmoke16> `{% raise "Generics can only be Number.class" unless T <= Number %}` or something like that
<FromGitter> <mattrberry> Oops, I missed that message somehow. Sounds good, I guess I'll just do that then. Thanks everyone
<yxhuvud> That is runtime error though, right?
<jhass> not within {% %} :)
<yxhuvud> you can raise in arbitrary macros? things I learned.
<FromGitter> <Blacksmoke16> yess, custom compile time errors essentially
<jhass> you can puts too, handy for warnings
<FromGitter> <Blacksmoke16> related https://github.com/crystal-lang/crystal/issues/9246
<raz> but careful, puts can raise exceptions :p
<jhass> damn, we need macro rescue after all
<raz> exhaustive macro rescue please!
<FromGitter> <Blacksmoke16> RIP, i broke something `BUG: trying to downcast Athena::Validator::Constraints::Range(B, E) <- Athena::Validator::Constraint+ (Exception)`
<FromGitter> <Blacksmoke16> sigh
<raz> i'm kinda surprised i never run into those types of bugs, despite doing rather weird stuff occassionally
<raz> might be cause i usually build my crazier contraptions with macros rather than complex inheritance
<FromGitter> <Blacksmoke16> im not even using complex inheritance, at most 3 levels
<raz> the only compiler bug i remember sometimes seeing is a transient one. where it bugs out with something but then the next compile just works.
<FromGitter> <Blacksmoke16> issue is prob related to how some children use generics and some dont
<raz> well, if it's reproducible then there's surely a way to figure out
<FromGitter> <Blacksmoke16> ofc, imma try removing generics, but gets a bit frustrating to always have to workaround compiler bugs like this
<raz> yup, can imagine
<FromGitter> <j8r> with great power come great bugs
<raz> ^
<raz> but i mean, if it's a legit compiler bug, it should be fixed there and your code doesn't need to change
<raz> unless the fix means that your code is invalid ofc
<FromGitter> <j8r> sad
<raz> yea, when asterite talks like a dentist it must be something serious ("filling holes or providing more overloads")
zorp has quit [Ping timeout: 272 seconds]
<FromGitter> <j8r> There are 2 kinds of "bugs": ⏎ ⏎ 1) those that should work, but don't ⏎ 2) those that are in fact not, but with cryptic compiler errors [https://gitter.im/crystal-lang/crystal?at=5f6673523651184d30000f03]
<raz> yea. well, at least stuff involving `abstract` doesn't worry me anymore. i just avoid it.
<jhass> nobody needs abstract anyhow
<raz> for a while i thought i like it. but at some point i just end up removing it anyway cause i need something extra
<FromGitter> <j8r> that's a nice plus, to limit duck typing
<raz> i mean, is there anything that can *only* be done with abstract?
<FromGitter> <j8r> That's quite handy for documentation
<FromGitter> <j8r> also, `abstract struct`
<FromGitter> <j8r> otherwise you can't inherit from it.
<raz> hmmm
<jhass> just use a module
<raz> sometimes i feel like people just go to deep into that OO rabbit hole
<FromGitter> <j8r> `abstract class` is more useful
<FromGitter> <j8r> Then, how to impose a children to be a class?
<jhass> you don't need to
<FromGitter> <j8r> I need, because they are mutable
<FromGitter> <j8r> otherwise, using structs does not mutate the children
<jhass> no, you want to babysit your user and be okay with them ignoring your docs. there's a diff
<FromGitter> <Blacksmoke16> yea... i always seem to kinda run into this stuff
<FromGitter> <j8r> That's just a pros compared to using module
<FromGitter> <j8r> For the same result
<FromGitter> <j8r> There is possibility for an error on using module, but none using `abstract class`
<raz> perhaps a simpler mechanism for cases like that could be found (interfaces?) and abstract be dropped
<FromGitter> <j8r> the opposite is not true AFAIK, I don't see any reason to force an user to use a struct, except maybe perf?
<jhass> it's really a mindest issue not a technical one. you want to destroy all footguns. I want them pointed out so I can be careful with them in case I need them other than you intended
<FromGitter> <j8r> Both don't exclude each other
<jhass> Ruby's so full of those footguns, that's what makes it sometimes so fun and powerful. Crystal started with spirit. All this abstract stuff is an instance of going to the Javaesque "the right way and only the right way" camp
<FromGitter> <Blacksmoke16> in my context inheritance makes more sense than a module
<FromGitter> <Blacksmoke16> depends on how you want it to work which would be better
<jhass> fwiw a module still is inheritance
<FromGitter> <j8r> Agree
<FromGitter> <j8r> For me I have very rarely more than 1 level of inheritance/inclusion, so does not matter much
<FromGitter> <Blacksmoke16> to me a module is more useful when you want to mixin something into an arbitrary type, versus have a more rigid structure
<raz> perhaps `class_module` and `struct_module` could be an option (to restrict what can include them, where `module` can still be included by both)
<jhass> please. just no.
<jhass> you're making it worse :P
<FromGitter> <Blacksmoke16> like any type can be `Comparable`, but it wouldnt make sense to include my `Constraint` type into something arbitrary
<raz> well, i don't like footguns either :p
<raz> i like the idea of abstract. it just seems the compiler complexity to support it is a bit crazy.
<jhass> it's bolted on and at least half redundant, that's the problem
<FromGitter> <j8r> I see something like: ⏎ ⏎ ```Matter of taste``` [https://gitter.im/crystal-lang/crystal?at=5f667785b190f2328e83e2ff]
<jhass> language wasn't initially designed for it. (well it wasn't designed much if we're honest, but it came pretty late)
<FromGitter> <j8r> Does traits could be the solution? Don't know
<jhass> that's just modules, again
<FromGitter> <Blacksmoke16> `Car` makes more sense as a parent
<FromGitter> <j8r> languages like multiple fancy names for little difference 😅
<FromGitter> <Blacksmoke16> is like saying "i want to take the implementation of a Car, but add to it/modify it in some way"
<FromGitter> <j8r> yep, and `Car` have abstract functions, implemented by an Engine module for instance
<FromGitter> <Blacksmoke16> plus are some cases where idt the compiler can correctly do initializers with modules
<FromGitter> <Blacksmoke16> and re the initial comment that started this, idt i can get rid of my generics because of
<jhass> what's the parent of amphibian vehicle? Car or Boat? It's a metapher, don't make technical decisions on it
<FromGitter> <Blacksmoke16> ```code paste, see link``` [https://gitter.im/crystal-lang/crystal?at=5f66786fa9c2c8511ea6d406]
<FromGitter> <Blacksmoke16> `Error: instance variable '@range' of Athena::Validator::Constraints::Size must be Range(Int32 | Nil, Int32 | Nil), not Range(Int32, Nil)`
<FromGitter> <Blacksmoke16> jhass i think in that context it would probably share at least some characteristics with a parent, just heavily modified
<jhass> besides my point, but okay ;)
<FromGitter> <Blacksmoke16> now i need to try and hack around this bug :/
<FromGitter> <Blacksmoke16> hmm
<raz> smoke: i like your code cause it always shows me how different our brains are
<FromGitter> <j8r> jhass that's can be true, here it is impossible to have an amphibian vehicle: and that's a feature
<raz> i would've def written `constraints` to take a proc
<FromGitter> <Blacksmoke16> :thinking: why a proc?
<raz> and surely it wouldn't have worked and i'd have to use crazy macro sledge hammers to somehow beat it into submission
<raz> so that the user can just write `constraints: { value.size > 10 && value == 'foobar' }`
<raz> (or something to that effect)
<FromGitter> <j8r> I usually want to force an object to be only one thing, and not be a glob of everything - which can be possible. This can then run into bugs, because of same ivars/methods that are overridden etc
<FromGitter> <Blacksmoke16> not that simple raz, there is another obj that handles the actual validation of the value, the constraint is an abstraction around *how* the value gets validated
<FromGitter> <Blacksmoke16> like optional/extra options, error messages etc
<FromGitter> <Blacksmoke16> otherwise you dont have a way to do custom messages etc
<raz> blacksmoke: `constraints: { raise FancyError.new("i'm a custom error message") unless value.size > 10 && value == 'foobar' }`
<raz> :p
<raz> but i'm not saying that's necessarily better or a righter way. something just feels wrong to me about effectively re-inventing operations like `==` as classes
<FromGitter> <Blacksmoke16> ```code paste, see link``` [https://gitter.im/crystal-lang/crystal?at=5f667aa689b38d09213b4c9c]
<FromGitter> <Blacksmoke16> is how its setup atm `initializer` is a helper method to define the default initializer since this constraint doesnt need anything extra/custom
<raz> hm yea. for your version i just have to read and understand so much code
<FromGitter> <Blacksmoke16> if you're looking at the source, otherwise it would just be like
<FromGitter> <Blacksmoke16> ```@[Assert::NotNull] ⏎ property name : String?``` [https://gitter.im/crystal-lang/crystal?at=5f667b11a857200e6d6f97d6]
<FromGitter> <Blacksmoke16> :p
<raz> yeh well. when i want `!foo.nil?` i generally prefer to write just that. rather than `Athena::Validator::Constraints::NotNull` ;)
<raz> but i can also see the advantages in your style, safety, auto-docs, etc.
<FromGitter> <Blacksmoke16> is also the difference in trying to make a generic framework, versus a oneoff implementation
<FromGitter> <Blacksmoke16> yes, reusability, auto docs, testability etc
<raz> but the verbosity 🙈
<FromGitter> <Blacksmoke16> :rolls_eyes: im not sure i see the problem, its not like you have to write any of it other than apply an annotation?
<raz> nah. it's just... i mean... have you ever tried passing it to javac? it almost looks like it might just compile :p
<FromGitter> <Blacksmoke16> its actually based on the java validation framework spec :P
<raz> ha!
<FromGitter> <Blacksmoke16> well the PHP implementation of the java implementation :S
<FromGitter> <Blacksmoke16> adapted to Crystal xD
<raz> well, not saying it's bad. in fact i think it's great to have a framework in that style in crystal, in addition to the less "enterprisey" ones
<raz> and who knows, maybe it even turns out to be the best way to do it in the end
<jhass> meanwhile our heritage just does this https://dry-rb.org/gems/dry-validation/1.5/
<FromGitter> <Blacksmoke16> i try to make everything generic and flexible, hence the reliance on annotations. this way you're not dependent on a DSL specific to validations, while also needing one specific to your ORM and another one specific to serialization...
<FromGitter> <Blacksmoke16> include a module, add an annotation to an ivar/method and call it a day
<raz> yea, i have yet to see a proper real world athena app to see how it all clicks together
<raz> with the snippets you post i often don't even know how much of that i'd have to write in practice (as you say, if i just write that once somewhere and then re-use it with annotations, all my reluctance could vanish)
<FromGitter> <Blacksmoke16> thats the idea :)
<raz> is there a small athena demo app somewhere? (e.g. the obligatory todo-app)
<FromGitter> <Blacksmoke16> i like to think its the most flexible framework :P
<FromGitter> <Blacksmoke16> https://github.com/Blacksmoke16/athena-blog-tutorial and the related blog post
<raz> ah, gotta take a peek
<FromGitter> <Blacksmoke16> with code comments included 😉
<raz> yea but...
<raz> my first question was "hm, what is a converter?"
<raz> your API docs don't tell me. and the comment in that file says "Define a custom `ASR::ObjectConstructorInterface` to allow sourcing the model from the database
<raz> # as part of `PUT` requests, and if the type is a `Granite::Base`"
<raz> not good
<raz> comments should not tell me what you are doing, the code already does that
<raz> they should tell me *why* you're doing it :D
<FromGitter> <Blacksmoke16> thats in the blog :P
<raz> then the comment should say "read the blog!"
<raz> :p
* FromGitter * Blacksmoke16 updates every comment to say "read the blog" 😉
* raz reads the blog
<FromGitter> <Blacksmoke16> but to answer your question, i assume you mean a param converter?
<raz> well a converter in general
<FromGitter> <Blacksmoke16> the example i always use is its a way to apply custom logic to take a primitive value and "convert" it into something more complex
<raz> in the context of athena. i mean, i can assume it converts things. but why do i need it, how is it used. i don't think i've used a framework that had converters before (or at least not under that name)
<FromGitter> <Blacksmoke16> E.x. imagine you have an endpoint `GET /user/{id}`
<FromGitter> <Blacksmoke16> other frameworks give the `id` in the controller action
<FromGitter> <Blacksmoke16> a converter would allow you to take that `id`, lookup the related user, and supply the actual user obj to the controller action
<raz> ok yup, got it... hmpf...
<raz> `@[ADI::Register(alias: ASR::ObjectConstructorInterface)]`
<FromGitter> <Blacksmoke16> essentially abstracts how the user gets supplied, making testing easier, and reducing boilerplate
<FromGitter> <Blacksmoke16> DI stuff, thats a topic in of itself
<raz> yup i guess that's my problem with athena. i have the attention span of a fruit fly.
<FromGitter> <Blacksmoke16> tl;dr DI would supply that type where other types that have constructor args typed to `ASR::ObjectConstructorInterface`
<FromGitter> <Blacksmoke16> DI is super cool, its my fav shard so far :P
<raz> i can imagine. i'll have to look into it. my problem is just that i have to say that about every 2nd line in that code :D
<raz> but then again... i remember starting with rails wasn't much different back in the day
<FromGitter> <Blacksmoke16> all of that stuff is optional ofc
<raz> everyone made these amazing tutorials, "look, a facebook in only 50 LOC!"
<FromGitter> <Blacksmoke16> simplest example would be
<raz> and it took an eternity to figure out what black magic these lines even invoke
<FromGitter> <Blacksmoke16> ```code paste, see link``` [https://gitter.im/crystal-lang/crystal?at=5f66800eea62d70e6521e8e5]
<raz> yea that looks much more digestable
<FromGitter> <Blacksmoke16> yup, and is totally fine for simple apps just starting out
<FromGitter> <Blacksmoke16> but the abstractions are there if/when you need/want htem
<raz> i feel like for promoting athena to people like me (not sure if i'm the target audience ;)) it would help to have a bare minimum example. like a todo app that only uses what's absolutely needed. then i can be like "ok, and now i want fancier validations" and read up on them. rather than being smacked over the head with `@[ADI::Register(alias: ASR::ObjectConstructorInterface)]`
<FromGitter> <Blacksmoke16> also realize that was a full on blog post app, not general high level intro
<FromGitter> <Blacksmoke16> would prob be a better introduction for you (and others)
<raz> ok gotcha, i should do more reading and less complaining
<FromGitter> <Blacksmoke16> indeed :p
<FromGitter> <Blacksmoke16> but yes, the blog post was like "im going to use all the features just to touch on everything", doesn't actually mean they're all required/actually necessary
<raz> hm i still feel a todo app might be nice tho. something i can git-clone and run with.
<FromGitter> <Blacksmoke16> oof, but then i need to make a UI :P
<raz> exactly :p
<FromGitter> <Blacksmoke16> server rendered HTML is possible deff lacking abstractions
<raz> well, maybe just a "todo api" ;)
<FromGitter> <Blacksmoke16> now that i could do 😉
<FromGitter> <Blacksmoke16> is essentially what the blog is, create user, login, crate article, read article(s), update/delete article :P
<FromGitter> <Blacksmoke16> create*
<raz> the readme just kinda leaves me alone after "hello world". and the step up to the blog app throws me right in the middle of ObjectConstructorInterfaces
<FromGitter> <Blacksmoke16> only config requirement iirc is like `export SECRET=FOO` export some secret, and ofc have a db running
<FromGitter> <Blacksmoke16> but yea, updating the readme with how to run this w/o looking at the blog is prob a good idea
<raz> yea i think that's kinda what i want to know. what's the minimum viable app here. for kemal it's 3 lines of code. and then a super-flat learning curve. how many is it for athena and how many of them do i have to research to understand what they do. :) kinda speaking from the "newbie looking to try athena" perspective.
<FromGitter> <Blacksmoke16> @dscottboggs might have some feedback as he went thru it
<FromGitter> <Blacksmoke16> the example i shared earlier would be the simplest
<FromGitter> <Blacksmoke16> then from there you can research things as you discover a need for them
<FromGitter> <Blacksmoke16> goes back to that premature optimization quote :p
<raz> gonna do some reading
<FromGitter> <Blacksmoke16> id read thru the getting started link, id be happy to answer anything beyond that as well
<raz> yup yup
<FromGitter> <Blacksmoke16> 👍
zorp has joined #crystal-lang