2 ms·
class Person { def age; } There is a second-class "age" property: the only way to specify it or use it is to first declare the ADT/Person class. (In t
by wellpast 9y ago
class Person {
def age;
}
There is a second-class "age" property: the only way to specify it or use it is to first declare the ADT/Person class. (In this example, I use a dynamic language; that is, the property can be given any type of value.)
Here is the usage of a first-class age property:
public boolean isOld(Map<String, Object> entity) {
return entity.getAs("age", int.class) > 30;
}
Note that in this example, age's semantics are independent of any Person/ADT. Now a statically-typed language that would let me define a vocabulary that include "age" and its type independent of an ADT would be supporting first-class properties.
I could cheat and do this in a typed language like Java like so:
class Age { int val; } // using class to define a property
class Entity { Age age; ...and list all other properties here... }
But this is absurdly cumbersome and obviously Java isn't conducive to this.
- Bogdanp 9y ago> Note that in this example, age's semantics are independent of any Person/ADT. Now a statically-typed language that would let me define a vocabulary that include "age" and its type independent of an ADT would be supporting first-class properties. What you're referring to is known as row polymorphism or structural typing and it exists in languages such as Elm, Purescript, Scala and Go (and I believe Haskell has these as an extension). For instance, this is valid Scala: def isOld(thing: {def age: Int}): Boolean = thing.age > 30 case class Person(name: String, age: Int) case class Dog(age: Int) println(isOld(Person("Jim", 25)) println(isOld(Dog(31)) And here's Elm: isOld : {age: Int | a} -> Bool isOld thing = thing.age > 30 Debug.log (isOld {name: "Jim", age: 42}) Debug.log (isOld {age: 10})
- wellpast 9y agoThis is pretty cool. The Scala version is a little weird. Why do I need a separate Person and Dog class? Can't a dog have a name as well? Why do I need to declare that upfront or ensure that no other part of code can give a Dog a name? That's the problem with ADTs; there's all this impedance. The Elm version is much better. Except of course I have to upfront at the method signature declare my property requirements -- while that is also a burden, it is a much lesser burden than having ADT/taxonomy of Person, Dog, etc.
- Bogdanp 9y ago> The Elm version is much better. Except of course I have to upfront at the method signature declare my property requirements -- while that is also a burden, it is a much lesser burden than having ADT/taxonomy of Person, Dog, etc. Elm (and Purescript) should be able to fully infer the type of that function w/o forcing you to declare which fields you want to use up-front.
- wellpast 9y agoHow would it infer: isOld(parseJson(json)) When parseJson may or may not produce an entity with the "age" property?
- Bogdanp 9y agoSorry, I wasn't clear. My point was that you don't have to declare the type of `thing` up front in that example. It would be able to infer that it's a record with an `age` field from the function's definition. In your example, `parseJson` would have to return a record with an age field, otherwise the compiler would complain. Personally, I think that represents a reasonable tradeoff in ease of use for safety.
- simon_o 9y agoI think the Scala version does exactly what you want it to do, but you seem to be confused about something that I can't really pin-point. > Why do I need a separate Person and Dog class? You don't. It's an example. > Can't a dog have a name as well? It can. > Why do I need to declare that upfront or ensure that no other part of code can give a Dog a name? I have trouble understanding the meaning of that sentence.
- tome 9y ago> I believe Haskell has these as an extension Not really ... Haskell has libraries that claim to implement this but we Haskellers have to admit Haskell's support for row types is pretty poor. A Clojurist's objections to Haskell on those grounds are valid (in practice though not in theory).
- tome 9y agoDoes this meet your criteria of "first-class" property? It works on any entity that "has" and "age", at least according to my definition of those things! -- The class definition can often be entirely elided -- with Generics or Template Haskell. Instance -- definitions can generally require minimal or no -- implementation either class HasAge r age | r -> age where getAge :: r -> age setAge :: r -> age -> r isOld :: HasAge r => r -> Bool isOld r = getAge r > 30