4 ms·
For terseness, it's probably hard to beat Julia: addone1(x) = x+1 Completely generic with type inference, and compiles to optimal machine code. But it als
by photon-torpedo 10y ago
For terseness, it's probably hard to beat Julia:
addone1(x) = x+1
Completely generic with type inference, and compiles to optimal machine code. But it also suffers from the "latent type errors deferred to the user" that the article discusses. Calling it with a type that does not support adding 1 will raise an error for a non-existing "+" method:
addone1("0")
ERROR: MethodError: no method matching +(::String, ::Int64)
This is pretty understandable in this case, but we may prefer annotating the addone function so that it can only be called with types that support adding 1. Using the (very conservative) notion that only numbers can have 1 added to them, we could use Julia's type hierarchy to restrict the types for which addone may be called:
addone2(x::T) where T<:Number = x+1
Calling it with an incompatible type now properly points to the outer level:
addone2("0")
ERROR: MethodError: no method matching addone2(::String)
But this type of dispatch restrictions rely on a type hierarchy, causing similar problems as class hierarchies in OO languages. For example, the user might have defined their own type that supports addition but not multiplication. Using addone on this type makes sense, but the type shouldn't be a subtype of Number. Such more flexible dispatch scenarios can be achieved with traits. Although Julia currently doesn't have direct language support for traits, they can be implemented inside the language, with macros for syntactic sugar. With traits, the previous example becomes:
using SimpleTraits
@traitdef CanAddOne{T}
@traitimpl CanAddOne{T} <- issubtype(T,Number)
@traitfn addone3{T; CanAddOne{T}}(x::T) = x+1
Still relatively straight-forward to write and read, and the trait-restricted addone has the same performance as the original one. For incompatible types, the error message still points to the outer level:
addone3("0")
ERROR: MethodError: no method matching addone3(::Type{SimpleTraits.Not{CanAddOne{String}}}, ::String)
Importantly, the user can extend the CanAddOne trait (with additional @traitimpl lines) to cover their own type, without being forced to make it a subtype of Number.
- deckiedan 10y agoHaskell: add1 = (+1) > add1 23 24 > add1 2.01 3.01 > add1 "0" No instance found... But that error is at compile time.
- eggy 10y agoAnd in J: add1 =: >: add1 1r3 4r3 add1 0 1
- photon-torpedo 10y agoRight, with currying it can be even more terse. And getting the error at compile time is actually preferable. My Haskell is very rusty, but IIRC it will deduce that the argument to add1 must be Num, because of the type constraint on (+): (+) :: Num a => a -> a -> a So if I'm not mistaken, your add1 is equivalent to my addone2, i.e. incorporating a type constraint based on the type hierarchy. I'm curious: what's the equivalent of my addone3 in Haskell? How would you define add1 so that it also works on a type T later defined by the user, with T just supporting addition but T not subtype of Num? Do you need to define your own addition function (acting as a proxy for (+) for Num types)?
- mmalone 10y agoI'm still pretty new to Haskell, but I think there are several ways. The equivalent to your addone3 would be some sort of Template Haskell incantation. Template Haskell gives you lisp-style hygienic macros that are expanded and type checked at compile time (except more complicated because of the more complicated semantics and AST). Haskell also provides a ton of options for generic programming that provide similar power (still statically typed). GADTs (Generalized Algebraic Data Types) might be of use here. There are also existential types and type classes. What you probably really want though is a dependently typed language like Idris or Agda. If you're not familiar, here's the one liner: Java generics are types that depend on types (like a List of Bools). Dependent types are types that can depend on values (like a List of exactly 3 Bools). So in theory you should be able to define the type you're looking for here, but I don't know what the concrete syntax would be. The problem with dependently typed languages right now is that the surface languages and concepts tend to be hard for people to grok. Personally I think meta-programming facilities and DSLs will help with this. Ther are lots of languages heading in that direction.
- kazinator 10y ago