Fingers crossed Java never implements the `implicit` keyword...
Fingers crossed Java never implements the `implicit` keyword...
[1] - http://learnyouahaskell.com/making-our-own-types-and-typecla...
I'm sure that there's some kind of purity to them, but the fact that a two records can't share field names ends up making them incredibly difficult to use for anything practical. Granted, using the makeFields function with Lenses allows you to kind of avoid the clashing, but that in itself feels like somewhat of a hack.
data Person = Person {
id :: Int,
name :: Text
}
data Account = Account {
id :: Int, # Oh no! Name clash
amount :: Int
}
This is because `id` isn't really a "field," among other thing it's a generated function so that id :: Amount -> Int
id (Amount actualId _) = actualId
This particular case is compounded by the fact that `id` is already the name of the identity function in Haskell's standard library (one reason why many projects that roll their own standard library rename `id` to `identity`).For the partiality:
data AllowedItems = Person { id :: Int, name :: Text} | JustANormalInteger Int
name (JustANormalInteger 5) # Blows up at runtime
That is name has the type AllowedItems -> Text, even though JustANormalInteger 5 is also a member of AllowedItemsThe workaround for clashing is to prefix your fields with your type name (luckily Haskell still has module namespaces so you don't need a globally unique name, just one unique for your module).
The workaround for partiality is to disallow a type with a record to be anything other than that record (e.g. disallow JustANormalInteger or require that Person {...} must first be an independent type before it can be used in AllowedItems).
The talk of lenses is a way to generalize the notion of field accessors and talk about the "path" generated by a series of field accessors in a first class way. E.g. you might imagine that in a standard OO language
myClass.field0.subField0.subSubField0
could have a stand-alone concept of `field0.subField0.subsubField0` as a path through `myClass` that you could then use either to get a value (get(field0.subField0.subSubField0, myClass)) or set a value (set(field0.subField0.subSubField0, myClass, newValue)).Moreover, if you can talk about the paths through a class in a first-class way, what's to say that this path must actually correspond to a true field in a class? It could just be anything "field-like." For example:
lens = integer.lastDigit.leastSignificantBit
set(345, lens, 0) // Yields 344
even though the individual digits of myInteger aren't actually generally truly fields in any OO language nor are the individual bits of a number.Because you have now removed the need for true "fields," lenses are one strategy to get around the hackiness of fields in Haskell and in fact in many ways represent an advance in expressiveness over "true" fields (although they suffer from not really being integrated into the language in the way faux-fields are currently in Haskell).
They're essentially just syntactic sugar for a standard product type with auto-generated functions for access and update. Unfortunately those functions come with foot guns. Here's two:
1. As you pointed out, clashing field names (since fields don't truly exist but are rather functions)
2. Partiality of the generated functions if you have multiple constructors for a type, at least one of which is a record.
Lenses are great, but would benefit from first-class integration as the "blessed" way of interacting with records (this hopefully would also reduce their learning curve).
One of my top wishes for Haskell is proper row types a la Purescript. Purescript records and polymorphic variants are amazing. I would absolutely love if this https://github.com/natefaubion/purescript-checked-exceptions was possible in Haskell.
I'm not super familiar with Purescript, so I can't comment on the row types; are they more-pleasant than Haskell's?
myValue { field0 { subField0 { subSubField0 = 5 }}}
Semantically... oh boy, row types (i.e. extensible product types) and polymorphic variants (i.e. extensible sum types) are fantastic! I'd go so far as to say as long as you give me `newtype`, I would be in favor of row types and polymorphic variants completely replacing `data` declarations. -- forall x here means that our record can have other fields
resetName :: forall x. { name :: String | x } -> { name :: String | x }
resetName itemWithName = itemWithName { name = "Default" }
-- Notice how Person is just a type synonym!
-- And there's no data constructor
-- { name :: String, id :: Int } is a type, not just a constructor
type Person = { name :: String, id :: Int }
type Pet = { name :: String, owner :: Person }
bob = { name : "Bob", id : 0 }
doggy = { name : "Doggy", owner : bob }
-- Look they both work!
bobReset = resetName bob
doggyReset = resetName doggy
-- And no clashes, because records are a first class type declaration
bob.name == "Bob"
doggyReset.name == "Default"
Purescript unfortunately doesn't have built-in polymorphic variants (you can derive them from row types with a bit of type-level hackery), so a bit of this is pseudo-Purescript (mainly the case declaration, functionally you can get the same thing, it just looks slightly different). newtype TextTooShort = ...
newtype TextNotValidASCII = ...
findFirst5CharWord :: forall e. String -> Either (TextTooShort + e) String
asciiNumberRepresentation :: forall e. String -> Either (TextNotValidASCII + e) (Array Int)
processString :: forall e. String -> Either (TextTooShort + TextNotValidASCII + e) (Array Int)
-- No lifting or wrapping of intermediate errors required!
-- All the types still unify!
processString = findFirst5CharWord `andThen` asciiNumberRepresentation
-- But you still get exhaustivity checking!
displayString :: String -> String
displayString input = case (processString input) of
Left TextTooShort -> "You didn't give me enough text!"
Left TextNotValidAscii -> "Whoops no ASCII rep exists!"
Right arrayOfInts -> show arrayOfIntsThat is awkward sometimes yes. Record fields are functions, and you can't have two functions with the same name in the same namespace.
You can, however, use namespaces to import two separate modules with conflicting names. It does mean you have to define the two different records in two different modules.
If, you want different records to have common fields because they are conceptually related, you probably want to use type classes.
I'm not a fan of ad-hoc typeclasses. I know that various approaches in modern Haskell advocate for them, but I think that ad-hoc typeclasses are a smell for overuse of typeclasses when plain-old data types should suffice.
I am a very big proponent of demanding that typeclasses have associated laws. Otherwise you can descend into the same hierarchy mess that plagues statically-typed class-based languages. And sometimes you just want to have different fields be named the same thing without an underlying profound relationship between the two.
For example in OO languages `obj.func(1, 2)` will typically require the compiler to know the type of obj before knowing which func method is being referred to here.
In Haskell however the equivalent expression is `func obj 1 2`. The compiler has already performed name resolution and knows exactly what func is. So that when doing type-checking, it is not only possible for typing information to propagate from object to method, but also from function to object. That is to say, it is possible that the compiler initially knows what type func has, but has no idea what type obj has; it will only do so after type inference using the type of func.
This makes type inference more general and more elegant. I suspect it also makes the implementation of name resolution easier. Unfortunately as you have pointed out, it's practically more of a nuisance.
Also note that this applies to more than just names of fields associated with a type; it even applies to type names associated with a type. For example if you have
class G a where
data N a :: *
The name N is directly usable without even mentioning G. This would require, say in C++, you to write `G::N` to refer to an inner class: class G {
public:
class N;
};
The reasoning is of course the same.My favorite usage is probably in a typesafe Query engine that several people[1] built at Foursquare that is a lot better than what you can find in the java world.
Venue.where(_.closed eqs false)
.orderAsc(_.popularity)
.limit(10)
.modify(_.closed setTo true)
.updateMulti
It allows for all sorts of typesafe queries that will fail at compile time if things are malformed including stuff like failing to query on an index.https://github.com/foursquare/rogue
1. Hi Jorge, Liszka & Neil!
The best case scenario is that they aren't instantly horrified / confused / repulsed.
implicit is one of them
More often than not Java programming just consists of throwing gang signs on your keyboard for your Ide to create tons of boilerplate.
Well case classes can implement traits, might have a companion object and can (poorly) replace enums.
Scala sometimes seems to not need standard classes.
Other languages like go, rust or c can do just fine without classes at all.
There will be new syntax, but otherwise the underlying semantics will remain the same; it's just the implicit keyword itself and the kitchen sink of functionality that it provides in Scala 2 will be split up into new (soft) keywords like "given foo(using: Bar) = ..." rather than "implicit def foo(implicit bar: Bar) = ...", along with "extension listOps of" replacing "implicit class ListOps ...".
The community isn't united over the proposed changes (mostly due to migration concerns it seems), but overall it looks like a nice improvement over the status quo, particularly around typeclass definitions.