The Crystal Programming Language Forum

Is casting the idiomatic way of dealing with nil?

I have a nil-able type property that I use in a conditional statement which checks for nil, but the compiler still requires casting. Is this the idiomatic way, or is there another way?

For example:

# Post.published_at : Time?

def post_img_src_prefix(post : Post) : String?
  if post.published_at
    date = post.published_at.as(Time).to_s("%F") # compiler requires .as(Time) even though nil check in if
    # ...
  else
    # ...
  end
end

Use a local variable to assign the result

def post_img_src_prefix(post : Post) : String?
  if published_at = post.published_at
    date = published_at.to_s("%F")
    # ...
  else
    # ...
  end
end

Object#try might also help

3 Likes

This is because of multi-threading, right?

No, it’s how the type inference works.

I really dislike Nil, it’s a concept that breaks my logic with if everywhere and cause troubles (hopefully, Crystal catch a lot of nil, but not if forced me to use if again)

Due to this error management, I ended up by creating the Monads shards https://github.com/alex-lairan/monads/ and I encourage everyone to try to use monads :smiley:

This will remove the nil factor (unless you need one) by using triples principle: Context, Data, and Manipulations.

Yes, it’s one reason.

The reasons are:

  • the compiler doesn’t look at how the method is implemented. What if the method randomly returns nil or a Time? If the first if succeeds it doesn’t mean the next time the value is not nil
  • in multithreading the value behind published_at might change between the two calls

A benefit of the compiler requiring to assign the value to a local variable is that the method is only called once. It kind of forces you to program in an optimized way. In Ruby it’s very frequent to call methods many times, or access hash values many times.

I think monads are useful in Haskell because they provide a good and necessary abstraction in the language.

That said, I think nil is pretty easy to deal with in Crystal, you can use if, we have try, etc.

nil is actually a monad in Crystal. Something is a monad not because it inherits Monad or because it’s called Maybe. Something is a monad because of the operations it allows.

Reading Haskell’s wiki it says a monad can be thought as a composable computation. A classic example is fmap for Maybe (a nilable something in Crystal) to only perform a computation on Nothing (nil in Crystal). This is just try in Crystal:

nilable_value.try { |value| do_something_with value }

So you see, we don’t need to include any library, create a new type or anything, try is already there and is the equivalent of fmap.

Also monads are related to the do notation in Haskell, which, again, is not needed in Crystal.

In summary, Monads exist in Haskell because of the way it works, but they are not needed in Crystal.

If you want to use monads, go ahead, but as the author of the language I can’t recommend them because the language already has built-in solutions for these problems, solutions which are simpler and more efficient.

3 Likes

Ahhh, of course, the name made be think of a simple property, but the compiler only sees a method call there with a union type.

Even if it was a simple property (for example an instance variable), its value could still change between invokations in multithreaded execution.

1 Like

Yes, that was the use case I had in my mind above.

Yeah, that’s what I had before. Though I got tired of all of the temp local variables. It’s a code smell in some languages. However, if it’s recommended for Crystal, I’ll revert my changes.

Thanks for the info!

Thanks for explaining this! I was wondering myself why the compiler couldn’t evaluate the property without the cast in the true branch.

Theoretically though, at some point in the future, the compiler (or pre-processor) could evaulate the method and know the true branch doesn’t need casting, right?

A better method of handling Nil is removing Nil from the types to begin with. However, there are valid cases where nil makes sense, but having properties be nilable just because isn’t a good practice.

So without knowing anything else about your program it’s hard to say, but an implementation (if possible) that reduces the need/amount of nilable properties would help as well.

Also, again without knowing anything about your program, wouldn’t it make more sense to have post_img_src_prefix method within the Post object itself? I’m assuming there is additional logic going on that we don’t see, but given your example it sure seems redundant to pass a function an instance of Post when you could just call the method on the Post itself.

The Post model is populated from a corresponding record in Postgres. I have a published_at nullable column for the date when that post was published.

Getting rid of the nilable published_at in this case doesn’t seem ideal. I want to be able to save unpublished posts in the database and later published them.

True, post_img_src_prefix could live in my Post model class. Though, it’s dealing with html so I have it in a PostHelper view helper class. It’s a separations-of-concerns thing; not mixing data representation with html-related view functions.

But, admittedly, I sometimes question if s-o-c really matters in my own personal/private project which only I will work on. There’s nothing preventing all Post-related methods from living in one Post class. If this were a functional-programming project, I’d probably have one Post module.

Yea of course. It makes sense here.

Is it possible you could take advantage of https://crystal-lang.org/api/master/ECR.html#def_to_s(filename)-macro or something similar?

That’s interesting. I’ve used a similar technique in Ruby using ERB + binding for custom templates. I like how Crystal uses an IO stream instead of Ruby’s variable binding. :+1:t2:

Thanks for pointing this out! I could take advantage of it if I wanted a Presenter class, or if I ever port my Ruby project which has custom templates. For this particular project, though, I’m using Kemal which already supports views and partials.

1 Like

No. It can’t know that because it’s factually untrue. The first call to published_at could return Time while the second one returns nil. As long as the method returns a value that can be modified from other threads, there’s no guarantee it won’t change between invokations.

Does this mean Crystal is pass-by-reference instead of pass-by-value? You’re saying the pass-in reference to my Post instance could be changed inbetween the if nil check and when it’s being used? I get that.

I guess if it were pass-by-value the compiler could have an AST (or bytecode) branches for the true and false conditions and it wouldn’t require the cast.

Feel free to ignore my questions if they annoy you, or if they are a waste of your time. I’m just asking for my own edification, no pressure.

In Crystal there are reference types and value types. For example, classes are reference types, and basic types like booleans, integers, and notably structs and tuples are value types.

When you pass an instance of the Post class as an argument you are passing a reference to the post object. You’re always working with references. As in Java or Ruby. If the post instance is mutable through its API, the called method can change its state.

That is not possible with value types. If the post was an instance of a struct, then the whole thing is copied. You can be certain there is no way the called method can modify the state of the original struct in the caller regardless of whether there is API for it.

In C, structs are also copied like that. If you want to be able to modify a struct in the called function you need to add an indirection by passing a pointer to the struct.

I don’t like to use the terms pass by reference and pass by value because they are often confusing. For example, Java is a pass by value language in my definition of pass by value (you pass references by value, according to the JLS).

C is also pass by value, you may pass primitive types or pointers, but they go always by value.

To me, pass by reference means in practice that you can change the content of variables in the caller. Swap is the canonical example:

a = 1
b = 0

swap(a, b)

# Now a = 0, and b = 1 in the caller.

Perl is pass by reference.

(The documentation of Crystal mentions often “pass by reference”, though, so the core team may work with different definitions. But practical usage, regardless of the names, is the one explained above.)

Note that value types can also be mutable; usually they are not, but they can. So if the program does aliasing via pointers, there are no deductions the compiler can make, even for value types.

Can you show an example Brian?

Just to clarify, I was not saying value types are inmutable (indeed, my wording implies they can be mutable). Here we are talking about what happens to things when passed as arguments in method calls. All I am saying is that for value types things are copied, so you can’t change the struct of the caller if passed a struct.

In my view, Crystal is pass by value, like Java, C, or Ruby.