Every new GHC release is like a little Christmas morning! The pace of new feature development impresses me.
I'm particularly interested in the new type-level literals (which seem like they will make it much less cumbersome to express certain static constraints) and the convenient new syntax for multi-way if expressions (I've long wanted Lisp's `cond` in Haskell) and case analysis on lambda arguments.
I've been away from Haskell for a while, it's great to see interesting things still being done. Wondering what the use of type-level naturals in Haskell is though? I dabbled with Agda a bit where they're used to satisfy the termination checker, is that sort of thing being done in Haskell now?
Also one thing that always gave me pause about Haskell was the record system, and the inability to have fields with the same name in the same module (especially when the fields are generated from a database). Has there been any progress on that front?
Also one thing that always gave me pause about Haskell was the record system, and the inability to have fields with the same name in the same module (especially when the fields are generated from a database). Has there been any progress on that front?
There have been a zillion proposals, but as far as I know, not much actual consensus or implementation work.
Anecdotally, I've never run into a situation where this was anything more than a minor annoyance. I don't doubt that it can be a problem under the right set of circumstances, but I think the issue gets more attention than its magnitude truly merits. People theorize a lot about the difficulties it can hypothetically cause, but in practice, it seldom actually manifests itself.
In my experience it was a perpetual pain point. Do you namespace all your field names with a common prefix based on the outer type name? Or do you just get lucky and not have the names collide?
In the past most people prefixed their field names with the type name. Nowadays, the most common thing is to put types in their own module, and use the module system to sort out any conflict. Qualified imports can add a prefix, but you can also import parts unqualified, or hide one problematic field.
While I prefer the module solution, it's not always possible when you have mutually recursive record definitions. Well, technically it is possible - but it's very unpleasant [1].
I've had quite the opposite experience. Every programmer I've convinced to try haskell has had problems with records. It is incredibly limiting when coming from any language that has objects or structs. Having to prepend every single name in every single record with the information that is already in that record's name is horribly ugly. I think there is no progress made because nobody can decide on a perfect solution, so all the good enough solutions sit and rot.
I suppose that, in my personal experience, I just haven't written a lot of Haskell code that used multiple similar record types in the same namespace.
I don't have any evidence for this, but I feel like it's often a bit of a code smell when this problem crops up. It seems like it frequently happens when programmers used to OOP are trying to shoehorn an object oriented design into a language which is just fundamentally not object oriented. I'm not saying that that's the only situation in which this problem arises, nor that the complaint isn't a legitimate one, regardless. There is no question that the situation could be improved. And perhaps I've just gotten lucky so far and haven't come across a scenario in which this really causes difficulties.
That said, there are always ways to work around the issue. I will make no claims of elegance here, but it's really not that much trouble. For one, you can define the different record types in separate modules, and then import them qualified. That's easy enough to do; it's just annoying to have to use multiple files.
Alternatively, you can keep the ugly prefixes on the record field names, but then use a type class to elide them away everywhere you refer to them. This technique only works when the similarly-named fields have the same type in each record, unless you use fundeps or associated types to attach additional type information to your class. For example:
{-# LANGUAGE TypeFamilies #-}
data Foo = Foo {foo_field1 :: Char, foo_field2 :: String}
data Bar = Bar {bar_field1 :: Int, bar_field2 :: String}
class MyRecord a where type Field1Type a
field1 :: a -> Field1Type a
field2 :: a -> String
instance MyRecord Foo where type Field1Type Foo = Char
field1 = foo_field1
field2 = foo_field2
instance MyRecord Bar where type Field1Type Bar = Int
field1 = bar_field1
field2 = bar_field2
You could even use Template Haskell to automatically generate the instances so you don't have to tediously type them out yourself, especially handy for records with a large number of fields.
So I'm not claiming that there's no problem to be solved. I would be happy to see a real solution to this that doesn't require any workarounds. I just don't think it's a big enough issue to keep me from using a language which is, in nearly all other respects, awesome. :)
Quite a few packages on Hackage use type-level naturals, hand-implemented using mechanisms like a type-level zero and a type-level successor function (and often a type-level "times 2" function to avoid the huge inefficiency of a unary representation). Building type-level naturals into GHC will allow all of those packages to become much cleaner and saner.
Type-level strings (AKA Symbols) also allow very impressive constructs, such as type-safe extensible records using type-level strings as keys.
Haskell already has the capability to write type-level representations of numbers, so we can have types like
zipVec :: Vector a n -> Vector b n -> Vector (a, b) n
where Vector a b is the type of a vector of elements of type a that is of length n. The above function will therefore only zip together two vectors if they have the same length; otherwise it's a compile-time error. Right now, people usually roll their own representations of numbers, but this new extension means not only do we have a standard one, we can now write something like myBuffer :: Buffer Int 256 where 256 is a type-level number.
With type-level symbols, we could do something like
data StringWrapper (a :: Symbol) = S String
getUserData :: IO (StringWrapper "unsanitized")
writeToDB :: StringWrapper "sanitized" -> IO ()
sanitize :: StringWrapper "unsanitized" -> StringWrapper "sanitized"
stringLength :: StringWrapper a -> Int
so that writing code like
do x <- getUserData
putStrLn ("Length is " ++ show (stringLength x))
writeToDb x -- forgetting to sanitize
produces a compile-time error because the types don't match up.
This is a good example, but I'm not sure it's very real world. You wouldn't really use symbols in this case - you'd specifically introduce a Sanitized type and promote it to the kind level. But I'm picking holes, it's a good example of the power that the new type system gives us :)
Type-level naturals let you express things like "map on a Vector of length n will produce a Vector of length n" and "given an element x and a vector xs of length n, cons x xs will produce a Vector of length (n + 1)". Other functions can inspect these nats and prevent you from taking the head of an empty list, as a very basic example.
Does anyone know how multiple arguments work with lambda-case[1]? Is the solution just using a tuple and manually currying like so (suggested here[2])?
curry (\case (Nothing,_) -> ...)
(It seems like this would defeat the purpose of lambda-case for lambdas with more than 2 arguments, and it is awkward even for those with exactly 2 arguments.)
Multi-argument lambda case proves quite difficult to handle sanely, because normal non-lambda case doesn't do multiple arguments, just tuples. Having multiple arguments requires separating the pattern for each argument somehow, a problem that doesn't come up for non-lambda cases.
I'm particularly interested in the new type-level literals (which seem like they will make it much less cumbersome to express certain static constraints) and the convenient new syntax for multi-way if expressions (I've long wanted Lisp's `cond` in Haskell) and case analysis on lambda arguments.