miha-x64
02/26/2019, 8:15 AMMarc Knaup
02/26/2019, 9:07 AMmiha-x64
02/26/2019, 9:22 AMmiha-x64
02/26/2019, 9:23 AMMarc Knaup
02/26/2019, 9:52 AMmiha-x64
02/26/2019, 10:09 AMMarc Knaup
02/26/2019, 10:36 AMis
and as
as well as all smart-case benefits for instances and instance properties
• it's quite confusing that if you set the type of something to : Player
or : User
you won't get an actual instance but merely a schema
• plenty of operations are remapped/reinvited as - I don't even know how to call them - for example CharSequencez.…
, Objectz.…
- that's basically yelling at you "something is horribly wrong here"
• high amount of boilerplate even for simple things
• you lose some compile-time guarantees (what if I build an instance but a property is missing? likely fails at runtime)
•
override fun saveOrRestore(d: PropertyIo) {
d x emailProp
d x nameProp
d x surnameProp
}
or
val buttonClickedProp = propertyOf(false).also {
// reset flag and perform action — patch 'user' with values from memory
it.clearEachAnd {
user.transaction { t ->
t.setFrom<User>(editableUser, User.Email + User.Name + User.Surname)
}
}
}
How are these (and similar weird constructs) easier to understand and less magical than clean generated code which I can inspect, read and debug as if I've written it by myself, with standard Kotlin?
I see that it's a clever solution, but it's not easy, not transparent, not without magic but complex instead.
Regarding performance:
• using a HashMap for every single object instance adds a lot of overhead already (you're basically creating a semi-dynamic type system here)
• every single property is wrapped in a wrapper object, hidden behind an interface - +1 object allocation per property per object, +n virtual function calls for every single property access while probably killing all possibilities JVM-internal property access caching
• because generics are used, every single primitive value is boxed - ~+1 object allocation per primitive property value
• because generics are used, every single inline class instance is boxed - +1 object allocation per inline class property value
That's just a quick first shot without going deeper. But by far enough reasons for me not to.miha-x64
02/26/2019, 10:48 AMcannot use data classesyep, hashCode/equals/toString are implemented, destructuring and copy are not, you're right
you lose the ability to useThese operators are evil should never exist. But you're still can check struct.schema == anotherSchemaandis
as
it's quite confusingWell, a habit comes with time. Also, it's quite confusing when you have no control over your class, as it happens with reflection/codegen.
that's basically yelling at you "something is horribly wrong here"They're basically yelling "Kotlin cannot deduplicate method references, Android will suck at loading many classes"
high amount of boilerplatecomparing to...
you lose some compile-time guaranteesYou also don't have them when your DTO schema cannot be mapped to actual JSON/SQLite/Prefs.
I still think it is better then writing bothd x emailProp
parcel.writeString
and parcel.readString
for a single property
using a HashMap for every single objectOf course I don't.
every single property is wrapped in a wrapper objectOnly mutable ones, which could be bound directly to views, e. g.
textView.bindTextTo(user prop Name)
.
Any better ways to do observability? I know only worse ones.
every single primitive value is boxedYep, it is known problem with no acceptable solution, but tens of boxes are negligible if compared to a single
TextView#onMeasure
.Marc Knaup
02/26/2019, 11:36 AM> cannot use data classes
yep, hashCode/equals/toString are implemented, destructuring and copy are not, you're rightCan I override them?
> you lose the ability to useandis
as
These operators are evil should never exist. But you're still can check struct.schema == anotherSchemaThey are perfectly fine and more safe than casting between generics which I would have to do after I've compare schemas.
> it's quite confusing
Well, a habit comes with time. Also, it's quite confusing when you have no control over your class, as it happens with reflection/codegen.If I and all developers who take over my code have such a steep learning curve then it's too complicated. Sure, I can learn everything with time, so that's not a helpful argument here. What do you mean by no control?
> that's basically yelling at you "something is horribly wrong here"
They're basically yelling "Kotlin cannot deduplicate method references, Android will suck at loading many classes"Method overload resolution works quite well, even for references 🤔 The issue here is reinventing the wheel at stdlib-level. If that's needed than building an own programming language is probably more suitable than a library imo.
> high amount of boilerplate
comparing to...schema definition vs. simple (data) class, schema instance creation vs. simple POJO, having to use
Something<Foo>
everywhere where just POJO Foo
would be fine, wrapping every single value in propertyOf
> you lose some compile-time guarantees
You also don't have them when your DTO schema cannot be mapped to actual JSON/SQLite/Prefs.They're only lost for DTOs which is normal and unavoidable, but in the library they're lost also when manually writing Kotlin code which is the important case.
>d x emailProp
I still think it is better then writing bothAnd I prefer clarity over brevity. Too much magic hidden behind non-self-explaining infix operator and variable name.andparcel.writeString
for a single propertyparcel.readString
Marc Knaup
02/26/2019, 11:36 AM> using a HashMap for every single object
Of course I don't.True, Array here mostly. Nice solution! Still causes boxing though.
> every single property is wrapped in a wrapper object
Only mutable ones, which could be bound directly to views, e. g.True, only for mutating, observing, global variables and whenever.textView.bindTextTo(user prop Name)
propertyOf
makes sense.
Any better ways to do observability? I know only worse ones.Yes, no observability! It completely circumvents proper abstractions by giving every single piece of an application, even the tiniest one, direct access to the data model where they respond to and cause changes independently without knowing any context of the change. It's like KVO on iOS which causes more issues than it actually solves due to unexpected side-effects which are very difficult to anticipate in advance and there's no control over the order in which changes are applied. Rather than random elements all over the place updating their own state on model changes - or even worse directly updating some global/high-level model - all updates should be properly communicated top-down (Activity knows much more context of the change than a single TextField for example and thus decides how to propagate downwards) and all modification should be communicated bottom-up (TextField triggers the change, reports upward to the Activity which can put the change into proper context and cause the appropriate action).
> every single primitive value is boxed
Yep, it is known problem with no acceptable solution, but tens of boxes are negligible if compared to a singleI don't see how boxing helps preventing.TextView#onMeasure
TextView#onMeasure
. Usually you simply compare new state to old state and if there's a change then a measurement is needed. Works perfectly without any property observation or other magic, although a bit more boilerplate, but I take that for clarity and predictability.miha-x64
02/26/2019, 12:01 PMCan I override them?You can define extensions:
operator fun Struct<Something> component1() = this[Something.Field]
.
But I don't really know why you want destructuring for non-tuples.
safer than casting between genericsThat's true. But typecasting as such just yells "something went wrong!"
What do you mean by no control?For example, with Gson you can declare
@JsonAdapter([De]Serializer::class)
, but it will be instantiated with parameterless constructor. This means that YourDto::class and [De]Serializer::class are singletons, i. e. cannot have any parameters/dependencies.
With my approach, Schemas are just objects, so they can have parameters — you don't need any special annotations to choose converters or names for properties.
Method overload resolution works quite well, even for referencesI'm talking about a different problem: for n
SomeClass::method
expressions compiler will generate n classes. While this issue exists, I will workaround it in wild ways.
https://youtrack.jetbrains.com/issue/KT-15690
schema definition vs. simple (data) class, schema instance creation vs. simple POJO
class User(
val name: String
val surname: String
)
User(
name = "John",
surname = "Galt"
)
object User : Schema<User> {
val Name = "name" let string
val Surname = "surname" let string
}
User.build {
it[Name] = "Hank"
it[Surname] = "Rearden"
}
The noticeable difference is that Name
symbol is duplicated by "name"
string literal, etc. That's the price I ready to pay for keeping in-program identifier and exposed name apart.
having to usethere are different forms of `Something`: write-onlyeverywhere where just POJOSomething<Foo>
would be fineFoo
StructBuilder
, read-only StructSnapshot
, mutable & observable ObservablePropertyStruct
, Record
for RDBMS, SharedPreferencesStruct
for Android prefs. With no additional boilerplate, for free!
Too much magic hidden behind non-self-explaining infix operator and variable name.Totally agree.
d
was renamed to io
, and I want to rename x
.
Still causes boxing though.It is also avoidable for non-observable structs, but it's not the main priority.
Yes, no observability!It's optional.
:presistence
module does not depend on reactive :properties
.
no control over the order in which changes are appliedI'm currently designing a solution which will allow applying changes transactionally, without transient states.
I don't see how boxing helps preventingOf course not, it's just a comparison. I think that these boxings are insignificant..TextView#onMeasure